Aby przetestować webhooki Resend lokalnie w Next.js, utwórz trasę App Router POST, która odczytuje surową treść, zweryfikuj jej nagłówki Svix za pomocą klucza podpisu Resend i zarejestruj adres URL tunelu HTTPS w panelu kontrolnym Resend. Wyślij e-mail przez Resend, a następnie zajmij się rzeczywistością email.sent, email.delivered, email.bouncedlub email.complained zdarzenia na localhoście.
Co webhook Resend mówi Twojej aplikacji
Odpowiedź API informująca, że wiadomość e-mail została zaakceptowana, nie jest dowodem na to, że dotarła ona do adresata. Dostawa odbywa się asynchronicznie. Webhooki Resend pozwalają aplikacji aktualizować stan wiadomości, usuwać nieprawidłowe adresy, zgłaszać zwroty i reagować na skargi po zakończeniu pierwotnego żądania wysłania. oficjalna dokumentacja webhooka Resend wyświetla listę typów zdarzeń i konfigurację pulpitu nawigacyjnego.
Test lokalnego webhooka powinien obejmować całą maszynę stanu, a nie tylko to, czy POST dotrze do Twojej trasy. Powiąż identyfikator e-mail każdego zdarzenia z rekordem utworzonym podczas wysyłania. Traktuj stany jako przejścia: zaakceptowane, wysłane, dostarczone, opóźnione, odesłane, złożone zażalenie, otwarte lub kliknięte, jeśli ma to zastosowanie. Późniejszy duplikat nie może zastąpić bardziej użytecznego stanu ani dwukrotnie wywołać tego samego alertu.
Utwórz trasę Next.js App Router
Zainstaluj weryfikator utrzymywany dla formatu podpisu:
npm install svix
Następnie utwórz trasę środowiska uruchomieniowego węzła. Resend podpisuje oryginalne ciało, więc użyj request.text() dokładnie raz przed analizą.
// 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 });
}
Sekret podpisywania należy do tego punktu końcowego elementu webhook i zwykle zaczyna się od prefiksu specyficznego dla dostawcy. Skopiuj go z ustawień webhooka Resend do ignorowanego pliku środowiska lokalnego, takiego jak .env.local. To nie jest klucz API Resend używany do wysyłania wiadomości e-mail.
Dlaczego trzy nagłówki Svix mają znaczenie
svix-idjednoznacznie identyfikuje dostawę i jest najlepszym kluczem idempotencji.svix-timestampwiąże podpis z czasem, umożliwiając weryfikatorowi odrzucenie nieaktualnych żądań poza jego tolerancją.svix-signaturemoże zawierać jeden lub więcej wersjonowanych podpisów używanych do uwierzytelnienia treści.
Nie implementuj tego protokołu, dzieląc ciągi nagłówków, chyba że masz ważny powód. Zestaw SDK obsługuje kodowanie, wiele podpisów i sprawdzanie znaczników czasu. Resend wyraźnie zaleca użycie sekretu podpisu i tych nagłówków do weryfikacji. Im głębiej przewodnik weryfikacji podpisu wyjaśnia, dlaczego surowe bajty i kontrole bezpieczne pod względem czasu mają znaczenie.
Ujawnij Next.js i zarejestruj punkt końcowy
- Biegnij
npm run devi potwierdź, że aplikacja nasłuchuje na porcie 3000. - Zacznij
npx portpreview 3000w innym terminalu. - W Resend utwórz webhook, którego punktem końcowym jest
https://YOUR-TUNNEL.portpreview.dev/api/webhooks/resend. - Wybierz tylko zdarzenia e-mail, które przetwarza Twoja aplikacja.
- Skopiuj sekret podpisywania punktu końcowego do
RESEND_WEBHOOK_SECRETi zrestartuj Next.js, aby załadować zmienną. - Wyślij wiadomość korzystając ze zweryfikowanej domeny i sprawdź zdarzenia, które docierają na trasę lokalną.
Utrzymuj publiczny adres URL stabilny podczas sesji. Jeśli początek tunelu ulegnie zmianie, przed ponownym testowaniem dokonaj edycji punktu końcowego Resend. Punkt końcowy skonfigurowany ze starym adresem URL nie może dotrzeć do nowego procesu, nawet jeśli sam host lokalny jest w dobrym stanie.
Użyj typowanego dyspozytora zdarzeń
Ładunki elementu webhook powinny trafiać do jednego wąskiego modułu rozsyłającego. Zweryfikuj wymagane pola i umożliwij obserwację nierozpoznanych typów zdarzeń bez traktowania ich jako awarii serwera.
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);
}
}
Zachowaj zgodność typów ładunków z bieżącym schematem Resend, zamiast zakładać, że każde zdarzenie ma identyczne dane. Na przykład szczegóły odesłań i listy odbiorców mogą być istotne tylko w przypadku niektórych zdarzeń. Zapisz typ zdarzenia, identyfikator e-mail dostawcy, sygnaturę czasową zdarzenia i minimalną zredagowaną zawartość na potrzeby dochodzenia w ramach pomocy technicznej.
Zapewnij idempotentną obsługę przed testowaniem ponowień
W praktyce systemy webhook zapewniają co najmniej jednorazową dostawę. Przekroczenie limitu czasu może nastąpić po zatwierdzeniu bazy danych, ale zanim dostawca otrzyma odpowiedź 200. Następnie dostawca ponawia żądanie, które już złożyłeś. Użyj svix-id jako unikalny klucz dostawy i wstaw go do tej samej transakcji, co zmiana stanu.
await db.transaction(async (tx) => {
const inserted = await tx.webhookDelivery.insertOnce({
provider: 'resend',
deliveryId,
});
if (!inserted) return;
await applyEmailEvent(tx, event);
});
Nie wykonuj deduplikacji wyłącznie na podstawie identyfikatora e-mail, ponieważ w jednym e-mailu legalnie znajduje się wiele typów zdarzeń. W zależności od modelu danych zachowaj zarówno unikalny klucz na poziomie dostarczania, jak i reguły zmiany stanu. Przeczytaj wzorce ponawiania prób webhooka i idempotencji przed połączeniem zdarzeń z rozliczeniami, pomijaniem lub powiadomieniami klientów.
Szybko zwracaj odpowiedź bez utraty zdarzenia
Weryfikacja podpisu jest odpowiednia w ścieżce żądania; powolna praca biznesowa nie jest. Utrzymaj lub umieść w kolejce zweryfikowane zdarzenie, a następnie zwróć 2xx. Jeśli wrócisz przed jakimkolwiek trwałym zapisem, awaria procesu może spowodować utratę zdarzenia. Jeśli zaczekasz na kilka zdalnych interfejsów API, Twój punkt końcowy może przekroczyć limit czasu i zaprosić do ponownych prób. Tabela skrzynki odbiorczej bazy danych jest często najprostszym projektem lokalnym i produkcyjnym.
Generuj przydatne zdarzenia testowe
Wysłano i dostarczono
Wyślij na adres, który kontrolujesz ze zweryfikowanej domeny. Zapisz identyfikator e-mail zwrócony przez interfejs API wysyłania i potwierdź, że zdarzenia przychodzące aktualizują ten sam wiersz. Czas dostarczenia różni się w zależności od serwera odbiorcy, dlatego nie należy zakładać, że zdarzenia nadejdą natychmiast lub w uproszczonej kolejności.
Odbicia
Zamiast wymyślać ruch do niepowiązanych domen, korzystaj z udokumentowanych adresów testowych lub funkcji testowych Resend. Sprawdź, czy trwałe awarie uniemożliwiają przyszłą pocztę, a warunki tymczasowe są zgodne z zasadami ponawiania. Nie wyłączaj automatycznie każdego opóźnionego zdarzenia.
Reklamacje
Rozpatrywanie reklamacji to zarówno dostarczalność, jak i logika zgodności. Upewnij się, że powtarzający się webhook nie tworzy powtarzających się alertów i upewnij się, że odbiorca, którego to dotyczy, zostanie wykluczony z późniejszych kampanii zgodnie ze swoimi zasadami.
Rozwiązywanie problemów z błędami webhooka Resend
Weryfikacja podpisu zawsze kończy się niepowodzeniem
Potwierdź, że tajemnica podpisywania punktu końcowego — a nie klucz API — została załadowana. Użyj await request.text(), nie analizuj ani nie zmieniaj ponownie ciągu JSON i przekaż wszystkie trzy nagłówki Svix z ich dokładnymi wartościami. Po zmianie zrestartuj serwer programistyczny .env.local.
Trasa zwraca 404 lub 405
Pliki tras App Router muszą mieć nazwy route.ts pod zamierzonymi segmentami adresu URL i wyeksportuj POST. Sprawdź, czy oprogramowanie pośredniczące przepisuje żądanie tunelu do ustawień regionalnych lub strony logowania. Przetestuj publiczny adres URL za pomocą curl i sprawdź rzeczywistą odpowiedź.
Resend pokazuje ponowne próby pomimo pomyślnego przetwarzania
Sprawdź, czy każde pomyślne rozgałęzienie natychmiast zwraca wartość 2xx. Błędy zgłoszone po aktualizacji bazy danych mogą spowodować błąd 500 i ponowną próbę. Spraw, aby przetwarzanie było transakcyjne i idempotentne, a następnie sprawdź opóźnienie odpowiedzi.
Wydarzenia docierają, ale nie można ich połączyć z wiadomością e-mail
Zachowaj identyfikator e-mail dostawcy z oryginalnej odpowiedzi na wiadomość Resend. Nie traktuj tematów ani adresów odbiorców jako identyfikatorów. Pola te nie są ani unikalne, ani wystarczająco stabilne, aby można było je korelować.
Powtórzone przechwyty nie podlegają weryfikacji znacznika czasu
Można się tego spodziewać podczas odtwarzania starego podpisanego żądania za pomocą normalnego weryfikatora: jego znacznik czasu może wykraczać poza dozwoloną tolerancję. Preferuj ponowną dostawę przez dostawcę, jeśli jest dostępna. W przypadku izolowanych testów logiki biznesowej zweryfikuj raz, zapisz oczyszczone urządzenie zdarzenia i osobno przetestuj program rozsyłający. przewodnik po powtórce wyjaśnia tę granicę.
Bezpieczeństwo i prywatność podczas testowania zdarzeń e-mail
- Nigdy nie wystawiaj
RESEND_API_KEYlub sekret podpisywania punktu końcowego w źródle, pakietach przeglądarki, zrzutach ekranu lub dziennikach żądań. - Sprawdź przed analizą lub utrwaleniem zdarzenia.
- Redaguj odbiorców, tematy, nagłówki i metadane wiadomości ze wspólnych przechwyceń tunelu.
- Zastosuj limity przechowywania do nieprzetworzonych ładunków webhooka; przechowuj tylko to, czego wymaga wsparcie i zgodność.
- Użyj oddzielnego tajnego lokalnego punktu końcowego z produkcji i obróć go po usunięciu testowego punktu końcowego.
Ostateczny projekt powinien działać identycznie po wdrożeniu: publiczny punkt końcowy HTTPS, weryfikacja surowej treści, trwała idempotencja, szybkie potwierdzenie i asynchroniczna obsługa stanu. Szczegóły surowego korpusu specyficzne dla App Router można znaleźć w Przewodnik po lokalnym hoście Next.js webhooka.
