从 p=none 走到 p=reject,而不弄丢自己的邮件
分阶段推进 DMARC:先把聚合报告读明白,把所有合法发信方找齐并配上对齐,pct 已被移除之后改用 t=y 当刹车,最后才真正开启强制。
发布 v=DMARC1; p=none 大概花一分钟。发布 p=reject 也差不多一分钟——但如果你在同一个下午干完这两件事,接下来一周可能都在解释为什么账单、密码重置邮件和招聘系统的通知全都不到了。
这两条记录之间的差距不是技术上的,是清点上的。p=reject 是请求收信方拒收未通过 DMARC 的邮件,而不是保证每家都会照做。几乎每个组织实际发信的地方,都比它凭记忆能列出来的多。所谓上线流程,就是在你提出这个请求之前,先把这些地方找齐。
如果下面的概念还需要打底——什么叫对齐、为什么光 SPF 通过不算数——先看SPF、DKIM、DMARC 到底各管什么。这篇默认你已经有那套模型,只谈操作这一半。
第 0 阶段:发一条只做观察的记录,别的什么都不做
第一条记录的唯一任务,是让收信方开始给你寄报告:
v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com
p=none 不请求按 DMARC 强制处置。收信方仍可照常使用垃圾邮件与滥用过滤;变化在于,参与 DMARC 的收信方可以开始就使用你域名的邮件发送聚合报告。
关于那个报告地址,有两件事最好一上来就搞对,因为它们出错时都是静默的。
如果报告地址在别的域名下,对方域名必须授权你。 想把报告发到 you@dmarc-vendor.example,需要在 yourdomain.com._report._dmarc.dmarc-vendor.example 上有一条含 v=DMARC1 的 TXT 记录。缺了它,规范的发报方会直接丢弃你的报告——而「收不到报告」和「根本没人发过」看起来完全一样。托管型 DMARC 服务商通常会替你发布这条记录,但值得确认一下,别默认。
聚合报告是 XML,通常会用 gzip 压缩。 报告周期通常按 UTC 天算,但同一收信方可能发多份,也可能一份都不发。小域名用一个邮箱接着就行;量一大就需要能解析它们的东西。无论哪种,别把它扔进一个没人看的邮箱——报告就是这个阶段的全部意义。
记录是否生效、报告目的地是否已授权,可以用 SPF/DMARC 检测工具确认,它会逐个标签读记录,并专门验证外部报告授权。
然后就是等。如果这是供员工日常发信、也可能参与邮件列表的通用域名,RFC 9989 建议在 p=none 至少观察一个月;即便是专用发信域,也要等到按月、按季和按年的发信方都出现。在低频发信方还没在数据里露过面时就开启强制,意外正是这么发生的。
第 1 阶段:把报告变成一份发信方清单
聚合报告按来源 IP 告诉你:发了多少封、SPF 过没过、DKIM 过没过,以及最关键的那一项——其中是否有一个与你的 From 域名对齐。
你要做的就是一张表:
| 发信方 | SPF 对齐 | DKIM 对齐 | 处理 |
|---|---|---|---|
| Google Workspace(员工邮件) | 是 | 是 | 无需处理 |
| 营销平台 | 否 | 否 | 两个都配 |
| 账单系统 | 是 | 否 | 补 DKIM |
| 客服系统 | 否 | 否 | 配好,或迁到子域 |
| 境外的陌生 IP | 否 | 否 | 开启强制前先查清楚 |
每次都让人意外的是这张表有多长。薪资系统、CI 通知、CRM、日程工具发出的会议邀请、2023 年某人注册的那个问卷工具——每一个「代表你」发信的 SaaS 都会出现在这里。这就是第 0 阶段换来的东西。
有些来源不是你的系统,而是真实的转发:收件人设了自动转发,转发服务器用它自己的 IP 中继,于是 SPF 失败。但只要 DKIM 还活着,DMARC 在 DKIM 这一侧仍然通过。这是「与其押注 SPF,不如把 DKIM 对齐配到每一处」最有力的理由之一。
顺带说清报告做不到什么:它是聚合的,一个失败来源体现为一个 IP 加一个计数,不是一封你能读的信。当你需要知道某一封具体的邮件为什么失败时,答案在它的邮件头里——把原始邮件源存下来,丢进邮件头分析工具——具体该看哪些地方,怎么读邮件头讲的就是这个。
第 2 阶段:把每个合法发信方都配上对齐
对清单上的每一项,目标是至少有一条对齐的通过。实践中优先把 DKIM 对齐配到每一处,因为它扛得住转发,也不消耗你的 SPF 预算。
在服务商侧配 DKIM,通常是服务商生成密钥对,给你一条 CNAME 或 TXT 记录去发布,位置多半形如 s1._domainkey.yourdomain.com。关键细节在生成的签名里那个 d=:它必须是你的域名,不是服务商的。一个 d=mailer-provider.com 的签名验得完美无缺,却和任何东西都不对齐。既然人已经在那儿了,顺手看一眼密钥长度:RFC 8301 把下限定在 1024 位、推荐 2048,而服务商现在仍然会发 1024 位的密钥。检测工具会把找到的每一把密钥都量一下位数。
SPF 对齐通常要求 SMTP 信封发件人在你的域名之下。很多服务商为此提供「自定义退信域名」或「自定义 Return-Path」的设置——配上它,SPF 才有可能对齐。不配的话,信封发件人一直是服务商的退信域名,SPF 在那儿通过,对 DMARC 毫无贡献。看一封已送达邮件时,应以 smtp.mailfrom(空反向路径则看 smtp.helo)为准,不能只看 Return-Path。
改 SPF 的时候顺便盯住天花板。SPF 只允许十个消耗 DNS 查询的项,include 是递归计数的,每加一家服务商至少多一个。超了返回 PermError,DMARC 按 SPF 失败处理——而记录在超限的同时看起来完全正常。检测工具会把整棵 include 树展开、把每个消耗查询的项都对着上限数一遍,这是看清自己离上限多远最实际的办法。这个数是保守上限而不是逐封邮件的结论,所以超过十要读成「有些发信路径会撞上 PermError」,然后去把那些路径找出来。
如果某个发信方实在配不了对齐,把它迁到子域——比如客服系统用 notifications.yourdomain.com。子域邮件按 sp(或 np)判定,于是你可以在主域上严格执行,同时让子域跑在另一档策略上。
第 3 阶段:先隔离,再拒收
清单干净、报告安静之后,往上走一档。先隔离:
v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com
隔离有一种拒收没有的性质:可挽回。一封本不该失败的信落进垃圾箱,收件人仍能找到它并告诉你;而一封被拒收的信就没了,发信方收到的退信未必会转给你。通用邮件域应在隔离档至少观察一个月,继续读报告。
然后:
v=DMARC1; p=reject; sp=reject; np=reject; adkim=r; aspf=r;
rua=mailto:dmarc-reports@yourdomain.com
当这条记录由组织域继承时,sp=reject 管已存在的子域,np=reject 管压根不存在的子域——正是攻击者爱编造的那些,编造的原因就是没有任何东西约束它们。np 值得专门写上:不花什么成本,堵住的却是一个真实缺口。
上线用的刹车是
t,不是pct。 如果你是 2026 年之前学的 DMARC,当时的标准做法是用pct=20让策略只作用于一部分邮件。RFC 9989 在 2026 年 5 月废止 RFC 7489 时移除了pct(连同rf和ri),并指明由测试模式标签t接替它。仍然带着pct的记录照常工作——收信方是忽略被移除的标签,而不是拒绝整条记录——但行为从此分成两半:遵循现行规范的收信方对每一封都执行你的完整策略,仍认pct的则按比例抽样。如果你把pct当安全余量,那么你以为还在的余量,其实已经不在了。
t=y 到底是什么行为
t=y 是现在的刹车,而它经常被读成「先别强制执行」。更准确地说,它请求收信方不要直接套用已公布的策略;RFC 9989 预期结果会比你公布的低一档,但最终怎样处置仍由收信方决定:
| 你公布的 | 加上 t=y 后的预期处理 |
|---|---|
p=reject | quarantine |
p=quarantine | none |
p=none | none |
所以 p=reject; t=y 的用法是:把你最终想要的策略先公布出去,而预期处理停在隔离。谨慎切换时它确实好用——同时也是个坑,因为一条一眼看上去是 p=reject 的记录,在有人把标签摘掉之前实际请求的是隔离。给它设个提醒。检测工具在看到 t=y 时会把预期处理单独列出来,让降档结果直接可见,而不是靠你记着。
开启强制之后要盯什么
强制不是报告关系的终点。rua 要长期留着——你就是靠它发现某个新上的 SaaS 开始以你的名义发信,或者某个服务商轮换了 DKIM 密钥却没通知你。
有两种情况值得点名,因为它们看起来像 DMARC 坏了,其实不是:
邮件列表。 会在正文追加签名档或改写主题的列表,会让 DKIM 签名失效,而且它用自己的 IP 中继,SPF 也一并失败。在 p=reject 之下,用严格收信方的订阅者就收不到这些邮件了。做得好的列表会把 From 头改写成列表自己的域名来规避;做得不好的不会。还有一些中间方会用 ARC 把自己看到的结果封存下来,让下游收信方知道这封信在被列表改动之前是通过认证的——但这只在收信方信任那个中间方时才起作用,所以它是缓解而不是解决。无论哪种,这都是开启强制的已知代价,不是你这边配错了。
转发。 形态类似,结局更好:只要正文没被改动,对齐的 DKIM 就还在,DMARC 依然通过。
清单
- 发布带可用
rua的p=none,报告地址在域外的话,确认外部授权记录已就位。 - 等到所有发信方都出现。 通用邮件域在
p=none至少观察一个月。 - 从报告里建出发信方清单:每一个来源,以及它有没有一条对齐的通过。
- 逐个配好对齐,优先 DKIM 且
d=yourdomain.com。盯住 SPF 十次上限。实在配不了的迁到子域。 - 切到
p=quarantine,通用邮件域再继续观察至少一个月。 - 切到
p=reject,并且显式写上sp和np。 - 需要刹车就用
t=y——记住它是降一档,也记住要摘掉。 rua永远留着。
整个流程刻意地枯燥。p=reject 本身没有任何难发布之处,功夫全在那份清单上;而那份清单之所以存在,是因为你在 p=none 上老老实实读了几周报告,而不是靠猜。想检查最终这条记录——策略、子域与不存在子域的处理、对齐档位、背后的 SPF 预算,以及报告地址是否已获授权——把域名过一遍 SPF/DMARC 检测工具。