← 返回博客

从应用发送事务邮件(2026):接入 Rovela API

把应用里的注册、结账和密码重置事件接到 Rovela 的 POST /emails:仅服务端密钥、按动作的 Idempotency-Key,以及同时提供 HTML 与 text 正文。

更新于 2026年11月10日
从应用发送事务邮件(2026):接入 Rovela API

从你的应用发送事务邮件

当用户注册、结账或请求重置密码时,应该由你的应用发信——放在服务端 handler 里,密钥永远不要发到浏览器。本文把这些产品事件接到 Rovela 的 POST /emails。封装架构见Email API 集成;「什么是事务 API」见事务邮件 API。各语言的总览已经有了(在 Node.js 中发送邮件、用 Python 发送邮件、用邮件 API 发送验证码)。

可以从Email API产品页、快速开始、发送邮件 API和身份认证入手。 官方示例:验证码、密码重置、发票附件。

本系列相关产品指南:

调用应该放在哪

把发送放在刚刚提交了用户动作的、受信任的同一进程里:

不要从 SPA 或移动应用调用 Rovela。浏览器只显示「我们已发送验证码」;与 API 对话的是服务器(身份认证)。

一个适合放进 handler 的例子

const res = await fetch("https://api.rovela.dev/emails", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.ROVELA_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": `signup-verify:${userId}:${codeId}`,
  },
  body: JSON.stringify({
    from: process.env.MAIL_FROM, // e.g. "Acme <no-reply@yourdomain.com>"
    to: user.email,
    subject: `Your Acme code: ${code}`,
    html: `<p>Your verification code is <b>${code}</b>. It expires in 10 minutes.</p>`,
    text: `Your verification code is ${code}. It expires in 10 minutes.`,
    tags: { kind: "signup-otp", userId },
  }),
});
if (!res.ok) {
  const err = await res.json();
  throw new Error(`Rovela ${res.status}: ${err.message}`);
}
const { id } = await res.json(); // accepted and queued — not yet delivered

每个 handler 的清单:

  1. MAIL_FROM 位于已验证域名的 Email API。
  2. ROVELA_API_KEY 是环境里的 sending_access。
  3. Idempotency-Key 对这一次动作稳定(password-reset:${userId}:${tokenId}),而不是 Date.now()。
  4. 同时发送 html 和 text(主题和 text 正文里的验证码,才是手表或锁屏上能看到的)。
  5. 记录返回的 id,永远不要记录 API Key。200 表示已入队,不是已投递。
  6. 失败要进入你的错误追踪;超时重试时必须带上同一把 key。

营销会改的欢迎文案放进已存储模板(用模板发送欢迎邮件)。秘密(验证码、令牌)仍在应用内生成。

接到常见框架(契约相同)

无论你在 Express、Django、Rails、Laravel 还是 Serverless 函数里,步骤都一样:服务端环境变量、一个封装、一次 POST。博客上已有的框架文章(Next.js、FastAPI、Laravel OTP 等)都使用这套契约。如果该框架里目前走的是 SMTP,迁移路径是从 SMTP 迁到 Email API。

若一个任务里有许多个性化后续(试用提醒),把该任务改走批量事务邮件 API。「在 09:00 发送这封已经定稿的正文」走定时发送邮件。「系统越过阈值」走邮件通知 API。

用户被发信之后

订阅 email.bounced 和 email.complained,避免坏地址一直停在你的重试循环里(事务邮件 Webhook)。若用户应能回复给真人,把 reply_to 设到托管邮箱(事务邮件 API + 托管邮箱)。

FAQ

用户把「发送验证码」点了两次。 若两次请求共享同一把 Idempotency-Key(同一个 codeId),他们只会收到一封。若你签发了两个验证码,也就签发了两把 key——第二次应在应用层拦住(限流,或复用尚未过期的码)。

可以在请求 handler 之外,用队列 Worker 发吗? 可以,而且常常应该如此,只要 Worker 持有 sending_access,并且幂等键在动作被接受时就生成,而不是等到 Worker 碰巧运行。

GraphQL 或 tRPC 呢? 它们仍然是服务端过程。复用封装即可;博客里已有专门写这些入口的文章。

第一次接线? 先按5 分钟发送第一封邮件或第一封邮件指南走一遍,再回来把它接到真正的用户事件上。

在事件发生的地方调用 API

应用已经知道用户刚才做了什么。把密钥和发送留在那个动作旁边,给请求一把稳定的幂等键,然后让 Rovela 投递。「从应用发送事务邮件」要的就是这些。