Um Resend-Webhooks lokal in Next.js zu testen, erstellen Sie eine App Router POST-Route, die den Rohtext liest, überprüfen Sie deren Svix-Header mit Ihrem Resend-Signaturgeheimnis und registrieren Sie eine HTTPS-Tunnel-URL im Resend-Dashboard. Senden Sie eine E-Mail über Resend und wenden Sie sich dann an real email.sent, email.delivered, email.bounced, oder email.complained Ereignisse auf localhost.
Was ein Resend-Webhook Ihrer Anwendung mitteilt
Eine API-Antwort, die besagt, dass eine E-Mail angenommen wurde, ist kein Beweis dafür, dass sie den Empfänger erreicht hat. Die Lieferung erfolgt asynchron. Mit Resend-Webhooks kann Ihre Anwendung den Nachrichtenstatus aktualisieren, fehlerhafte Adressen unterdrücken, Bounces melden und auf Beschwerden reagieren, nachdem die ursprüngliche Sendeanforderung abgeschlossen ist. Die offizielle Resend Webhook-Dokumentation listet Ereignistypen und Dashboard-Setups auf.
Ein lokaler Webhook-Test sollte die gesamte Zustandsmaschine abdecken und nicht nur, ob ein POST Ihre Route erreicht. Korrelieren Sie die E-Mail-ID jedes Ereignisses mit dem beim Senden erstellten Datensatz. Behandeln Sie Zustände als Übergänge: akzeptiert, gesendet, zugestellt, verzögert, zurückgesendet, beanstandet, geöffnet oder ggf. angeklickt. Ein späteres Duplikat darf keinen nützlicheren Zustand überschreiben oder denselben Alarm zweimal auslösen.
Erstellen Sie die Route Next.js App Router
Installieren Sie den für das Signaturformat gepflegten Verifizierer:
npm install svix
Erstellen Sie dann eine Knoten-Laufzeitroute. Resend signiert den Originaltext, also verwenden request.text() genau einmal vor dem Parsen.
// app/api/webhooks/resend/route.ts
import { Webhook } from 'svix';
export const runtime = 'nodejs';
export async function POST(request: Request) {
const payload = await request.text();
const headers = {
'svix-id': request.headers.get('svix-id') ?? '',
'svix-timestamp': request.headers.get('svix-timestamp') ?? '',
'svix-signature': request.headers.get('svix-signature') ?? '',
};
let event: ResendEvent;
try {
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET!);
event = webhook.verify(payload, headers) as ResendEvent;
} catch {
return Response.json({ error: 'Invalid webhook signature' }, { status: 400 });
}
await enqueueResendEvent({
deliveryId: headers['svix-id'],
event,
});
return Response.json({ received: true });
}
Das Signaturgeheimnis gehört zu diesem Webhook-Endpunkt und beginnt normalerweise mit einem anbieterspezifischen Präfix. Kopieren Sie es aus den Webhook-Einstellungen Resend in eine ignorierte lokale Umgebungsdatei, z .env.local. Es handelt sich nicht um den API-Schlüssel Resend, der zum Senden von E-Mails verwendet wird.
Warum die drei Svix-Header wichtig sind
svix-idIdentifiziert eine Lieferung eindeutig und ist der beste Idempotenzschlüssel.svix-timestampbindet die Signatur an eine Zeit, sodass der Verifizierer veraltete Anfragen außerhalb seiner Toleranz ablehnen kann.svix-signaturekann eine oder mehrere versionierte Signaturen enthalten, die zur Authentifizierung des Körpers verwendet werden.
Implementieren Sie dieses Protokoll nicht durch Aufteilen von Header-Strings, es sei denn, Sie haben einen zwingenden Grund. Das SDK übernimmt die Kodierung, Mehrfachsignaturen und Zeitstempelprüfungen. Resend empfiehlt ausdrücklich die Verwendung des Signaturgeheimnisses und dieser Header zur Überprüfung. Je tiefer Leitfaden zur Signaturüberprüfung erklärt, warum Rohbytes und zeitsichere Prüfungen wichtig sind.
Machen Sie Next.js verfügbar und registrieren Sie den Endpunkt
- Lauf
npm run devund bestätigen Sie, dass die Anwendung Port 3000 überwacht. - Starten
npx portpreview 3000in einem anderen Terminal. - Erstellen Sie in Resend einen Webhook, dessen Endpunkt ist
https://YOUR-TUNNEL.portpreview.dev/api/webhooks/resend. - Wählen Sie nur die E-Mail-Ereignisse aus, die Ihre Bewerbung verarbeitet.
- Kopieren Sie das Signaturgeheimnis des Endpunkts in
RESEND_WEBHOOK_SECRETund starten Sie Next.js neu, damit die Variable geladen wird. - Senden Sie eine Nachricht über eine verifizierte Domäne und überprüfen Sie die Ereignisse, die die lokale Route erreichen.
Halten Sie die öffentliche URL für die Sitzung stabil. Wenn sich der Tunnelursprung ändert, bearbeiten Sie den Endpunkt Resend, bevor Sie den Test erneut durchführen. Ein mit einer alten URL konfigurierter Endpunkt kann Ihren neuen Prozess nicht erreichen, selbst wenn localhost selbst fehlerfrei ist.
Verwenden Sie einen typisierten Ereignis-Dispatcher
Webhook-Nutzlasten sollten in einen schmalen Dispatcher eingegeben werden. Validieren Sie erforderliche Felder und machen Sie nicht erkannte Ereignistypen beobachtbar, ohne sie als Serverausfälle zu behandeln.
async function processEvent(event: ResendEvent) {
switch (event.type) {
case 'email.delivered':
await markDelivered(event.data.email_id, event.created_at);
break;
case 'email.bounced':
await markBounced(event.data.email_id, event.data.bounce?.message);
await suppressIfPermanent(event.data);
break;
case 'email.complained':
await suppressRecipients(event.data.to);
await alertCompliance(event.data.email_id);
break;
default:
await recordUnhandledEvent(event);
}
}
Halten Sie die Nutzlasttypen am aktuellen Resend-Schema ausgerichtet, anstatt davon auszugehen, dass jedes Ereignis über identische Daten verfügt. Beispielsweise können Bounce-Details und Empfängerlisten nur für bestimmte Ereignisse relevant sein. Speichern Sie den Ereignistyp, die E-Mail-ID des Anbieters, den Ereigniszeitstempel und eine minimale redigierte Nutzlast für Supportuntersuchungen.
Machen Sie die Handhabung idempotent, bevor Sie Wiederholungsversuche durchführen
Webhook-Systeme bieten in der Praxis ein mindestens einmaliges Zustellverhalten. Nach dem Festschreiben Ihrer Datenbank, aber bevor der Anbieter Ihre 200-Antwort erhält, kann es zu einer Zeitüberschreitung kommen. Der Anbieter wiederholt dann eine bereits beantragte Anfrage. Benutzen svix-id als eindeutigen Lieferschlüssel und fügen Sie ihn in derselben Transaktion wie die Statusänderung ein.
await db.transaction(async (tx) => {
const inserted = await tx.webhookDelivery.insertOnce({
provider: 'resend',
deliveryId,
});
if (!inserted) return;
await applyEmailEvent(tx, event);
});
Deduplizieren Sie nicht ausschließlich anhand der E-Mail-ID, da eine E-Mail legitimerweise mehrere Ereignistypen empfängt. Behalten Sie abhängig von Ihrem Datenmodell sowohl einen eindeutigen Schlüssel auf Bereitstellungsebene als auch Zustandsübergangsregeln bei. Lesen Webhook-Wiederholungs- und Idempotenzmuster bevor Sie Ereignisse mit Abrechnung, Unterdrückung oder Kundenbenachrichtigungen verbinden.
Kehren Sie schnell zurück, ohne die Ereignis zu verlieren
Im Anforderungspfad ist eine Signaturüberprüfung angebracht. Langsame Geschäftsarbeit ist es nicht. Behalten Sie das überprüfte Ereignis bei oder stellen Sie es in die Warteschlange und geben Sie dann 2xx zurück. Wenn Sie vor einem dauerhaften Schreibvorgang zurückkehren, kann das Ereignis bei einem Prozessabsturz verloren gehen. Wenn Sie auf mehrere Remote-APIs warten, kann es bei Ihrem Endpunkt zu einer Zeitüberschreitung und zu Wiederholungsversuchen kommen. Eine Datenbank-Posteingangstabelle ist oft das einfachste lokale und Produktionsdesign.
Generieren Sie nützliche Testereignisse
Gesendet und geliefert
Senden Sie von einer verifizierten Domain aus an eine von Ihnen kontrollierte Adresse. Notieren Sie die von der Sende-API zurückgegebene E-Mail-ID und bestätigen Sie, dass eingehende Ereignisse dieselbe Zeile aktualisieren. Der Zeitpunkt der Übermittlung variiert je nach Empfängerserver. Gehen Sie daher nicht davon aus, dass Ereignisse sofort oder in einer einfachen Reihenfolge eintreffen.
Springt
Verwenden Sie die dokumentierten Testadressen oder Testfunktionen von Resend, anstatt Datenverkehr zu nicht verwandten Domänen zu erfinden. Stellen Sie sicher, dass dauerhafte Fehler zukünftige E-Mails unterdrücken, während vorübergehende Bedingungen Ihrer Wiederholungsrichtlinie folgen. Nicht jedes verzögerte Ereignis automatisch unterdrücken.
Beschwerden
Bei der Beschwerdebearbeitung geht es sowohl um Zustellbarkeit als auch um Compliance-Logik. Stellen Sie sicher, dass ein wiederholter Webhook keine wiederholten Warnungen auslöst, und stellen Sie sicher, dass der betroffene Empfänger gemäß Ihrer Richtlinie von späteren Kampagnen ausgeschlossen wird.
Beheben Sie Resend-Webhook-Fehler
Die Signaturüberprüfung schlägt immer fehl
Bestätigen Sie, dass das Signaturgeheimnis des Endpunkts – nicht der API-Schlüssel – geladen ist. Benutzen await request.text(), JSON nicht analysieren und erneut stringifizieren und alle drei Svix-Header mit ihren genauen Werten übergeben. Starten Sie den Entwicklungsserver nach der Änderung neu .env.local.
Die Route gibt 404 oder 405 zurück
App Router Routendateien müssen benannt werden route.ts unterhalb der vorgesehenen URL-Segmente einfügen und exportieren POST. Überprüfen Sie, ob die Middleware die Tunnelanforderung in ein Gebietsschema oder eine Anmeldeseite umschreibt. Testen Sie die öffentliche URL mit Curl und überprüfen Sie die tatsächliche Antwort.
Resend zeigt Wiederholungsversuche trotz erfolgreicher Verarbeitung an
Stellen Sie sicher, dass jede erfolgreiche Verzweigung umgehend 2xx zurückgibt. Fehler, die nach einer Datenbankaktualisierung ausgegeben werden, können zu einer 500 und einem doppelten Wiederholungsversuch führen. Machen Sie die Verarbeitung transaktional und idempotent und überprüfen Sie dann die Antwortlatenz.
Ereignisse kommen an, können aber nicht mit einer E-Mail verknüpft werden
Behalten Sie die E-Mail-ID des Anbieters aus der ursprünglichen Resend-Sendeantwort bei. Verlassen Sie sich nicht auf Betreffzeilen oder Empfängeradressen als Identifikatoren. Diese Felder sind weder eindeutig noch stabil genug für eine Korrelation.
Wiederholte Aufnahmen schlagen bei der Zeitstempelüberprüfung fehl
Das ist zu erwarten, wenn eine alte signierte Anfrage über den normalen Verifizierer wiedergegeben wird: Der Zeitstempel liegt möglicherweise außerhalb der zulässigen Toleranz. Bevorzugen Sie die erneute Lieferung durch den Anbieter, sofern verfügbar. Für isolierte Geschäftslogiktests führen Sie eine einmalige Überprüfung durch, speichern einen bereinigten Ereigniseinbau und testen den Dispatcher separat. Die Wiederholungsanleitung erklärt diese Grenze.
Sicherheit und Datenschutz für E-Mail-Ereignistests
- Nie aussetzen
RESEND_API_KEYoder das Endpunktsignaturgeheimnis in Quellen, Browserpaketen, Screenshots oder Anforderungsprotokollen. - Überprüfen Sie dies, bevor Sie das Ereignis analysieren oder beibehalten.
- Schwärzen Sie Empfänger, Betreffzeilen, Header und Nachrichtenmetadaten aus gemeinsam genutzten Tunnelerfassungen.
- Wenden Sie Aufbewahrungsbeschränkungen auf rohe Webhook-Nutzlasten an; Speichern Sie nur das, was Support und Compliance erfordern.
- Verwenden Sie ein separates lokales Endpunktgeheimnis aus der Produktion und rotieren Sie es, wenn der Testendpunkt gelöscht wird.
Das endgültige Design sollte nach der Bereitstellung identisch funktionieren: öffentlicher HTTPS-Endpunkt, Rohkörperüberprüfung, dauerhafte Idempotenz, schnelle Bestätigung und asynchrone Statusbehandlung. App Router-spezifische Rohkörperdetails finden Sie im Next.js Webhook-Localhost-Leitfaden.
