← 返回博客

事务邮件 DKIM 配置(2026):DNS TXT、对齐与 API 发送

面向开发者的事务邮件 DKIM 指南:签名原理、selector TXT 记录、常见 DNS 错误、与 SPF/DMARC 的高层对齐,以及域名验证后通过 Rovela 发送。

更新于 2026年11月4日
事务邮件 DKIM 配置(2026):DNS TXT、对齐与 API 发送

为什么在放大 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 如何工作

高层流程:

  1. 在邮件服务商处添加发信域名(或子域)。
  2. 服务商为该域生成密钥对。正常 API 发信时你不持有私钥——服务商保管并用其签名。
  3. 你在选择器作用域的名字上发布 DNS TXT:<selector>._domainkey.yourdomain.com,值形如 v=DKIM1; k=rsa; p=<公钥>。
  4. 每封发送都会带上 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=…
DMARCSPF/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 错误

  1. 照抄博客里的示例 p=,而不是 POST /domains / 控制台返回的按域密钥(API:Domains)。
  2. 在相对名称 UI 里重复追加 zone(selector._domainkey.example.com.example.com)。
  3. 轮换时在新选择器确认前删掉旧 DKIM TXT。
  4. 以为根域 SPF 能覆盖 return-path 子域——不能;子域需要自己的 SPF TXT 与 MX(API:Domains)。
  5. 调试「认证」时发布两条 SPF。两条 v=spf1 TXT 是永久失败;应合并 include(域名验证)。
  6. 把验证当成一劳永逸。DNS 迁移时记录常被删;Rovela 会在已验证域漂移时告警,设置变更还可触发 domain.updated(域名验证)。
  7. 用未验证或未对齐的域「先测」拉高 OTP 量。Rovela 对未验证 from 直接 400(Quick Start);别的栈可能收下并默默烧掉声誉。

在 OTP / 收据放量之前先做域名认证

预热与列表卫生重要,但对事务流量,第一道滤网是认证:

  • 在自动化注册码与密码重置之前先证明域名。
  • 应用邮件优先用发信子域,把事务声誉与营销隔离(发信子域)。
  • 魔法链接 / 重置链接保持朴素:关闭点击追踪,让邮件里的 URL 与用户打开的一致(OTP 进垃圾箱)。
  • DNS 修好、API 路径上线后,用 Idempotency-Key 做幂等发送,避免重试重复投递验证码(API:emails)。

单靠认证不能保证进收件箱——但没有 DKIM(以及对齐的 SPF/DMARC),放大事务量多半是在训练过滤器:你看起来像伪造邮件。

DNS 正确之后,用 Rovela 做发送层

域名验证通过后,Rovela 是 HTTP 发送路径——不是每个请求再配一遍 DNS:

  1. 添加域名;发布返回的记录(含 DKIM);验证(Quick Start、域名验证、如何配置域名邮箱)。
  2. 为生产 worker 创建 sending_access API 密钥(Authentication)。
  3. 从已验证的 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 做它该做的事:投递已签名的邮件。