Todos los artículos
Sobres de eventos de entrega, rebote y queja de correo que fluyen por un túnel firmado hasta una ruta de Next.js en localhost.
ResendNext.jsemail webhookslocalhost

Probar webhooks de Resend en local con Next.js

Para probar los webhooks Resend localmente en Next.js, cree una ruta POST App Router que lea el cuerpo sin formato, verifique sus encabezados Svix con su secreto de firma Resend y registre una URL de túnel HTTPS en el panel de control Resend. Envíe un correo electrónico a través de Resend y luego maneje datos reales. email.sent, email.delivered, email.bounced, o email.complained eventos en localhost.

Qué le dice un webhook Resend a su aplicación

Una respuesta de API que diga que se aceptó un correo electrónico no es prueba de que haya llegado al destinatario. La entrega se realiza de forma asincrónica. Los webhooks Resend permiten que su aplicación actualice el estado del mensaje, suprima direcciones incorrectas, informe de rebotes y reaccione a las quejas una vez finalizada la solicitud de envío original. el documentación oficial del webhook Resend enumera los tipos de eventos y la configuración del panel.

Una prueba de webhook local debe cubrir toda la máquina de estado, no solo si un POST llega a su ruta. Correlacione el ID de correo electrónico de cada evento con el registro creado al enviar. Trate los estados como transiciones: aceptado, enviado, entregado, retrasado, devuelto, reclamado, abierto o hecho clic cuando corresponda. Un duplicado posterior no debe sobrescribir un estado más útil ni activar la misma alerta dos veces.

Crear la ruta Next.js App Router

Instale el verificador mantenido para el formato de firma:

npm install svix

Luego cree una ruta de tiempo de ejecución de nodo. Resend firma el cuerpo original, así que use request.text() exactamente una vez antes del análisis.

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

El secreto de firma pertenece a este punto final de webhook y normalmente comienza con un prefijo específico del proveedor. Cópielo desde la configuración del webhook Resend en un archivo de entorno local ignorado, como .env.local. No es la clave API Resend utilizada para enviar correo electrónico.

Por qué son importantes los tres encabezados Svix

  • svix-id identifica de forma única una entrega y es la mejor clave de idempotencia.
  • svix-timestamp vincula la firma a una hora, lo que permite al verificador rechazar solicitudes obsoletas fuera de su tolerancia.
  • svix-signature puede contener una o más firmas versionadas utilizadas para autenticar el cuerpo.

No implemente este protocolo dividiendo cadenas de encabezado a menos que tenga una razón convincente. El SDK maneja codificación, firmas múltiples y verificaciones de marca de tiempo. Resend recomienda explícitamente utilizar el secreto de firma y estos encabezados para la verificación. Cuanto más profundo guía de verificación de firma explica por qué son importantes los bytes sin formato y las comprobaciones de tiempo seguro.

Exponga Next.js y registre el punto final

  1. correr npm run dev y confirme que la aplicación escucha en el puerto 3000.
  2. Empezar npx portpreview 3000 en otra terminal.
  3. En Resend, cree un webhook cuyo punto final sea https://YOUR-TUNNEL.portpreview.dev/api/webhooks/resend.
  4. Seleccione solo los eventos de correo electrónico que procesa su aplicación.
  5. Copie el secreto de firma del punto final en RESEND_WEBHOOK_SECRET y reinicie Next.js para que cargue la variable.
  6. Envía un mensaje utilizando un dominio verificado e inspecciona los eventos que llegan a la ruta local.

Mantenga estable la URL pública para la sesión. Si el origen del túnel cambia, edite el punto final Resend antes de realizar la prueba nuevamente. Un punto final configurado con una URL antigua no puede llegar a su nuevo proceso, incluso si el propio localhost está en buen estado.

Utilice un despachador de eventos escrito

Las cargas útiles del webhook deben ingresar a un despachador estrecho. Valide los campos obligatorios y haga observables los tipos de eventos no reconocidos sin tratarlos como fallas del servidor.

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

Mantenga los tipos de carga útiles alineados con el esquema Resend actual en lugar de asumir que cada evento tiene datos idénticos. Por ejemplo, los detalles de rebote y las listas de destinatarios pueden ser relevantes solo en ciertos eventos. Guarde el tipo de evento, el ID de correo electrónico del proveedor, la marca de tiempo del evento y una carga útil redactada mínima para investigaciones de soporte.

Haga que el manejo sea idempotente antes de reintentar la prueba

Los sistemas Webhook proporcionan un comportamiento de entrega al menos una vez en la práctica. Puede ocurrir un tiempo de espera después de la confirmación de su base de datos pero antes de que el proveedor reciba su respuesta 200. Luego, el proveedor vuelve a intentar una solicitud que ya presentó. uso svix-id como clave de entrega única e insértela en la misma transacción que el cambio de estado.

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

No realice la deduplicación únicamente mediante ID de correo electrónico porque un correo electrónico recibe legítimamente varios tipos de eventos. Dependiendo de su modelo de datos, mantenga una clave única de nivel de entrega y reglas de transición de estado. leer Reintento de webhook y patrones de idempotencia. antes de conectar eventos con facturación, supresión o notificaciones al cliente.

Regresa rápidamente sin perder el evento.

La verificación de firma es apropiada en la ruta de solicitud; el trabajo empresarial lento no lo es. Persista o ponga en cola el evento verificado y luego devuelva 2xx. Si regresa antes de cualquier escritura duradera, una falla del proceso puede hacer perder el evento. Si espera varias API remotas, su punto final puede agotar el tiempo de espera e invitar a reintentar. Una tabla de bandeja de entrada de base de datos suele ser el diseño local y de producción más simple.

Generar eventos de prueba útiles

Enviado y entregado

Envíe a una dirección que usted controle desde un dominio verificado. Registre el ID de correo electrónico devuelto por la API de envío y confirme que los eventos entrantes actualicen la misma fila. El tiempo de entrega varía según el servidor del destinatario, por lo que no asuma que los eventos llegan inmediatamente o en una secuencia simplista.

Rebota

Utilice las direcciones de prueba documentadas o las funciones de prueba de Resend en lugar de inventar tráfico a dominios no relacionados. Verifique que las fallas permanentes supriman el correo futuro mientras que las condiciones temporales sigan su política de reintento. No suprima automáticamente cada evento retrasado.

Quejas

El manejo de quejas es tanto una lógica de entregabilidad como de cumplimiento. Asegúrese de que un webhook repetido no cree alertas repetidas y asegúrese de que el destinatario afectado quede excluido de campañas posteriores de acuerdo con su política.

Solucionar problemas de fallas del webhook Resend

La verificación de firma siempre falla

Confirme que esté cargado el secreto de firma del punto final, no la clave API. uso await request.text(), no analice ni vuelva a encadenar JSON y pase los tres encabezados Svix con sus valores exactos. Reinicie el servidor de desarrollo después de cambiar .env.local.

La ruta regresa 404 o 405

Los archivos de ruta App Router deben tener un nombre route.ts debajo de los segmentos de URL deseados y exporte POST. Compruebe si el middleware reescribe la solicitud del túnel en una configuración regional o página de inicio de sesión. Pruebe la URL pública con curl e inspeccione la respuesta real.

Resend muestra reintentos a pesar del procesamiento exitoso

Verifique que cada rama exitosa devuelva 2xx rápidamente. Los errores generados después de una actualización de la base de datos pueden generar un reintento 500 y un reintento duplicado. Haga que el procesamiento sea transaccional e idempotente y luego inspeccione la latencia de respuesta.

Los eventos llegan pero no se pueden vincular a un correo electrónico

Conserve el ID de correo electrónico del proveedor de la respuesta de envío original de Resend. No confíe en las líneas de asunto o las direcciones de los destinatarios como identificadores. Esos campos no son lo suficientemente únicos ni estables para la correlación.

Las capturas reproducidas fallan en la verificación de la marca de tiempo

Esto es lo que se espera al reproducir una solicitud firmada antigua a través del verificador normal: su marca de tiempo puede estar fuera de la tolerancia permitida. Prefiere la reenvío del proveedor cuando esté disponible. Para pruebas aisladas de lógica empresarial, verifique una vez, guarde un evento desinfectado y pruebe el despachador por separado. el guía de repetición explica este límite.

Seguridad y privacidad para pruebas de eventos de correo electrónico

  • Nunca exponer RESEND_API_KEY o el secreto de firma del punto final en el código fuente, paquetes de navegador, capturas de pantalla o registros de solicitudes.
  • Verifique antes de analizar o persistir el evento.
  • Redacte destinatarios, asuntos, encabezados y metadatos de mensajes de capturas de túneles compartidos.
  • Aplicar límites de retención a cargas útiles de webhooks sin procesar; almacene solo lo que requiere soporte y cumplimiento.
  • Utilice un secreto de punto final local independiente de la producción y rótelo cuando se elimine el punto final de prueba.

El diseño final debería funcionar de manera idéntica después de la implementación: punto final HTTPS público, verificación de cuerpo sin formato, idempotencia duradera, reconocimiento rápido y manejo de estado asincrónico. Para obtener detalles del cuerpo sin procesar específicos de App Router, consulte la Guía de host local del webhook Next.js.

Preguntas frecuentes

¿Cómo pruebo los webhooks Resend localmente en Next.js?
Cree una ruta POST App Router, verifique el cuerpo sin formato con los encabezados Svix y el secreto de firma del punto final, exponga el puerto 3000 a través de HTTPS y registre esa URL pública en Resend.
¿Debería un webhook Resend utilizar request.json() en Next.js?
No antes de la verificación. Lea await request.text() para que los bytes firmados permanezcan sin cambios, verifique con Svix y use el evento verificado devuelto por el SDK.
¿El secreto de firma del webhook Resend es el mismo que la clave API?
No. La clave API autoriza el envío de solicitudes. Cada punto final de webhook tiene un secreto de firma que se utiliza para verificar los eventos entrantes; guarde ambos por separado.
¿Cómo evito el procesamiento de webhooks duplicados Resend?
Almacene Svix-id bajo una restricción única y aplique el evento en la misma transacción. No realice la deduplicación únicamente por ID de correo electrónico porque un correo electrónico tiene varios eventos válidos.