← 返回博客

Email API 集成(2026):把 Rovela 接到你的应用

Rovela Email API 的集成架构:仅服务端的 sending_access、统一的发送封装、Idempotency-Key 设计、重试,以及经过校验的 webhook 处理。

更新于 2026年11月10日
Email API 集成(2026):把 Rovela 接到你的应用

把 Email API 集成进你的应用

Email API 集成要做的,是让 Rovela 变成一个无聊的依赖:业务 handler 只调用一个模块,密钥从不离开服务器,重试不会重复发送,再加一个 webhook 端点告诉你入队之后发生了什么。这不是「在 Node 里怎么发」(那是在 Node.js 中发送邮件),也不是「注册钩子里怎么发」(那是从应用发送事务邮件)。本文是那些文章底下的架构。

可以从Email API产品页、快速开始、发送邮件 API和身份认证入手。 另见Webhook 概览和Webhook 安全。

本系列相关产品指南:

有哪些部分,各由谁负责

部分归属
已验证发信域名、DNS你 + Rovela Dashboard / 域名指南
ROVELA_API_KEY(sending_access)、MAIL_FROM服务端环境 / 密钥库
sendTransactional(…) 封装你的后端
每个逻辑动作的 Idempotency-Key调用封装的那一方
队列 / HTTP 重试你的平台
投递、DKIM、退信Rovela
Webhook 签名校验 + 事件处理带有 signing secret 的服务端路由(只有在你要读信时才需要 full_access)

Rovela 不托管你的应用、Worker 或 cron。你调用POST /emails;它负责入队和投递。

1. 先有域名和密钥,再写代码

验证你要用来发信的域名。为应用创建 sending_access Key,只放在运行封装的环境里。也可以把 Key 限制在这一个域名(身份认证,已验证域名的 Email API)。需要注册 webhook 或查看某封邮件时,再用第二个更收紧的进程(或 Dashboard)持有 full_access。轮换用 create-then-revoke。永远不要把 Key 放进 SPA、移动应用或公开仓库。

2. 只保留一个封装

所有产品代码都应走同一个函数,这样请求头和错误处理才能保持一致:

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

用 Python(urllib / httpx)发同样的头也可以。封装应当:

  • 从环境读取 ROVELA_API_KEY 和 MAIL_FROM。
  • 要求调用方传入 idempotencyKey(用户触发的邮件若缺失,就大声失败)。
  • 同时发送 html 和 text(或 template id + 变量)。
  • 在非 2xx 时抛错,并带上 API 的 message(文档中的错误形态是 { statusCode, name, message })。
  • 把 200 当作「已入队」,把 id 写入日志——而不是「已投递」。

键的设计见邮件 API 的幂等键(用 signup-otp:${userId}:${codeId},不要用 Date.now())。

3. 重试与限流

如果 HTTP 客户端或队列会重试,重试必须带上同一把 Idempotency-Key。遇到 429 要退避;若有 Retry-After 请遵守。批量里每封信单独计入限流(发送邮件 API)。不要在超时后换一把新 key 再发一次。

4. 集成里的 webhook 一侧

注册一个 HTTPS 端点(不能是私网或回环地址),只订阅你处理的事件——通常是 email.bounced、email.complained,也许再加上 email.failed。空的 events 数组会订阅全部事件,包括以后新增的类型,这在生产里通常不合适(概览)。

按文档校验 x-resend-signature(t=<unix>,v1=<hex>,对 "<timestamp>.<raw body>" 做 HMAC-SHA256)。按事件 id 去重。一旦你已接收该事件就返回 2xx;只有 5xx、408、429 会重试,最多五次。细节见验证 Webhook 签名、事务邮件 Webhook。

5. 配置与环境

每个环境都使用已验证域名和真实 Key;Rovela 没有另一套行为不同的沙箱模式或测试 Key(快速开始)。开发时发给你能控制的 catch-all,或靠幂等键避免本地反复运行打到真实用户。预发与生产的 Key 和域名要分开,免得压测打到客户。

FAQ

封装要单独做成微服务吗? 只有在很多应用必须共享时才需要。单个产品里,一个模块就够,也更容易把 sending_access 的范围收紧。

模板放在哪? 已存储模板用 full_access 或 Dashboard 管理,由封装按 id 发送(邮件模板 API 指南)。Batch 不能带 template——先渲染,或一封一封发模板邮件。

边缘函数里能放 Key 吗? 可以,前提是它是带机密的服务端运行时,而不是浏览器打包结果。博客里已有 Cloudflare Workers 等专门的发信文章;密钥规则不变。

这次集成不包含什么? 邮箱开通、IMAP 和 webmail(事务邮件 API + 托管邮箱),以及入站解析(入站与出站 Email API)——等产品需要时,再作为独立模块加上。

让发信成为依赖,而不是支线任务

一个封装、一把只用于发送的 Key、强制的幂等键,再加上经过校验的 webhook 处理。这就是一套可以运维的 Email API 集成——之后从应用发送事务邮件里的每个产品事件,都只是一行调用。