邮件送达率实战:SPF、DKIM与退信分类

「我们自己发出去的邮件,客户收不到,但发到 Gmail 就是好的」——这句话背后,往往不是 SMTP 配置错了,而是收件方在你看不见的地方做了一系列判断:你的域名有没有 SPF、DKIM 签名对不对齐、发信 IP 有没有被列入黑名单、内容里有多少链接、退信率高不高。这些判断全部发生在「投递成功」之后,所以你看到的日志是 250 OK,用户体验是石沉大海。

本文讲的是「送达率」这件事的工程化做法:怎么建立可观测性、三类认证记录到底在验什么、退信应该怎么分类处理、以及一个大多数人忽略的事实——**同样的配置,不同收件域的判定结果完全不同,所以你必须分域监控**。

一、先建立事实:邮箱不是网络问题,是信誉问题

很多人一上来就怀疑端口被封、怀疑 DNS。实际上你自己用脚本 telnet 一封一封发,和用正规渠道批量发,得到的待遇截然不同。原因在于收件方(尤其是 Gmail、Outlook、QQ 邮箱)评估的是「发信域名 + 发信 IP + 历史行为」这个三元组的信誉,而不是单封邮件本身。

所以第一件事不是改配置,而是搞清楚现状。你需要能回答三个问题:这封邮件到底到没到收件服务器?到了之后是被收下还是被拒?被拒的原因代码是什么?

  • 你自己的 MTA 日志:Postfix 的 /var/log/mail.log 里,一行完整投递记录会包含 to=<user@example.com>、relay=...、dsn=2.0.0、status=sent。这里的 250 只代表「收件服务器接收了」,不代表进了收件箱。
  • 收件服务器的响应码:4xx 是临时拒绝(会重试),5xx 是永久拒绝(不会重试)。区分这两者是整个退信处理的基础。
  • 发送域名的认证状态:SPF、DKIM、DMARC 三者的检查结果,通常只体现在收件方的 Authentication-Results 头里,你自己看不到。

想看到第三点,最实用的办法是给自己发一封测试信,然后在客户端里查看原始邮件头。Authentication-Results 那一行会明确写出 spf=pass / dkim=pass / dmarc=pass,以及它们各自对齐的是哪个域名。这是唯一一个「不收钱就能看到对方判定结果」的窗口,值得每次都做。

二、SPF:一份被误读最多的记录

SPF 的本质是一句话:告诉收件方「哪些 IP 有权用我这个域名发信」。它是一条 DNS TXT 记录,看起来简单,但坑极多。

; 一个常见的写法
example.com.  IN TXT  "v=spf1 ip4:203.0.113.10 include:_spf.google.com include:sendgrid.net -all"

先看机制部分。ip4: 直接列 IP,include: 引入第三方服务商的 SPF 记录,-all 表示「不在列表里的一律硬拒绝」。这里第一个坑就是 -all:如果你的 SPF 里漏了某个实际在用的发信渠道(比如公司用的阿里企业邮、或者某个部门用的第三方营销平台),这些渠道的信会被直接判失败。**在切换渠道的过渡期,用 ~all(软失败)比 -all 安全**,稳定之后再收紧。

第二个坑是 DNS 查询次数上限。SPF 规范限制在一次校验中最多做 10 次 DNS 查询(include、a、mx、ptr、exists 都算)。很多公司层层 include,最后 include:_spf.google.com 一个就吃掉 4~5 次,超限后结果是 permerror——收件方直接判失败,而且这个错误在有些 MTA 里不会告诉你。检查方法:

# 用第三方工具做完整校验(会明确告诉你 lookup 次数)
# 或者本地统计 include 链的深度
dig example.com TXT +short

第三个坑是「记录存在多行」。DNS 允许一个域名有多条 TXT 记录,但 SPF 规范要求**只能有一条以 v=spf1 开头的记录**。如果两条并存,规范规定结果是 permerror。这在域名从旧服务商迁移过来时特别常见——旧服务商留下的 SPF 没删干净,新的加了上去,从此所有邮件认证失败,但 dig 查出来「看着都有」。

三、DKIM:签名对了,为什么还是 fail

DKIM 用私钥给邮件头部和正文算一个签名,公钥放在 DNS 里,收件方用公钥验签。规则听起来不会出错,但要落地时会发现失败的原因集中在四个地方。

第一,签名域与发件域不一致。DKIM 允许你用 d=newsletter.example.com 签名,而 From 头是 example.com。这在技术上「签名有效」,但在 DMARC 的「对齐」检查里算失败。DMARC 要求 DKIM 的 d= 必须和 From 头的域名相同或为其子域,这叫 relaxed 对齐;严格对齐(adkim=s)则要求完全一致。**用子域签名是最常见的对齐失败原因**,尤其在用第三方邮件平台时。

第二,正文被改写。DKIM 签名覆盖正文的哈希。如果中间有任何环节改写了正文——比如某个转发网关加了一段企业免责声明、或者 MTA 重编码了字符集——签名立刻失效。这就是为什么「转发之后 SPF 也可能 fail、DKIM 也 fail」,也是为什么 ARC 这套机制被发明出来。

第三,公钥选择器(selector)配错。签名头里的 s= 参数是选择器名,公钥必须放在 选择器._domainkey.域名 这个位置。最常见的错误是选择器名大小写不一致(DNS 不区分,但有些工具生成的记录对不上),或者 _domainkey 打成了 _domainkeys。

第四,换行与长度处理。有些版本在签名时用了 relaxed/relaxed 规范化,有些用 simple/simple。simple 模式对头部字节完全敏感,多一个空格就失败。如果同一封信通过两条路径发出,一条成功一条失败,首先要怀疑的就是规范化算法不一致。

# 查看选择器对应的公钥是否存在
dig selector1._domainkey.example.com TXT +short

# 输出应该形如 v=DKIM1; k=rsa; p=MIIBIjANBg...
# p= 为空说明记录存在但公钥被删了,同样会 fail

四、DMARC:从「认证」到「策略」,也决定了你能看到什么

SPF 和 DKIM 只回答「这封信是不是你发的」。DMARC 回答的是另外两个问题:「如果不是你发的,我该怎么办」,以及「把结果报告给谁」。

_dmarc.example.com.  IN TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; pct=100; fo=1"

策略 p 有三个值:none(只观察不处理)、quarantine(进垃圾箱)、reject(直接拒收)。**正确的上线路径一定是 none → quarantine → reject,每一步至少观察两周**。直接从 none 跳到 reject,等于把「不知道有没有在用但没配 SPF 的业务」一次性全部打掉。

rua 是聚合报告的接收地址。这是整件事里最有价值的部分:Gmail、Outlook、Yahoo 会每天给你发一份 XML 压缩包,告诉你「有多少封信用的是你的域名、有多少通过了 SPF/DKIM、分别来自哪些 IP」。**这是唯一能发现「有人在伪造你的域名发信」的手段**,也是判断能不能收紧策略的唯一依据。

聚合报告是 XML 且每天一封,几百封的站点一天就有几十 KB。落地上建议:用一个专门的信箱接收,写一个脚本解压、解析、入库,只看三个数——总邮件数、pass 率、以及「通过率低但量大的源 IP」。如果某个 IP 每天发几千封而 pass 率是 0,那不是你自己发的,是有人在冒用。

fo=1 的意思是「任何一项检查失败就发一份失败报告」,但有些收件方根本不实现 ruf。不要指望 ruf,只靠 rua 就够了。

五、退信要分类,不能只统计数量

退信率高是导致域名进黑名单最直接的原因,所以「降低退信率」比「优化内容」优先级更高。关键动作是把退信分成三类,分别处理:

  • 硬退信(5xx):地址不存在(550 5.1.1)、域名不存在(550 5.1.2)、收件方明确拒收(550 5.7.1 常见于黑名单)。这类地址必须**立刻从列表中移除**,重试毫无意义,而且会拉高退信率。
  • 软退信(4xx):邮箱满(452 4.2.2)、限流(421 4.7.0)、服务器临时故障(451)。这类要重试,但要有上限——一般 3 次、间隔递增(1 小时、6 小时、24 小时),超过就转为硬退信处理。
  • 「接收了但没投递」:收件方返回 250,但用户没收到。这类在 MTA 日志里完全是成功的,只能靠用户反馈发现。常见原因就是本文的主题——被判定为垃圾邮件。

第三类是最需要专门处理的。一个可行的做法是在邮件里放一个「没收到验证码?」的入口,并在后台记录这类工单,按收件域(@gmail.com / @qq.com / @163.com)统计。**如果某个域的这类工单量突然上升,几乎可以确定是你的信被该域降权了**,而不是用户看漏了。

另一个必备动作是「发信量与退信率的看板」。经验阈值:单次群发退信率超过 5%,或者连续三天日均退信率超过 2%,就该停下来清理列表。Gmail 对发信域的硬性要求里,垃圾邮件率低于 0.1% 是「良好」的门槛,超过 0.3% 会造成系统性降权——这个数量级意味着你几乎没有试错空间。

六、内容层面的四个降权因素

认证全过、退信率为零,邮件依然可能进垃圾箱。这时问题在内容特征上。下面是个人站长最常踩的四个:

第一,短链接和链接占比。正文里用 bit.ly 这类短链,是垃圾邮件的强特征——因为短链隐藏了真实目标,无法被内容审核。同理,一张图 + 一个按钮、几乎没文字的「营销模板」,会被判定为低质量。**用你自己的域名做跳转页**,比短链安全得多。

第二,HTML 与纯文本的比例。只发 HTML 不发纯文本版本,是个弱信号;但如果 HTML 里有大量图片、几乎没有文字,就是强信号。多部分邮件(multipart/alternative)同时带 HTML 和纯文本,是标准做法。

第三,退订方式。没有退订链接、或者退订需要登录,会被直接判为垃圾邮件。更现代的做法是 List-Unsubscribe 头部,支持一键退订。批量邮件必须带,事务性邮件(验证码、订单通知)则不应该带。

第四,发信节奏。新 IP、新域名一上线就发几千封,这是最典型的垃圾邮件行为特征。正确做法是「预热」:第一周每天几十封,第二周几百封,逐步爬升。**新域名至少养两周再上量**,这个时间成本省不掉。

七、把监控做成每日一屏

送达率治理的难点不在配置,而在「问题发生时你不知道」。所以最后落地成一个每天看一眼的看板,只需要四行数据:

  • 按收件域拆分的投递成功率:gmail.com / outlook.com / qq.com / 163.com 各一行。分域是核心,聚合看会掩盖单个域的问题。
  • 4xx 与 5xx 的比例:4xx 突然上升通常意味着被限流(你的量太大或信誉在下降),5xx 上升意味着列表质量在恶化。
  • DMARC 聚合报告的 pass 率:这是唯一能反映「外部怎么看你的域名」的指标。
  • MX 与 SPF/DKIM 记录的变更告警:DNS 记录被人改了(哪怕是误操作)是最容易被忽略的事故,用一个每天跑一次的 dig 对比基线就够。
# 每日基线快照,和昨天 diff
for r in TXT:example.com TXT:selector1._domainkey.example.com TXT:_dmarc.example.com MX:example.com; do
  t=${r%%:*}; d=${r#*:}
  dig +short "$d" "$t" | sort
done > /var/lib/mailcheck/today.txt
diff /var/lib/mailcheck/yesterday.txt /var/lib/mailcheck/today.txt

这段脚本五分钟能写完,但它能拦住「某次误操作把 SPF 记录删了,三天后才发现所有邮件进垃圾箱」这类事故。

八、小结:送达率是流程问题,不是配置问题

把上面这些收拢成三句话。第一,**认证是入场券,不是通行证**:SPF/DKIM/DMARC 全过只保证你不被直接拒收,进不进收件箱取决于信誉,而信誉来自长期稳定的发信行为。第二,**退信管理比内容优化更重要**:硬退信立刻清理、软退信有限重试、分域统计「收了但没到」的投诉,这三件事做到位,比调十版文案有用。第三,**必须分域观测**:聚合数据会骗人,Gmail 好不代表 QQ 邮箱好,而你的用户可能在任何一个域里。

最后一句话是给新站长的:不要用主域名做营销发信。把 newsletter 放到 news.example.com 上,让主域名专门发验证码和订单通知。这样即使营销信出了问题、域名被降权,业务邮件也不会跟着遭殃——这是唯一一个「成本几乎为零、但能救你一命」的隔离措施。

Last modification:October 31st, 2026 at 08:31 pm

Leave a Comment