批量与定时发送

一次请求发出最多一百封个性化邮件,以及稍后才发出的邮件。

批量:一次请求,多封邮件

POST /emails/batch 接受一个最多 100 项的数组,每一项都是普通的发送请求体。每一项都是独立的邮件,有自己的收件人和内容——适合每晚的摘要,或者每封都略有不同的一轮提醒。

curl -X POST https://api.rovela.dev/emails/batch \
  -H "Authorization: Bearer re_your_sending_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reminders-2026-10-05" \
  -d '[
    {
      "from": "Acme <reminders@mail.acme.com>",
      "to": "alice@example.com",
      "subject": "你的试用还剩 3 天",
      "html": "<p>Alice 你好,你的试用将于 10 月 8 日结束。</p>",
      "text": "Alice 你好,你的试用将于 10 月 8 日结束。"
    },
    {
      "from": "Acme <reminders@mail.acme.com>",
      "to": "bob@example.com",
      "subject": "你的试用还剩 3 天",
      "html": "<p>Bob 你好,你的试用将于 10 月 8 日结束。</p>",
      "text": "Bob 你好,你的试用将于 10 月 8 日结束。"
    }
  ]'

响应按同样的顺序,为每一项返回一个 id:

{
  "object": "list",
  "data": [
    {"object": "email", "id": "c41a..."},
    {"object": "email", "id": "c41b..."}
  ]
}

批量请求的行为

  • 要么全部成功,要么全部失败。写入或扣费之前会先检查每一项。只要有一项有问题——域名未验证、正文为空——整批都会被拒,返回一个点明问题的 400。
  • 被抑制的地址是跳过,不是拒绝。和单封发送不同,批量请求不会因为某个收件人在抑制列表里而拒绝那一项。它会被受理,然后在投递时被跳过,状态为 suppressed,并触发 email.suppressed 事件。
  • 按收件人计费。100 项的批量和 100 次单发费用一样,计入频率限制的方式也一样。把大任务拆成批量会更快,但不会更便宜。
  • 不支持模板。每一项都不能用 template 或 topic_id;自己渲染正文,或者用 POST /emails 逐封发送模板邮件。

发送一个很长的列表

async function sendAll(messages) {
  for (let i = 0; i < messages.length; i += 100) {
    const chunk = messages.slice(i, i + 100)
    const res = await fetch("https://api.rovela.dev/emails/batch", {
      method: "POST",
      headers: {
        Authorization: "Bearer " + process.env.ROVELA_API_KEY,
        "Content-Type": "application/json",
        // 每一块的 key 固定不变,任务崩溃后重跑也不会把前面几块再发一遍。
        "Idempotency-Key": "digest-2026-10-05-" + i,
      },
      body: JSON.stringify(chunk),
    })
    if (res.status === 429) {
      await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("retry-after") ?? 1)))
      i -= 100            // 同一块再来一次
      continue
    }
    if (!res.ok) throw new Error("chunk " + i + ": " + (await res.json()).message)
  }
}
给一个列表发营销邮件,请用群发(broadcast):退订链接、主题订阅和按联系人的抑制它都替你处理了。批量接口是给「恰好要一次发很多」的事务邮件用的。

定时:稍后再发

在任意一次发送——单封或批量中的一项——里加上 ISO 8601 格式的 scheduled_at。邮件现在被受理,到点才投递;在那之前它的状态是 scheduled。

curl -X POST https://api.rovela.dev/emails \
  -H "Authorization: Bearer re_your_sending_key" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <hello@mail.acme.com>",
    "to": "alice@example.com",
    "subject": "第一周用得怎么样?",
    "html": "<p>Alice 你好,到目前为止有什么问题吗?直接回复这封邮件就行。</p>",
    "text": "Alice 你好,到目前为止有什么问题吗?直接回复这封邮件就行。",
    "scheduled_at": "2026-10-12T09:00:00+08:00"
  }'

一定要带时区偏移。没有偏移的时间戳不是一个时刻——在每个时区里读出来都是不同的时刻。

改期或取消

两者都需要 full_access 密钥或控制台会话——创建这封邮件的 sending_access 密钥在这里会得到 401。所以改动已经排队的邮件,需要一个有意为之的、权限更高的操作。

# 改期
curl -X PATCH https://api.rovela.dev/emails/<email_id> \
  -H "Authorization: Bearer re_your_full_access_key" \
  -H "Content-Type: application/json" \
  -d '{"scheduled_at": "2026-10-13T09:00:00+08:00"}'

# 取消
curl -X POST https://api.rovela.dev/emails/<email_id>/cancel \
  -H "Authorization: Bearer re_your_full_access_key"
# {"object": "email", "id": "<email_id>", "cancelled": true}
  • 只有邮件仍处于 scheduled 状态时才能改期。取消对已排队、但还没交给邮件服务器的邮件同样有效。
  • 一旦已经发出,两者都返回 400——已经离开的邮件撤不回来。
  • 投递时会再检查一次抑制列表,所以在定时和发送之间退过信的地址会被跳过,而不是照发。

全部字段和错误见 发送邮件。