Sign inGet Started

定时发送

设置 scheduled_at 将消息保留到指定时间。到达该时间后,消息进入正常的投递生命周期,产生与立即发送相同的事件。你的应用无需运行自己的调度器。

安排发送

在常规的 POST /v1/email/messages 发送中添加一个 scheduled_at 时间戳。载荷的其他部分无需更改。

const msg = await bird.email.send({
  from: "news@yourdomain.com",
  to: ["delivered@messagebird.dev"],
  subject: "Your weekly digest",
  html: '<p>Here is what happened this week...</p><p><a href="{{ bird.unsubscribe_url }}">Unsubscribe</a></p>',
  category: "marketing",
  scheduled_at: "2027-01-15T09:00:00Z",
});
console.log(msg.id, msg.status); // "em_…", "accepted"

调用立即返回 202 Accepted,包含 em_ 前缀的消息 ID 和 status: accepted。它与立即发送返回的消息对象相同,另外还包含以 UTC 回显的 scheduled_at,你无需再次读取即可确认发送时间。接受是同步的,投递是延迟的。请求中省略 scheduled_at,消息会立即发出,响应中不会包含 scheduled_at 键。

读取结果以异步方式更新。对于刚安排发送的消息,获取消息最初可能返回 404,消息列表或控制台中也可能尚未显示该消息。较大的内容或附件在存储期间可能延长等待时间。请保留 202 响应中的 ID 和 scheduled_at,并使用该 ID 采用退避策略重试读取。在消息显示之前,你也可以使用该 ID 取消发送。

如果消息显示时仍在等待发送,读取结果会包含 status: scheduled 及其 scheduled_at:

代码示例
{
  "id": "em_01ky7q24hafjgvzfg02v3m177p",
  "status": "scheduled",
  "scheduled_at": "2027-01-15T09:00:00Z",
  "category": "marketing"
}

时间到达后,我们释放消息,其状态按常规顺序推进(accepted,然后 processed,然后 delivered,依此类推)。scheduled_at 在之后仍然保留,因此你始终可以查看消息被安排在什么时间发送。

消息可能在读取结果更新之前就开始发送,因此你最先看到的可能是后续状态。没有一个固定的等待时长能保证之后读取时一定显示该消息。

每次定时发送会消耗你所在组织当前计费周期内定时邮件配额的一个单位。超出配额的请求将被拒绝,返回 422(E10003)。

安排内联内容或模板的发送时间

将 scheduled_at 与内联内容或已保存的模板一起使用,其构造方式与即时模板发送相同。我们在接受请求时会固定已发布的版本、选定的语言和参数值,并在计划时间发送该版本。发布较新版本不会改变这一选择。如果在计划时间之前删除模板,消息会被拒绝,不会发送。

marketing 类别的消息会在正文末尾附加一个小页脚形式的退订链接。如需自行放置该链接,请在您提供的每种正文中加入 {{ bird.unsubscribe_url }};对于模板发送,请加入模板的正文中。

批量发送中的条目同样接受 scheduled_at,因此一个批次可以混合定时和即时消息。每个定时条目各自消耗一个配额单位,如果任何条目的时间超出范围,整个批次将被拒绝。每个定时条目在批量响应中携带自己的 scheduled_at,立即发送的条目没有 scheduled_at 键;批量参考文档在一个响应中展示了两种情况。

即时发送的载荷仍可能过大而无法定时发送。如果正文、收件人列表或元数据超出定时发送限制,API 返回 422。请缩减这些字段或立即发送消息。

选择发送时间

scheduled_at 是一个绝对的 RFC 3339 时间戳。它受两条规则约束:

  • 必须在未来 30 秒到 30 天之间。 不足 30 秒或超过 30 天的将被拒绝,返回 422。下限防止定时发送与即时发送竞争。30 天是我们保留消息的最远期限。
  • 提供一个精确时刻。 包含 UTC Z(2027-01-15T09:00:00Z)或显式偏移量(2026-07-30T09:00:00-04:00,与 13:00:00Z 是同一时刻)。我们将该时刻与当前时间进行比较,不会解析裸本地时间或套用收件人的时区。如需在每位收件人的本地时间上午 9 点发送,请自行计算这些时刻,并按时区分别安排发送。

不接受 "in 2 hours" 等相对表达式。请发送解析后的时间戳。

列出定时消息

按状态筛选消息列表,可以查看已经显示且仍在等待发送的消息。刚被接受的定时发送可能在内容上传或读取结果更新期间尚未显示:

for await (const message of bird.email.list({ status: "scheduled" })) {
  console.log(message.id, message.scheduled_at);
}

status=canceled 列出你在发送前取消的消息。定时消息触发后进入投递管道,以投递状态显示,与其他发送无异。控制面板的邮件日志提供相同的 Scheduled 和 Canceled 过滤器。

取消定时发送

在消息开始发送前,随时使用 POST /v1/email/messages/{message_id}/cancel 取消:

await bird.email.cancel("em_abc123");

成功取消后返回 204 No Content。消息状态变为 canceled,不会发送,并触发 email.canceled webhook。需要注意四点:

  • 只能取消仍处于定时状态的消息。 已开始发送、已发送或已取消的消息将返回 409:

    代码示例
    {
      "error": {
        "type": "conflict_error",
        "code": "E10005",
        "name": "EmailNotCancelable",
        "message": "This message cannot be canceled.",
        "remediation": "Only a scheduled message that has not started sending can be canceled. Once sending begins there is no way to pull it back."
      }
    }

    在发送时间到达时,取消请求也可能在与发送本身的竞争中失败,因同样的原因返回 409。

  • 内容仍在上传时也可以取消发送。 带有附件或较大正文的定时邮件,在返回 202 后可能仍在存储内容。成功取消后,即使上传随后完成,邮件也会保持取消状态。

  • 取消不会退还定时邮件配额。 安排时消耗的单位仍然保持消耗状态,这就是为什么先安排再取消的循环无法绕过配额限制。你的常规发送配额不受影响,因为它仅在消息实际发送时才扣减。

  • 取消可安全重试,与其他写操作一样,使用 Idempotency-Key。

要将定时发送改到其他时间,请取消它并使用新的 scheduled_at 提交新发送。你会获得一个新的 em_ ID。

发送时会发生什么

定时发送只改变消息的释放时间,其构造和管控保持不变。附件、分类、标签和元数据的行为与即时发送完全相同,并以相同方式在 webhook 事件中回显。五项检查分布在两个时刻:

  • 载荷和域名验证在提交时执行。 格式错误的定时发送在 API 调用时即以 422 失败,你可以立即发现问题,而非等到上午 9 点。
  • 发送时会重新检查发件人域名。 如果你的 from 域名在定时发送时间到达时不再处于已验证状态,消息将不会发送。其收件人将以 rejected 返回并附带原因,而不是从未验证域名接收邮件。请在整个等待窗口内保持域名已验证。
  • 发送配额在发送时扣减。 常规发送配额在消息触发时消耗。安排定时发送不会影响配额。如果发送时配额已耗尽,收件人将被拒绝。
  • 退订名单在发送时评估,依据的是你的退订名单在那一刻的状态,因此在安排和发送之间退订的人仍会被尊重。
  • 已保存的模板在发送时必须仍然存在。 如果你在安排定时发送后删除了模板,消息将不会发送。其收件人将以 rejected 返回,并附带 generation_failure。

错误

状态代码触发条件
422E10003你所在组织当前计费周期的定时邮件配额已用完
422scheduled_at 不足 30 秒或超过 30 天
422载荷过大,无法暂存;请缩减正文、收件人或元数据,或立即发送
409E10005消息已无法取消:已开始发送、已发送或已被取消
404消息读取结果尚未反映接受状态,或此工作区中不存在该 ID 的消息

Webhooks

在常规投递事件之外,有两个事件专属于定时发送:

  • email.scheduled 表示消息正在等待未来的 scheduled_at 时间。对于使用模板的发送,系统会在发送时间到达时重新加载所选版本并准备其内容。此事件可能在这些工作完成前到达。
  • email.canceled 在定时消息于发送前被取消时触发。

消息触发后,常规的 email.accepted 事件链照常执行。

定时发送事件以异步方式发布。在 email.scheduled 发布之前,消息可能已不再处于等待发送的状态。收到 Webhook 并不意味着读取端点已经显示该状态。

后续步骤

继续查看此主题的文档、指南和示例。