事件类型
创建 Webhook 时,可以任意组合订阅这些事件类型。
| 事件 | 触发时机 | 将 last_event 设为 |
|---|---|---|
email.scheduled |
带有 scheduled_at 的邮件已被接受。 |
scheduled |
email.sent |
邮件已交给 Amazon SES。 | sent |
email.delivered |
收件人的邮件服务器已接受该邮件。 | delivered |
email.delivery_delayed |
投递被延迟或暂时失败;SES 会持续重试。 | delivery_delayed |
email.bounced |
永久性失败(地址不存在、域名拒收)。 | bounced |
email.complained |
收件人将邮件标记为垃圾邮件。 | complained |
email.opened |
收件人打开了邮件(需要开启打开追踪)。 | opened |
email.clicked |
收件人点击了链接(需要开启点击追踪)。 | clicked |
email.failed |
SpacePost 或 SES 无法发送该邮件(被拒绝、渲染失败、团队或租户被暂停)。 | failed |
email.suppressed |
由于收件人在你的抑制列表中,已跳过该收件人。 | suppressed |
Webhook 请求体与 Resend 的完全一致,因此为 Resend 编写的处理程序可继续使用。
{ "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" } }}type是事件类型;created_at是事件发生的时间。data.email_id、from、to(该邮件的所有收件人,而不仅是受影响的那一个)、subject和created_at(邮件的创建时间)始终存在。data.broadcast_id仅在群发邮件中出现。data.tenant_id仅在邮件是代表某个租户发送时(平台模式)出现。它是我们的租户 id,而不是你的external_id。data.tags仅在邮件带有标签时出现,形式为对象({ "name": "value" })。- 部分事件会在
data中再增加一个以事件命名的对象:bounce、click、open、failed或suppressed。它们列在下方。除此之外不会添加任何内容:没有顶层的bounceType、delayType或reason键。
email.scheduled
Section titled “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
Section titled “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
Section titled “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
Section titled “email.delivery_delayed”在 SES 投递延迟和临时性退信时触发。单纯的延迟没有额外字段。临时性退信还会携带 bounce 对象(见下文,其中 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
Section titled “email.bounced”只有永久性退信才会触发该事件,并且会将该地址加入你的抑制列表。data.bounce 包含 type(Permanent)、subType 以及接收服务器返回的 message。
{ "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." } }}退信子类型包括 General、NoEmail、Suppressed、OnAccountSuppressionList、MailboxFull、MessageTooLarge、ContentRejected 和 AttachmentRejected。
email.complained
Section titled “email.complained”没有额外字段。该地址会被加入你的抑制列表。
{ "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
Section titled “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
Section titled “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
Section titled “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
Section titled “email.suppressed”suppressed.reason 为 suppression_list(收件人在你的抑制列表中)或 recipient_domain_does_not_accept_mail(收件人的域名没有邮件服务器)。
{ "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" } }}在 TypeScript 中为事件添加类型
Section titled “在 TypeScript 中为事件添加类型”Node SDK 导出了以 type 为判别字段的联合类型 WebhookPayload,spacepost.webhooks.verify() 返回的就是它,因此根据 event.type 进行类型收窄,即可得到正确的 data 字段:
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 }}