事务邮件 DKIM 配置(2026):DNS TXT、对齐与 API 发送
面向开发者的事务邮件 DKIM 指南:签名原理、selector TXT 记录、常见 DNS 错误、与 SPF/DMARC 的高层对齐,以及域名验证后通过 Rovela 发送。
为什么在放大 OTP / 收据之前先做对 DKIM
事务邮件——注册验证码、密码重置、魔法链接、发票、订单回执——和营销邮件的失败方式不同。Newsletter 进垃圾箱令人烦;OTP 永远到不了就是注册卡死和支持工单。
收件方会评估:发信域名能否证明这封信是真的。DKIM(DomainKeys Identified Mail)是这套证明里的密码学半边:服务商用私钥给每封出站邮件签名;DNS 发布对应公钥,让接收方校验正文与部分头字段在传输中未被篡改。
本文是面向开发者的 事务邮件 DKIM 配置,不是又一篇「SPF + DKIM + DMARC 一页讲完」的通识。通识请看 SPF、DKIM、DMARC 如何协作 与 到达率:DKIM、SPF、DMARC。SPF / DMARC 专文见 SPF 记录配置 与 DMARC 记录配置。
若你在把产品接到事务型 HTTP API,请在拉高 OTP 量之前先把 DKIM(及其配套记录)做对。认证比事后救被烧掉的声誉便宜。
事务 API 发信时 DKIM 如何工作
高层流程:
- 在邮件服务商处添加发信域名(或子域)。
- 服务商为该域生成密钥对。正常 API 发信时你不持有私钥——服务商保管并用其签名。
- 你在选择器作用域的名字上发布 DNS TXT:
<selector>._domainkey.yourdomain.com,值形如v=DKIM1; k=rsa; p=<公钥>。 - 每封发送都会带上
DKIM-Signature头;接收方从 DNS 取公钥并验签。
就 Rovela 而言,文档对密钥归属写得很清楚:
- 通过
POST /domains添加域名会返回所需 DNS 记录。DKIM 密钥按域在服务端生成——从该响应(或控制台)读取,不要用博客示例里的公钥冒充(API:Domains)。 - Rovela 持有私钥并签名出站邮件;你负责发布公钥 TXT(管理域名、域名验证)。
- 未验证域名不能发信。未验证的
from会以400拒绝,而不是收下后静默丢掉(Quick Start)。
DKIM 不能替代 SPF。SPF 回答「这个 IP 能否代发?」;DKIM 回答「这封信是否被改过、由哪个域声明?」;DMARC 告诉接收方检查失败时怎么办,以及标识是否对齐。把它们当成一套系统;本文只深挖 DKIM 这一腿。
你真正要发布的 DNS TXT
形态(占位符与文档一致——务必使用你项目返回的 selector 与 p=):
name: <selector>._domainkey.yourdomain.com
type: TXT
value: v=DKIM1; k=rsa; p=<public key>
生产里常见踩坑:
- selector 是主机名的一部分。这样设计是为了轮换密钥时可以发新选择器,而不必在飞行中的邮件上拆掉旧记录(域名验证)。
- 许多 DNS 控制台把名称当作相对 zone。若仪表盘显示
<selector>._domainkey.yourdomain.com,而服务商会自动追加 zone,你往往只填<selector>._domainkey。把 FQDN 填进相对字段会变成<selector>._domainkey.yourdomain.com.yourdomain.com,永远解析不到——验证会「一直卡住」(域名验证)。 - 不要给 TXT 值再套一层引号(除非服务商会剥掉)。字面量
"会成为记录的一部分并破坏密钥(域名验证)。 - 很长的
p=在 DNS 线格式里常被拆成多段引号字符串,这很正常。原样粘贴 API/仪表盘给出的值,不要手改公钥。
密钥轮换(以文档为准)
轮换时,Rovela 的 domains API 可能给出第二条 DKIM 记录(标注类似 DKIM (add alongside the one above),且 required_for_verification: false)。请与现有记录并存发布,而不是替换。过早删掉旧 TXT 会让仍在用旧密钥签名的邮件全部验签失败(API:Domains)。
与 SPF / DMARC 的高层对齐
发 OTP 不必先写完一整篇 DMARC 策略,但邻居记录必须正确:
| 记录 | 回答的问题 | 典型形态(具体值以你域的实时文档/控制台为准) |
|---|---|---|
| SPF | 这个服务器能否代发? | 域上恰好一条 TXT:v=spf1 include:<our-host> ~all;已有记录则合并 include:(域名验证) |
| DKIM | 邮件是否被篡改? | <selector>._domainkey… 上的 TXT:v=DKIM1; k=rsa; p=… |
| DMARC | SPF/DKIM 失败怎么办? | _dmarc.yourdomain.com 上的 TXT,起步 v=DMARC1; p=none; 再收紧(域名验证、DMARC 配置) |
| Return-path | 退信去哪? | 子域上的 MX + SPF(不是根域);SPF 不会从父域继承(API:Domains) |
DMARC 对齐(简化):可见 From 头里的域,应与 DKIM 签名的域一致(和/或与 SPF 认证的 Mail From / return-path 域一致,取决于策略)。这也是为什么用你真正验证过的域发 from: no-reply@mail.yourapp.com、并把事务流量隔离到发信子域,会出现在到达率指南里(发信子域、OTP 进垃圾箱)。
Rovela 的域名验证会检查为你域返回的那组记录(SPF、DKIM、DMARC,以及配置了 return-path 时的相关记录)。发布后点 Verify;若仪表盘失败可能是传播延迟而非写错,用 dig 核对(域名验证):
dig +short TXT <selector>._domainkey.yourdomain.com
dig @1.1.1.1 TXT <selector>._domainkey.yourdomain.com
若 dig 与仪表盘要求一致,就等传播;若为空或值不同,先修 DNS——不要不停重新生成密钥指望 UI「刷新」。
会弄坏应用 DKIM 的常见 DNS 错误
- 照抄博客里的示例
p=,而不是POST /domains/ 控制台返回的按域密钥(API:Domains)。 - 在相对名称 UI 里重复追加 zone(
selector._domainkey.example.com.example.com)。 - 轮换时在新选择器确认前删掉旧 DKIM TXT。
- 以为根域 SPF 能覆盖 return-path 子域——不能;子域需要自己的 SPF TXT 与 MX(API:Domains)。
- 调试「认证」时发布两条 SPF。两条
v=spf1TXT 是永久失败;应合并 include(域名验证)。 - 把验证当成一劳永逸。DNS 迁移时记录常被删;Rovela 会在已验证域漂移时告警,设置变更还可触发
domain.updated(域名验证)。 - 用未验证或未对齐的域「先测」拉高 OTP 量。Rovela 对未验证
from直接400(Quick Start);别的栈可能收下并默默烧掉声誉。
在 OTP / 收据放量之前先做域名认证
预热与列表卫生重要,但对事务流量,第一道滤网是认证:
- 在自动化注册码与密码重置之前先证明域名。
- 应用邮件优先用发信子域,把事务声誉与营销隔离(发信子域)。
- 魔法链接 / 重置链接保持朴素:关闭点击追踪,让邮件里的 URL 与用户打开的一致(OTP 进垃圾箱)。
- DNS 修好、API 路径上线后,用
Idempotency-Key做幂等发送,避免重试重复投递验证码(API:emails)。
单靠认证不能保证进收件箱——但没有 DKIM(以及对齐的 SPF/DMARC),放大事务量多半是在训练过滤器:你看起来像伪造邮件。
DNS 正确之后,用 Rovela 做发送层
域名验证通过后,Rovela 是 HTTP 发送路径——不是每个请求再配一遍 DNS:
- 添加域名;发布返回的记录(含 DKIM);验证(Quick Start、域名验证、如何配置域名邮箱)。
- 为生产 worker 创建
sending_accessAPI 密钥(Authentication)。 - 从已验证的
from地址调用POST https://api.rovela.dev/emails:
curl -X POST https://api.rovela.dev/emails \
-H "Authorization: Bearer re_your_sending_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: otp-user_42-20261105" \
-d '{
"from": "Acme <no-reply@mail.yourdomain.com>",
"to": "user@example.com",
"subject": "Your Acme code",
"html": "<p>Your code is <b>482193</b>. It expires in 10 minutes.</p>",
"text": "Your code is 482193. It expires in 10 minutes."
}'
200 表示已接受投递,不是「已经在收件箱」。后续用 GET /emails/:id、事件或 webhook 观察(Quick Start)。
本文不编造文档未写的控制台逐步点击路径(文档范围是:添加域名 → 发布所示记录 → Verify;卡住时用 dig)。include: 与 MX 的具体主机名来自你的域的 API/仪表盘响应——绝不要写死博客里的字符串。
Free/Pro 额度见 Pricing。若栈里仍有 SMTP 中继,端口选择见 SMTP 端口 25 / 465 / 587——域名验证后,新应用代码通常应优先 HTTP API。
检查清单
-
已添加域名;DKIM TXT 来自你的返回记录(selector +
p=) - 相对 DNS 名称未双重后缀;TXT 值未被引号污染
-
仅一条合并后的 SPF;已有 DMARC(除非你已更严,否则从
p=none起步) - API 要求时已发布 return-path 子域的 MX + SPF
-
dig确认 DKIM 后再怪发送 API -
Verify 成功;再用已验证
from与Idempotency-Key发 OTP/收据
DKIM 是事务 API 无法在应用代码里伪造的签名。正确发布公钥、轮换时做加法、在域层对齐 SPF/DMARC,然后让发送 API 做它该做的事:投递已签名的邮件。