批量与定时发送
一次请求发出最多一百封个性化邮件,以及稍后才发出的邮件。
批量:一次请求,多封邮件
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——已经离开的邮件撤不回来。 - 投递时会再检查一次抑制列表,所以在定时和发送之间退过信的地址会被跳过,而不是照发。
全部字段和错误见 发送邮件。