批量事务邮件 API(2026):用 Rovela 一次发送最多 100 封个性化消息
使用 Rovela 的 POST /emails/batch 一次发送最多 100 封个性化事务邮件:校验、抑制名单、按分片的 Idempotency-Key,以及何时改走广播。
批量事务邮件 API(不是营销群发)
批量事务邮件 API 用一次 HTTP 请求发出许多个性化消息——试用提醒、续费通知、按用户的摘要——但不会把它们变成通讯。Rovela 的端点是 POST https://api.rovela.dev/emails/batch:最多 100 条普通发送正文,每条有自己的收件人和内容。
这不是批量发信或批量发信与单封发送(更宽的批量 vs 单封),也不是广播。营销名单展开、一键退订和主题抑制,请用广播(事务邮件与营销邮件)。字段级参考见发送邮件 API;下面的行为说明依据批量与定时示例。
可以从Email API产品页、快速开始、发送邮件 API和身份认证入手。
本系列相关产品指南:
- 事务邮件 API
- HTTP 邮件 API
- 从 SMTP 迁到 Email API
- SaaS 应用的 Email API
- 用 curl 发送邮件
- 事务邮件 API + 托管邮箱
- 入站与出站 Email API
- 邮件发送 API
- 邮件通知 API
- Email API 集成
- 事务邮件 Webhook
- 简单的事务邮件 API
- 从应用发送事务邮件
- 已验证域名的 Email API
请求
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-11-10" \
-d '[
{
"from": "Acme <reminders@mail.acme.com>",
"to": "alice@example.com",
"subject": "Your trial ends in 3 days",
"html": "<p>Hi Alice, your trial ends on Nov 13.</p>",
"text": "Hi Alice, your trial ends on Nov 13."
},
{
"from": "Acme <reminders@mail.acme.com>",
"to": "bob@example.com",
"subject": "Your trial ends in 3 days",
"html": "<p>Hi Bob, your trial ends on Nov 13.</p>",
"text": "Hi Bob, your trial ends on Nov 13."
}
]'
响应是 { "object": "list", "data": [ { "object": "email", "id": "…" }, … ] },顺序与你提交的数组相同。为这一整块带上 Idempotency-Key(例如 digest-2026-11-10-0),这样任务崩溃后可以安全重试该分片(邮件 API 的幂等键)。
每条使用与 POST /emails 相同的模式(from、to、subject、html / text、scheduled_at、attachments、tags……),但有两个刻意的例外:template 和 topic_id 会被拒绝,而不是被忽略。如果 batch 默默丢掉 topic_id,可能写信给已退订该主题的人;如果丢掉 template,就会发出没有正文的信。这些请走一次一次的 POST /emails,或先自己渲染 HTML 再批量。
一批请求会如何表现
根据批量示例:
- 在写入或扣费之前先做校验。 一条坏记录——域名未验证、正文为空——会让整单以指出问题的
400被拒绝。先搞清楚有没有受理任何东西再重发同一把幂等键(400表示什么都没写入)。 - 受理之后,各条独立投递。 某一收件人后来退信,不会收回其他信。
- 被抑制的地址会被跳过,而不是整单拒绝。 与单封发送不同,batch 不会因为某收件人已被抑制而拒收该条。它会被受理,然后在投递时跳过,状态为
suppressed,并产生email.suppressed事件。 - 按收件人计费,并不比 100 次单封更便宜。 100 封的一批与 100 次单封费用相同,限流也按同样方式计算(大型任务中途可能遇到
429;每封单独计数)。把任务拆成多批是为了更快,不是为了更便宜。 - 每条都可以带
scheduled_at,同样的 ISO 8601 规则(必须带偏移)。抑制名单会在投递时再检查一次(定时发送邮件)。
每条内部的 to+cc+bcc 仍然受 50 人上限约束。
超过 100 封时
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",
"Idempotency-Key": "digest-2026-11-10-" + 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);
}
}
这段模式来自官方示例。Worker 上使用 sending_access。
什么时候不要用 batch
- 注册验证码和密码重置——每个动作一次
POST /emails,并用「每一次尝试」的键(用邮件 API 发送验证码,SaaS 应用的 Email API)。 - 同一封营销正文发给联系人名单或分段——POST /broadcasts。
- 只有一两个收件人——一次
POST /emails更简单。
FAQ
一批里能混用 scheduled_at 和立即发送吗? 每条都是独立消息;只给需要等待的条目设置 scheduled_at。示例写明:单封或 batch 条目都可以带 scheduled_at。
如何取消一批里的某一封? 用 full_access 调用 POST /emails/:id/cancel,且该信仍处于 scheduled,或已入队但尚未交给邮件服务器——与单封相同(用 curl 发送邮件)。
API 参考里的「独立处理」和「全部校验或全部失败」矛盾吗? 把它们放在一起读:示例明确写了校验在写入/扣费之前是整单进行的;一批被受理之后,投递结果按每封计算。拿不准时,把 400 当作「没有入队」,并检查 200 返回的 id 列表。
批量个性化,但保持事务性
在任务里渲染每封正文,按 100 封一组 POST /emails/batch,并为每个分片设置幂等键。这就是批量事务邮件 API——适合提醒和摘要,也故意不适合做活动群发。