邮件事件
每个收件人在投递过程中推进时,我们都会发出事件。向三个地址发送一封邮件会产生三条独立的事件流,通过 email_id 和 recipient_id 关联。本页定义邮件事件类型。签名、重试、排序和重放请参阅 Webhooks。
每个收件人从 email.accepted 开始,然后是 email.processed。广播收件人本身就是一条独立消息,因此也有自己的 email.accepted,但仅出现在事件 API 和邮件日志中,而非通过 webhook 投递。此后,消息可能被接收服务器接受(email.delivered)、被延迟并重试(email.deferred,最终变为已投递或已退信)、被接收服务器拒绝(email.bounced),或根本未尝试投递(email.rejected)。投递完成后,事件流还可能产生 email.out_of_band_bounce、email.complained、email.opened、email.clicked、email.unsubscribed 和 email.list_unsubscribed。
每个收件人最终处于唯一的终态:delivered、bounced、complained 或 rejected,作为 GET /v1/email/messages/{message_id}/recipients 返回的每收件人 status。互动事件不会改变终态:已打开邮件的收件人仍然是 delivered。延迟退信报告会改变终态,因为接收服务器撤回了先前的接受,收件人从 delivered 变为 bounced。消息整体有自己的汇总状态和各状态计数,见 GET /v1/email/messages/{message_id}。
事件信封
事件以所有 webhook 通用的三字段信封到达:type、timestamp(事件发生时间,RFC 3339 格式)和一个特定于类型的 data 对象。
代码示例
{
"type": "email.delivered",
"timestamp": "2026-07-23T14:51:47.107Z",
"data": {
"email_id": "em_01ky7qc398fmxraqtxn604zeq9",
"recipient_id": "er_01ky7qc398fmwsc96nt6anrbqs",
"workspace_id": "ws_01ky7m21hjffh9s8kq76gyb1xf",
"recipient": "delivered@messagebird.dev",
"recipient_role": "to",
"tags": [{ "name": "category", "value": "welcome" }],
"metadata": { "order_id": "ord_123" },
"broadcast_id": null
}
}每个出站事件都包含 email_id、recipient_id、workspace_id、recipient 地址及其信封 recipient_role(to、cc 或 bcc)。它还会回传发送请求中的 tags 和 metadata,以便你将事件与自己的记录关联。每个可选值在发送时未提供的情况下为 null,broadcast_id 也不例外:它表示该发送所属的广播,便于你将广播的事件分组而无需逐一查找每次发送,在没有广播的发送中它为 null。有一种情况会对实际存在广播的发送报告 null:在我们添加该字段之前发出的邮件中的退订链接不包含广播信息,因此通过此类链接的退出会在 email.unsubscribed 和 email.list_unsubscribed 上报告 null,无论该邮件是否由广播发出。请将这两个事件上的 null 视为不确定值,否则你会低估广播的退出数量。broadcast_id 仅通过 webhook 到达:下方的事件 API 返回的每个事件不包含此字段。事件类型会添加生命周期、互动、屏蔽和入站部分中描述的字段。
同样的事件也可以事后从 GET /v1/email/messages/{message_id}/events 查询,其中每个事件还额外拥有 id(ev_ 前缀)和 occurred_at。你可以用它来回填、重放或与你的端点收到的数据进行核对。部分字段仅通过该 API 而非 webhook 到达;相关事件描述会逐一说明。
生命周期事件
email.accepted
我们已接受发送并开始准备投递。每个请求的收件人触发一次,是该事件流中的第一个事件。广播收件人也会收到此事件,因为每个收件人都是独立消息,但它只被记录而不投递:请从事件 API 或邮件日志中读取,而非从你的 webhook 端点。载荷:仅包含身份基础字段。
email.processed
消息已构建并排入队列,准备投递到收件人的邮件服务器。载荷:webhook 上仅包含身份基础字段;事件 API 额外提供 mailbox_provider 和 mailbox_provider_region,即接收邮件系统的分类(例如 gmail、NA),在能确定时存在,否则为 null。将此事件的 timestamp 与 email.accepted 的时间戳比较,即可得到单次发送的处理时间。广播没有这样的时间间隔:其接受和处理使用同一个调度时刻,因此两个时间戳相同而不构成处理区间,且接受事件仅通过事件 API 获取,显示为 occurred_at。
email.delivered
接收邮件服务器已接受消息并承担投递责任。此事件并不代表邮件已进入收件箱或已被阅读。Inbox Insights 提供采样的收件箱到达率估算;打开和点击事件记录跟踪请求。载荷:webhook 上仅包含身份基础字段;事件 API 额外提供 sending_ip(消息的发送地址,当可投递性问题追踪到某个 IP 时尤为重要),以及 mailbox_provider 和 mailbox_provider_region。
email.deferred
临时失败:接收服务器要求我们稍后重试(邮箱已满、灰名单、请求速率限制)。我们会自动重试,收件人最终变为 email.delivered 或 email.bounced,因此此事件是信息性的而非终态,收件人可能先被延迟多次。载荷:webhook 上包含 bounce_type、bounce_class、defer_reason(服务器给出的原因)和 sending_ip;事件 API 额外提供 mailbox_provider 和 mailbox_provider_region。
失败事件
email.bounced
SMTP 时的永久失败:接收服务器拒绝了消息,收件人的终态变为 bounced。载荷:bounce_type(见分类表)、bounce_class、bounce_code(SMTP 回复码,例如 550)、bounce_description(服务器给出的原因),以及 webhook 上的 sending_ip;事件 API 额外提供 mailbox_provider 和 mailbox_provider_region。硬退信会屏蔽该地址。
email.out_of_band_bounce
延迟退信:接收服务器在 SMTP 时接受了消息,随后发送了退信报告。其分类方式与 email.bounced 相同(webhook 上包含 bounce_type、bounce_class、bounce_code、bounce_description、sending_ip;事件 API 提供 mailbox_provider 和 mailbox_provider_region)。当报告分类为退信(表中的任一类别)时,说明服务器撤回了先前的接受,收件人从 delivered 变为 bounced。类别不在表中的报告(如自动回复)仅记录在时间线上,不改变状态。硬延迟退信同样会屏蔽该地址。
email.rejected
收件人从未到达远程邮件服务器,因此未尝试投递。这就是拒绝与退信的区别:退信是接收服务器拒绝,而拒绝是消息根本没有送出。载荷:rejection_reason,也出现在收件人记录上,值为以下之一:
| rejection_reason | 含义 |
|---|---|
| recipient_suppressed | 收件人在工作区级别被阻止,由屏蔽列表或已声明的偏好触发,因此未尝试投递 |
| transmission_failed | 消息无法传输以进行投递 |
| generation_failure | 消息无法构建以进行投递,属于模板或内容问题 |
| policy_rejection | 发送策略拒绝了消息 |
| domain_unverified | 发送域名未验证 |
| quota_exceeded | 组织的发送配额已用尽 |
| recipient_not_allowed | 该收件人不被允许用于此次发送;使用共享入门域名发送时只能到达工作区中已验证的成员 |
当接收邮件系统在拒绝前可以被分类时,事件 API 还会额外提供 mailbox_provider 和 mailbox_provider_region。
email.complained
收件人将消息标记为垃圾邮件,邮箱提供商通过反馈环路将此信息回报。投诉在投递后到达,并将终态设为 complained。载荷:feedback_type(提供商发送的报告类型,例如 abuse 或 fraud),当提供商未说明时为 null,以及来自事件 API 的 mailbox_provider 和 mailbox_provider_region。投诉会屏蔽该地址的营销邮件。请保持低投诉率:提供商会限制积累大量投诉的发送者。
互动事件
email.opened
消息正文中的跟踪像素被加载。载荷:已知时包含 ip_address 和 user_agent;事件 API 额外提供 is_prefetched、country(ISO 3166-1 alpha-2,由客户端 IP 推导)、mailbox_provider 和 mailbox_provider_region。在计入打开数之前请检查 is_prefetched。 当收件箱隐私功能自动获取了像素而非真人打开邮件时,它为 true,计入这些会虚增你的打开率。打开和点击跟踪介绍了检测机制。
email.clicked
收件人点击了一个被跟踪的链接。载荷:url(被点击的链接)、ip_address,以及已知时的 user_agent;事件 API 额外提供 country、mailbox_provider 和 mailbox_provider_region。点击通常是比打开更强的互动信号,因为隐私代理可能自动加载跟踪像素。
email.unsubscribed
收件人使用了消息正文中的退订链接。载荷:webhook 上仅包含身份基础字段;事件 API 额外提供 mailbox_provider 和 mailbox_provider_region。记录退出偏好以阻止营销邮件。退订链接介绍了如何将链接添加到邮件中。
email.list_unsubscribed
收件人使用了邮箱提供商在其自身 UI 中呈现的一键退订按钮,该按钮由消息的 List-Unsubscribe 请求头驱动。载荷:webhook 上仅包含身份基础字段(事件 API 额外提供 mailbox_provider 和 mailbox_provider_region);机制即事件类型本身,这也是它与 email.unsubscribed 分开的原因。同样记录退出偏好以阻止营销邮件。
消息级事件
email.scheduled
我们接受了一个 scheduled_at 设在未来的发送。载荷:消息级基础字段加 scheduled_at。到达该时间后,每收件人的生命周期从 email.accepted 开始。
email.canceled
定时消息在发出前被取消,因此不会产生任何收件人生命周期事件。载荷:仅包含消息级基础字段。
入站和邮箱事件
email.received 涵盖入站邮件。当我们收到并解析入站消息时触发。其载荷包含 inbound_message_id、地址信息、主题和认证判定。设置、载荷和回取 API 请参阅接收邮件。邮箱在此基础上拥有自己的 email_mailbox.* 系列事件,详见邮箱指南。
退信分类
bounce_class 是包含在 email.bounced、email.out_of_band_bounce 和 email.deferred 上的数值退信分类。它汇总为粗粒度的 bounce_type 并保留细粒度代码,因此即使两者都报告为 soft,你仍能区分邮箱已满和路由故障:
| bounce_class | bounce_type | 含义 |
|---|---|---|
| 1 | undetermined | 接收服务器的响应含义不明确 |
| 10, 30 | hard | 永久失败:地址无效或域名不存在 |
| 20 to 24, 40, 70, 100 | soft | 临时失败:邮箱已满、服务器暂时不可用、DNS 或路由问题 |
| 25 | admin | 管理性拒绝:中继被拒绝、域名被列入黑名单 |
| 50 to 54 | block | 接收服务器拒绝了发送 IP |
不在此列表中的类别映射为 undetermined。只有 hard 退信会屏蔽该地址;soft、block、admin 和 undetermined 不会,因为该地址可能仍然可达。
自动屏蔽
两种事件会自动将收件人添加到工作区屏蔽列表,它们阻止的邮件类型不同:
| 事件 | 屏蔽 reason | 阻止的内容 |
|---|---|---|
| email.bounced 或 email.out_of_band_bounce(bounce_type: "hard") | hard_bounce | 所有邮件,包括事务性邮件 |
| email.complained | complaint | 营销邮件;事务性邮件仍会发送 |
硬退信阻止所有邮件,因为地址本身已不存在。投诉仅阻止营销邮件,因为举报你新闻邮件为垃圾邮件的人仍然需要收到密码重置邮件。
email.unsubscribed 和 email.list_unsubscribed 与投诉一样仅阻止营销邮件,但通过不同的记录实现:它们不添加屏蔽条目,而是将收件人的退出记录为已声明的偏好。退出的效果完整介绍了该记录。
每次添加都会触发一个 email_suppression.created 事件,其中包含 suppression_id、被屏蔽的 email、reason 和 workspace_id。完整的记录结构以及如何手动管理条目请参阅屏蔽指南。
后续向被屏蔽地址的发送会被立即拒绝,原因为 email.rejected(rejection_reason: "recipient_suppressed"),且不会计入你的可投递性指标。
后续步骤
- Webhooks 与事件:端点设置、签名验证、重试和重放
- 屏蔽列表:屏蔽列表的工作原理及管理方式
- 退订链接:配置 email.unsubscribed 和 email.list_unsubscribed 背后的路径
- 测试与沙盒:沙盒发送会通过正常路径产生真实事件,是测试处理程序最低成本的方式
- 正确使用 Webhooks:可靠的投递事件:一段创建 webhook 并观察事件到达的视频