Усі статті
Конверти подій доставки, відмови й скарги на лист проходять через підписаний тунель до маршруту Next.js на localhost.
ResendNext.jsemail webhookslocalhost

Локальне тестування вебхуків Resend у Next.js

Щоб перевірити вебхуки Resend локально в Next.js, створіть маршрут App Router POST, який зчитує необроблене тіло, перевірте його заголовки Svix вашим секретом підпису Resend і зареєструйте URL-адресу тунелю HTTPS на інформаційній панелі Resend. Надішліть електронний лист через Resend, а потім обробіть реальні email.sent, email.delivered, email.bounced, або email.complained події на локальному хості.

Що вебхук Resend повідомляє вашій програмі

Відповідь API про те, що електронний лист прийнято, не є доказом того, що він досяг одержувача. Доставка відбувається асинхронно. Веб-хуки Resend дозволяють вашій програмі оновлювати стан повідомлень, пригнічувати неправильні адреси, повідомляти про відмову та реагувати на скарги після завершення оригінального запиту на надсилання. офіційна документація вебхука Resend перераховує типи подій і налаштування інформаційної панелі.

Тест локального вебхуку має охоплювати весь кінцевий автомат, а не лише те, чи досягає POST ваш маршрут. Співвіднесіть ідентифікатор електронної пошти кожної події із записом, створеним під час надсилання. Розглядайте стани як переходи: прийнято, надіслано, доставлено, затримано, відмовлено, поскаржено, відкрито або клацнуто, де це можливо. Пізніший дублікат не повинен перезаписувати більш корисний стан або двічі запускати те саме сповіщення.

Створіть маршрут Next.js App Router

Встановіть верифікатор, який підтримується для формату підпису:

npm install svix

Потім створіть маршрут Node-runtime. Resend підписує оригінальне тіло, тому використовуйте request.text() рівно один раз перед розбором.

// 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 });
}

Секрет підпису належить цій кінцевій точці вебхуку та зазвичай починається з префікса постачальника. Скопіюйте його з налаштувань вебхуку Resend у ігнорований файл локального середовища, наприклад .env.local. Це не ключ API Resend, який використовується для надсилання електронної пошти.

Чому важливі три заголовки Svix

  • svix-id унікально ідентифікує доставку та є найкращим ключем ідемпотентності.
  • svix-timestamp прив’язує підпис до часу, дозволяючи верифікатору відхиляти застарілі запити за межами допустимого значення.
  • svix-signature може містити один або кілька версійних підписів, які використовуються для автентифікації тіла.

Не використовуйте цей протокол, розділяючи рядки заголовка, якщо у вас немає вагомої причини. SDK обробляє кодування, численні підписи та перевірку часових позначок. Resend явно рекомендує використовувати секрет підпису та ці заголовки для перевірки. Чим глибше посібник з перевірки підпису пояснює, чому важливі необроблені байти та безпечні перевірки часу.

Виставте Next.js і зареєструйте кінцеву точку

  1. бігти npm run dev і підтвердьте, що програма прослуховує порт 3000.
  2. Почніть npx portpreview 3000 в іншому терміналі.
  3. У Resend створіть вебхук, кінцевою точкою якого є https://YOUR-TUNNEL.portpreview.dev/api/webhooks/resend.
  4. Виберіть лише події електронної пошти, які обробляє ваша програма.
  5. Скопіюйте секрет підпису кінцевої точки в RESEND_WEBHOOK_SECRET і перезапустіть Next.js, щоб він завантажив змінну.
  6. Надішліть повідомлення за допомогою підтвердженого домену та перевірте події, які досягають локального маршруту.

Зберігайте загальнодоступну URL-адресу стабільною протягом сеансу. Якщо вихід тунелю змінюється, відредагуйте кінцеву точку Resend перед повторним тестуванням. Кінцева точка, налаштована зі старою URL-адресою, не може досягти вашого нового процесу, навіть якщо сам локальний хост справний.

Використовуйте диспетчер введених подій

Корисні навантаження Webhook мають надходити в один вузький диспетчер. Перевірте обов’язкові поля та зробіть нерозпізнані типи подій видимими, не розглядаючи їх як збої сервера.

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);
  }
}

Зберігайте типи корисних даних узгодженими з поточною схемою Resend, а не припускайте, що кожна подія має ідентичні дані. Наприклад, деталі відмови та списки одержувачів можуть бути актуальними лише для певних подій. Збережіть тип події, ідентифікатор електронної пошти постачальника, позначку часу події та мінімальну відредаговану корисну інформацію для розслідувань служби підтримки.

Зробіть обробку ідемпотентною перед повторними спробами тестування

Системи Webhook на практиці забезпечують поведінку доставки принаймні один раз. Тайм-аут може виникнути після фіксації бази даних, але до того, як постачальник отримає вашу відповідь 200. Потім постачальник повторює запит, який ви вже застосували. використання svix-id як унікальний ключ доставки та вставте його в ту саму транзакцію, що й зміна стану.

await db.transaction(async (tx) => {
  const inserted = await tx.webhookDelivery.insertOnce({
    provider: 'resend',
    deliveryId,
  });
  if (!inserted) return;
  await applyEmailEvent(tx, event);
});

Не видаляйте дублікати лише за ідентифікатором електронної пошти, оскільки одна електронна пошта законно отримує кілька типів подій. Залежно від моделі даних зберігайте як унікальний ключ рівня доставки, так і правила переходу між станами. Прочитайте шаблони повторних спроб веб-хука та ідемпотентності перед підключенням подій до виставлення рахунків, припинення або сповіщень клієнтів.

Швидко повертайтеся, не втрачаючи події

Перевірка підпису доречна в шляху запиту; повільна робота бізнесу не є. Збережіть або помістіть перевірену подію в чергу, а потім поверніть 2xx. Якщо ви повернетеся до тривалого запису, збій процесу може втратити подію. Якщо ви чекаєте на кілька віддалених API, ваша кінцева точка може перервати час очікування та запросити повторні спроби. Таблиця вхідних повідомлень бази даних часто є найпростішим локальним і робочим дизайном.

Створення корисних тестових подій

Відправлено та доставлено

Надішліть на адресу, якою ви керуєте, із підтвердженого домену. Запишіть ідентифікатор електронної пошти, повернутий API надсилання, і підтвердьте оновлення вхідних подій у тому самому рядку. Час доставки залежить від сервера одержувача, тому не припускайте, що події надходять негайно або в спрощеній послідовності.

Відскоки

Використовуйте задокументовані тестові адреси або функції тестування Resend, а не придумуйте трафік до непов’язаних доменів. Переконайтеся, що постійні збої пригнічують майбутню пошту, а тимчасові умови відповідають вашій політиці повторних спроб. Не пригнічуйте автоматично кожну відкладену подію.

Скарги

Обробка скарг – це як логіка доставки, так і відповідності. Переконайтеся, що повторний вебхук не створює повторних сповіщень і переконайтеся, що відповідного одержувача виключено з подальших кампаній відповідно до вашої політики.

Усунення несправностей вебхуку Resend

Перевірка підпису завжди не вдається

Переконайтеся, що завантажено секрет підпису кінцевої точки, а не ключ API. використання await request.text(), не аналізуйте та не змінюйте рядки JSON, а передавайте всі три заголовки Svix з їхніми точними значеннями. Після зміни перезапустіть сервер розробки .env.local.

Маршрут повертає 404 або 405

Файли маршруту App Router повинні мати назву route.ts під запланованими сегментами URL-адреси та експортуйте POST. Перевірте, чи проміжне програмне забезпечення переписує запит тунелю на мову або сторінку входу. Перевірте загальнодоступну URL-адресу за допомогою curl і перевірте фактичну відповідь.

Resend показує повторні спроби, незважаючи на успішну обробку

Переконайтеся, що кожне успішне розгалуження швидко повертає 2xx. Помилки, які виникли після оновлення бази даних, можуть призвести до 500 і повторної спроби. Зробіть обробку транзакційною та ідемпотентною, а потім перевірте затримку відповіді.

Події надходять, але не можуть бути пов’язані з електронним листом

Зберігайте ідентифікатор електронної пошти постачальника з вихідної відповіді Resend. Не покладайтеся на рядки теми чи адреси одержувачів як ідентифікатори. Ці поля не є ані унікальними, ані достатньо стабільними для кореляції.

Відтворені знімки не проходять перевірку позначки часу

Це очікується під час повторного відтворення старого підписаного запиту через звичайний верифікатор: його позначка часу може бути за межами дозволеного допуску. Надавати перевагу повторній доставці постачальником, якщо це можливо. Для ізольованих тестів бізнес-логіки перевірте один раз, збережіть дезінфікований пристрій події та перевірте диспетчер окремо. керівництво по відтворенню пояснює цю межу.

Безпека та конфіденційність для тестування подій електронної пошти

  • Ніколи не викривайте RESEND_API_KEY або секрет підпису кінцевої точки в джерелі, пакетах браузера, знімках екрана або журналах запитів.
  • Перевірте перед аналізом або збереженням події.
  • Видалення одержувачів, тем, заголовків і метаданих повідомлень зі спільних записів тунелю.
  • Застосуйте обмеження збереження до необроблених корисних навантажень вебхуку; зберігати лише те, що вимагає підтримки та відповідності.
  • Використовуйте окремий локальний секрет кінцевої точки від виробництва та обертайте його, коли тестову кінцеву точку буде видалено.

Остаточний дизайн має працювати однаково після розгортання: загальнодоступна кінцева точка HTTPS, перевірка необробленого тіла, довготривала ідемпотентність, швидке підтвердження та асинхронна обробка стану. Детальну інформацію про необроблений корпус App Router див Next.js посібник з локального хосту webhook.

Поширені запитання

Як тестувати вебхуки Resend локально в Next.js?
Створіть маршрут POST App Router, перевірте необроблене тіло за допомогою заголовків Svix і секрету підпису кінцевої точки, відкрийте порт 3000 через HTTPS і зареєструйте цю загальнодоступну URL-адресу в Resend.
Чи повинен вебхук Resend використовувати request.json() у Next.js?
Не раніше перевірки. Прочитайте await request.text(), щоб підписані байти залишалися незмінними, перевірте за допомогою Svix і використовуйте перевірену подію, яку повертає SDK.
Чи секрет підпису вебхуку Resend збігається з ключем API?
Ні. Ключ API дозволяє надсилати запити. Кожна кінцева точка вебхуку має секрет підпису, який використовується для перевірки вхідних подій; зберігати обидва окремо.
Як запобігти повторюваній обробці вебхука Resend?
Зберігайте Svix-id під унікальним обмеженням і застосовуйте подію в тій самій транзакції. Не видаляйте дублікати лише за ідентифікатором електронної пошти, оскільки одна електронна пошта має кілька дійсних подій.