Tipos de evento
Suscríbete a cualquier combinación de estos tipos de evento al crear un webhook.
| Evento | Se dispara cuando | Establece last_event en |
|---|---|---|
email.scheduled |
Se aceptó un correo con scheduled_at. |
scheduled |
email.sent |
El correo se entregó a Amazon SES. | sent |
email.delivered |
El servidor de correo del destinatario aceptó el correo. | delivered |
email.delivery_delayed |
La entrega se retrasa o falla temporalmente; SES sigue reintentando. | delivery_delayed |
email.bounced |
Fallo permanente (la dirección no existe, el dominio rechaza). | bounced |
email.complained |
El destinatario marcó el correo como spam. | complained |
email.opened |
El destinatario abrió el correo (requiere el seguimiento de aperturas). | opened |
email.clicked |
El destinatario hizo clic en un enlace (requiere el seguimiento de clics). | clicked |
email.failed |
SpacePost o SES no pudieron enviar el correo (rechazado, fallo de renderizado, equipo o tenant pausado). | failed |
email.suppressed |
Se omitió un destinatario porque está en tu lista de supresión. | suppressed |
Estructura del payload
Sección titulada «Estructura del payload»Los cuerpos de los webhooks son exactamente los de Resend, así que los manejadores escritos para Resend siguen funcionando.
{ "type": "email.delivered", "created_at": "2026-10-23T14:02:10.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "broadcast_id": "0192e1c4-0000-7000-8000-0000000000bb", "tenant_id": "0192e1c4-5555-7000-8000-0000000000cc", "tags": { "category": "welcome" } }}typees el evento;created_ates cuándo ocurrió el evento.data.email_id,from,to(todos los destinatarios del correo, no solo el afectado),subjectycreated_at(cuándo se creó el correo) están siempre presentes.data.broadcast_idsolo está presente en los correos de campañas.data.tenant_idsolo está presente cuando el correo se envió para un tenant (modo plataforma). Es nuestro id de tenant, no tuexternal_id.data.tagssolo está presente si el correo tenía etiquetas, como un objeto ({ "name": "value" }).- Algunos eventos añaden un objeto más a
data, con el nombre del evento:bounce,click,open,failedosuppressed. Se listan a continuación. No se añade nada más: no hay claves de primer nivelbounceType,delayTypenireason.
Ejemplos
Sección titulada «Ejemplos»email.scheduled
Sección titulada «email.scheduled»{ "type": "email.scheduled", "created_at": "2026-10-23T14:02:09.700Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Your trial ends tomorrow", "created_at": "2026-10-23T14:02:09.512Z" }}email.sent
Sección titulada «email.sent»{ "type": "email.sent", "created_at": "2026-10-23T14:02:10.100Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z" }}email.delivered
Sección titulada «email.delivered»{ "type": "email.delivered", "created_at": "2026-10-23T14:02:11.350Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z" }}email.delivery_delayed
Sección titulada «email.delivery_delayed»Se dispara por retrasos de entrega de SES y por rebotes transitorios. Un retraso simple no tiene campos adicionales. Un rebote transitorio también incluye el objeto bounce (ver más abajo, con type: "Transient").
{ "type": "email.delivery_delayed", "created_at": "2026-10-23T14:07:00.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z" }}email.bounced
Sección titulada «email.bounced»Solo los rebotes permanentes llegan a este evento, y añaden la dirección a tu lista de supresión. data.bounce incluye type (Permanent), subType y el message del servidor receptor.
{ "type": "email.bounced", "created_at": "2026-10-23T14:02:12.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["nobody@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "bounce": { "type": "Permanent", "subType": "General", "message": "smtp; 550 5.1.1 The email account that you tried to reach does not exist." } }}Los subtipos de rebote incluyen General, NoEmail, Suppressed, OnAccountSuppressionList, MailboxFull, MessageTooLarge, ContentRejected y AttachmentRejected.
email.complained
Sección titulada «email.complained»Sin campos adicionales. La dirección se añade a tu lista de supresión.
{ "type": "email.complained", "created_at": "2026-10-23T15:30:00.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Our monthly newsletter", "created_at": "2026-10-23T14:02:09.512Z" }}email.opened
Sección titulada «email.opened»{ "type": "email.opened", "created_at": "2026-10-23T14:05:44.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "open": { "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (Macintosh)" } }}email.clicked
Sección titulada «email.clicked»{ "type": "email.clicked", "created_at": "2026-10-23T14:06:10.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "click": { "link": "https://acme.com/get-started", "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (Macintosh)", "timestamp": "2026-10-23T14:06:10.000Z" } }}email.failed
Sección titulada «email.failed»{ "type": "email.failed", "created_at": "2026-10-23T14:02:10.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "failed": { "reason": "Attachment https://files.acme.com/report.pdf returned HTTP 404" } }}email.suppressed
Sección titulada «email.suppressed»suppressed.reason es suppression_list (el destinatario está en tu lista de supresión) o recipient_domain_does_not_accept_mail (el dominio del destinatario no tiene servidor de correo).
{ "type": "email.suppressed", "created_at": "2026-10-23T14:02:10.000Z", "data": { "email_id": "0192e1c4-7a3b-7c1e-9d2f-3b5a8e6f4c10", "from": "Acme <hello@acme.com>", "to": ["user@example.com", "other@example.com"], "subject": "Welcome to Acme", "created_at": "2026-10-23T14:02:09.512Z", "suppressed": { "recipients": ["other@example.com"], "reason": "suppression_list" } }}Tipar los eventos en TypeScript
Sección titulada «Tipar los eventos en TypeScript»El SDK de Node exporta una unión WebhookPayload discriminada por type, y spacepost.webhooks.verify() la devuelve, así que al acotar por event.type obtienes los campos de data correctos:
import type { WebhookPayload } from "@spacepost/node";
function handle(event: WebhookPayload) { switch (event.type) { case "email.bounced": return event.data.bounce?.subType; case "email.clicked": return event.data.click?.link; case "email.suppressed": return event.data.suppressed.recipients; case "email.failed": return event.data.failed.reason; default: return event.data.tenant_id; // present when the email was sent for a tenant }}