更新日期:2026-09-18
「SMTP 250」只代表对端服务器把邮件收进了队列,不代表它进了收件箱。注册验证码、找回密码链接、账单发票、订阅周报一旦被投进垃圾箱或被静默丢弃,损失的是真实用户和真实收入,而应用侧日志只会显示「发送成功」。本文把发信链路拆成可观测的环节,让每一次「没收到」都能定位到具体一环。
覆盖范围:用自有域名发信的 WordPress 站点与自研 SaaS 应用,包括自建 Postfix 加 OpenDKIM/OpenDMARC,以及 DNS 仍在自己手里、投递交给第三方 ESP(Email Service Provider)的代发形态。与常见教程的区别在于顺序——多数文章从「加这几条 DNS 记录」讲起,本文从可观测性讲起:先让 DMARC 聚合报告列出「到底有哪些主机在替你的域名发信」,再回头补 SPF 与 DKIM,最后才分阶段收紧。站内 Let’s Encrypt 160 小时与 IP 地址证书实战:Certbot 自动续期和监控 与 WordPress 7.1 生产升级指南:新功能、兼容性检查与安全回滚 是这条链路上游的相关内容,建议对照阅读。
适用范围与环境前提
适用环境:Linux 服务器(Debian/Ubuntu 或 RHEL 系)上的 Postfix 3.x、OpenDKIM、OpenDMARC;DNS 由自己或云解析托管,能自由添加 TXT 记录;发信域名为站点主域名或其子域(如 mail.mf8.biz、news.mf8.biz);收件方包含 Gmail、Outlook、QQ/163 等公共邮箱。WordPress 侧典型组合是 wp-mail-smtp 类插件加一个 ESP,或直接调用本机 sendmail。
不适用场景:纯内网中继(没有可被公网解析的发信域名)、只收信不发信、以及已经拥有专职 deliverability 团队和独立 IP 池的超大规模发信方——那类环境需要的是信誉监控体系与流量调度策略。另需注意,DMARC 只覆盖「别人声称是你的域名」这一层,它不能阻止仿冒显示名的钓鱼邮件,也不能替代内容风控与账号安全。
先看结论
- 先上 p=none 加 rua,让聚合报告跑满 2 至 4 周,把所有替你的域名发信的主机列全,再谈收紧;直接跳到 p=reject 会立刻打断你都不知道存在的合法发信方。
- SPF 一个域名只能有一条 v=spf1 记录。出现两条时,RFC 7208 规定接收方按 permerror 处理,结果是全体失败,而不是「取并集」。
- SPF 的 DNS 查询次数上限是 10 次(RFC 7208 第 4.6.4 节),include 会递归计数。每季度实测一次并写进监控,别等到换 ESP 那天才发现已经 11 次。
- DKIM 用 2048 位 RSA、c=relaxed/relaxed,选择器按 s1、s2 成对存在,轮换时先发布新选择器、确认签名已切换、再删旧的,永远不要让 p= 变成空值。
- DKIM 的 d= 域必须与 From 头域名对齐(relaxed 对齐到组织域,strict 要求逐字符一致),否则 DMARC 不认这次 DKIM 通过——这是「DKIM 明明 pass 但 DMARC 还是 fail」的最常见原因。
- 事务邮件与营销邮件分域、分流、分 IP:验证码和找回密码不能被营销投诉率拖累,也不能因为营销暂停而停止发送。
- 排查从 DMARC 聚合报告开始,而不是从内容开始:报告里的 source_ip 与 auth_results 能直接指出是认证问题、转发问题还是陌生来源问题。
- 任何会改变发信状态的变更都要有回滚:DMARC 收不紧就退回 p=none,DKIM 轮换失败就切回旧选择器,改动前把旧记录原文备份下来。
送达率不是“发送成功”
邮件投递是一条链,任何一环断掉,应用侧的日志都一样是「已发送」:
发信身份(Envelope From / HELO / From 头)→ 认证(SPF、DKIM、DMARC)→ 信誉(IP 与域名的历史表现)→ 过滤(内容、链接、投诉率、名单质量)→ 投递结果(收件箱 / 垃圾箱 / 退信 / 静默丢弃)
应用层看到的「成功」通常只是第一个环节:SMTP 会话被接受。Postfix 日志里的 status=sent 也只说明本机把邮件交给了下一跳。真正决定用户体验的是最后一环,而最后一环只能通过退信通知、用户反馈与接收方的认证报告反向观测。
事务邮件与营销邮件的差别必须在架构层面体现,而不是靠文案区分:
- 事务邮件(注册验证、密码重置、支付回执、发票、安全告警)由用户主动触发,是一对一、可预期的。它们对时延敏感,失败会被用户直接感知为「网站坏了」,退订入口应当是「管理通知偏好」而非营销退订。
- 营销邮件(周报、活动、促销)是批量、非预期的,带来投诉、退订和垃圾邮件举报,是域名信誉的主要消耗方。
把两类流量混在同一个子域和同一个 IP 上,结果是营销的低投诉容忍度绑定到事务邮件上。常见做法是让事务邮件走 mail. 子域、营销邮件走 news. 子域,各自一套 DKIM 选择器、一套 DMARC 子域策略、一条独立配额。分开之后,任何一条流量的信誉问题都不会直接污染另一条。
先建立可观测性
没有观测就没有排查。第一步补齐发信侧日志字段,第二步接上接收方的认证报告。
SMTP 侧必须记录的字段
在 Postfix 中用 maillog 或 journalctl -u postfix 能取到大部分字段,但日志至少要保留 14 天,并且应用侧要把队列 ID 回写到业务记录里,否则用户投诉「没收到」时无法把业务单据与 SMTP 会话关联起来。
必须可检索的字段:队列 ID(Postfix 的 queue_id 或 ESP 返回的 message id)、Envelope From、From 头、收件人、实际发信 IP、HELO/EHLO 主机名、对端返回的认证结果、重试次数与每次响应码、增强状态码(RFC 3463),以及首次入队与最终投递的时间差(排队延迟)。
# 从 Postfix 日志抽取一次投递的关键字段
journalctl -u postfix --since '2 hours ago' \
| grep -E 'status=(sent|bounced|deferred)' \
| awk '{ for (i=1;i<=NF;i++) if ($i ~ /^(to|relay|dsn|status|queued|delay)=/) printf "%s ", $i; print "" }' \
| tail -30
# 只看被拒绝的连接(认证失败、黑名单、速率限制)
grep -E 'NOQUEUE|reject|Client host rejected|550 5\.7\.' /var/log/mail.log | tail -40
退信分类
退信不是一类东西,处理方式完全不同:
- 硬退信(Hard Bounce):增强状态码 5.x.x,如 550 5.1.1 用户不存在。必须立即永久移除该地址,重复投递只会累积投诉。
- 软退信(Soft Bounce):4.x.x,如 421、450 4.2.1 邮箱暂时不可用、452 空间不足。按指数退避重试,通常 24 至 72 小时后暂停该地址,而不是永久删除。
- 策略拒绝(Policy Rejection):多为 5.x.x,但原因是策略而非地址无效,例如 550 5.7.1 SPF/DMARC 校验失败、550 5.7.0 被列入黑名单。这类要修配置,不要删地址;删地址会掩盖真正的故障。
接收方工具与种子测试
主流邮箱服务商为发信方提供了官方的信誉与投递面板,例如 Google Postmaster Tools 与 Microsoft 的 SNDS。它们统计的投诉率、IP 信誉、认证通过率来自接收方自身口径,比第三方猜测可信;各家阈值与适用范围请以官方页面为准,不要照抄二手文章里的数字。
种子/放置测试用于回答「同一封邮件在不同邮箱里落到哪」:把一组自己的测试邮箱分布到目标服务商,发送带唯一标识的测试邮件再检查落点。它只反映测试时刻,不能替代 DMARC 报告。
DMARC 聚合报告是主信号
rua 聚合报告是唯一能告诉你「谁在替你的域名发信」的数据源。它每天以 gzip 压缩的 XML 形式发到指定邮箱,字段包含来源 IP、次数、策略判定结果与 SPF/DKIM 各自的认证结果。只要 rua 生效,即使策略还是 p=none,也能在几天内盘点出全部合法与非法来源。
SPF 正确配置
SPF 声明「哪些主机有权用这个域名的 Envelope From 发信」,接收方据此校验回弹地址(MAIL FROM)与 HELO 身份。它校验的是信封域而不是 From 头域,这一点决定了它必须与 DMARC 对齐配合才有意义。
语法与一条完整示例
; 主域:自有 IP 加 ESP 的 include,末尾用 -all 收口
mf8.biz. TXT "v=spf1 ip4:47.98.123.45 ip6:2408:4003:1234::/64 include:amazonses.com include:sendgrid.net ~all"
; 营销子域单独一条,权限只给营销平台
news.mf8.biz. TXT "v=spf1 include:sendgrid.net -all"
上面出现的服务商 include 域名来自各家官方文档给出的 SPF 值,具体以对应服务商最新文档为准;ip4:47.98.123.45 是示例中的服务器出口 IP,必须替换为真实出口地址(有 IPv6 时补 ip6)。
机制与限定符
- ip4: / ip6::直接列出授权网段,不消耗 DNS 查询次数,优先使用。
- a:把当前域名的 A/AAAA 记录当作授权主机,消耗 1 次查询。
- mx:授权该域名所有 MX 主机,消耗 1 次查询,且每查一个 MX 地址还要各消耗 1 次,是配额杀手。
- include::委托给另一个域名的 SPF 记录,消耗 1 次查询,其内部的 include/a/mx/exists 会递归累计到你的 10 次配额里。
- exists::对任意域名做一次存在性查询,消耗 1 次查询,常用于动态白名单,慎用。
- ptr:RFC 7208 已不建议使用,不要写进新记录。
限定符决定匹配后的行为:+(默认,Pass)、-(Fail,硬失败)、~(SoftFail,通常仍接收但标记)、?(Neutral)。-all 表示「不在列表里的主机一律失败」,配齐所有合法来源后应当使用;~all 是过渡期保守选择,适合还在盘点来源的阶段。+all 等同于放弃 SPF,绝对不要用。
10 次 DNS 查询上限与嵌套 include
RFC 7208 第 4.6.4 节规定,求值过程中的 DNS 查询不得超过 10 次,「空查询」(void lookup,返回 NXDOMAIN 或空答案)不得超过 2 次,否则返回 permerror。难点在于 include 是递归的:你只写了 3 个 include,但每个服务商记录内部可能又各含若干 include 与 a,累计可能已经 12 次。
# 递归统计一条 SPF 记录消耗的 DNS 查询次数(include 会递归累计)
spfcount() {
local d="$1" n=0 tok
local rec="$(dig +short TXT "$d" | tr -d '"' | grep -m1 '^v=spf1')"
for tok in $rec; do
case "$tok" in
include:*) n=$((n + 1 + $(spfcount "${tok#include:}"))) ;;
a|a:*|mx|mx:*|exists:*|ptr|ptr:*) n=$((n + 1)) ;;
esac
done
echo "$n"
}
spfcount mf8.biz # 输出应为 10 以内的整数
超过 10 次时,把 a/mx 换成显式 ip4,或使用服务商提供的合并 include(属于厂商特性,需查官方文档),或把不同发信流拆到各自子域单独维护 SPF。
常见破坏方式
- 两条 SPF 记录:DNS 允许多条 TXT,SPF 不允许。合并成一条,且注意单个 TXT 字符串不要超过 255 字节,长记录拆成多个引号片段。
- 换 ESP 后忘记加 include:新平台邮件直接 SPF fail,而 DKIM 可能正常,表现为「Gmail 能收到但 Outlook 进垃圾箱」这类割裂现象。
- 把 include 写成 include : 或漏掉冒号:整条记录语法错误,后果是 none/permerror。
- -all 上线过早:还有未盘点到的合法来源时,-all 会把它们全部判失败,配合 DMARC p=reject 就是静默丢信。
- 用 CNAME 指向 SPF 记录:SPF 要求 TXT,CNAME 不符合规范。
DKIM 正确配置
DKIM 用公钥密码学证明「邮件的指定头部与正文确实由持有私钥的一方签发,且中途未被修改」。公钥放在 选择器._domainkey.域名 的 TXT 记录里,私钥留在发信服务器或 ESP 侧。
选择器与密钥长度
选择器是一个命名空间,让你能在不中断签名的前提下换密钥:
; 2048 位 RSA 公钥,选择器 s1 与轮换用的 s2(先发布后启用)
s1._domainkey.mf8.biz. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
s2._domainkey.mf8.biz. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
选择器对接收方是透明的:签名头里的 s= 指向选择器,d= 指向域名。轮换流程是「生成 s2 → 发布 s2 公钥 → 切换签名器使用 s2 → 观察至少一个 DMARC 报告周期确认无 dkim=fail → 删除 s1」。反过来做会出现一个全网都无法验证的窗口。
RFC 6376 允许的最小密钥长度已不足以满足当前主流接收方要求,官方发件人指南普遍建议 2048 位 RSA。密钥越长,TXT 记录越大(2048 位约 400 字节),部分老旧解析器对超长 TXT 支持不佳,此时可用 ESP 托管签名。Ed25519 签名(RFC 8463)更短更快,但是否被接收方普遍支持需查官方文档。
规范化:relaxed 与 simple
DKIM 在计算哈希前要对头部和正文做规范化(canonicalization),因为传输会改动空行与折行:
- simple:几乎不做处理,保留原始空行与行尾空白,原始字节一变签名就失效。
- relaxed:折叠连续空白、去掉行尾空白、统一头部字段名大小写与折行,兼容性更好。
签名头里的 c=relaxed/relaxed 表示头部与正文都用 relaxed,绝大多数环境应当采用。c=simple/simple 只在两端完全可控时使用,经任意中继后失败概率显著上升。
正文哈希与转发为什么会破坏 DKIM
签名头里有两个关键标签:bh= 是正文哈希,b= 是头部与 bh 的签名值。只要正文被中间环节改动,bh 就不匹配,验证必然失败。常见改动来源:邮件列表追加页脚(退订说明、免责声明);邮件网关在正文插入消毒链接或警告横幅;自动转发规则重写 Received 与主题前缀;8bit 与 quoted-printable 之间的编码转换改变字节。
这解释了「自己发没问题、用户转发到 Gmail 就 dkim=fail」的现象。DKIM 无法防止转发导致的失效,这正是 DMARC 引入对齐并允许 SPF 独立通过的原因:转发场景下 SPF 常因中间主机 IP 而过不了,DKIM 又会因正文改动失败,稳健做法是让原始发信方严格对齐,并在报告中把 reason 的 forwarded 类型单独归类,而不是把所有失败都当成配置错误。
l= 标签允许只对正文前 N 字节签名(部分正文哈希),历史上被用来容忍列表页脚,但它等于允许正文被追加任意内容,现在多数接收方策略上不欢迎,不要主动使用。
读取签名头与常见 dkim=fail 原因
# 1) 列出邮件里所有 DKIM-Signature 的域、选择器与算法
grep -i '^DKIM-Signature' -A3 /path/to/msg-0001.eml \
| tr ';' '\n' | grep -Ei '(^|[[:space:]])(d|s|a|c|bh|b|h|x)='
# 2) 核对公钥是否真的发布(假设上面看到 d=mf8.biz s=s1)
dig +short TXT s1._domainkey.mf8.biz
# 3) 解出 p= 的 base64,确认它是一把结构合法的 RSA 公钥
dig +short TXT s1._domainkey.mf8.biz | tr -d '"' | sed 's/.*p=//' | base64 -d > /tmp/s1.pub.der
openssl pkey -pubin -inform DER -in /tmp/s1.pub.der -text -noout | head -5
# 4) 本机跑 OpenDKIM 时,用官方测试工具校验配置与密钥
opendkim-testkey -d mf8.biz -s s1 -vvv
dkim=fail 的常见原因按频率排列:选择器写错或已删除(s= 指向不存在的记录);轮换时旧记录被提前删除;d= 与 From 头域名不对齐导致 DMARC 判定失败(此时 DKIM 本身是 pass);正文被转发或网关修改;x= 签名过期时间已过;h= 未包含 From;私钥与已发布公钥不是一对(多台服务器各自生成密钥却只发布了一把)。读取 Authentication-Results 里的 header.d= 与 header.s= 可确认接收方实际使用的选择器。
DMARC 分阶段收紧
DMARC 建立在 SPF 与 DKIM 之上,回答的是另一个问题:邮件的 From 头域,是否与通过 SPF 或 DKIM 认证的那个域对齐。只有对齐的认证结果才算数,因此它能防住「用你的域名做信封、显示名伪造」的组合攻击。
记录语法
; 观测期:只收集报告,不改变投递行为
_dmarc.mf8.biz. TXT "v=DMARC1; p=none; rua=mailto:dmarc@mf8.biz; ruf=mailto:dmarc-forensic@mf8.biz; fo=1; adkim=r; aspf=r; pct=100; ri=86400"
; 收紧期:主域拒绝,子域先隔离
_dmarc.mf8.biz. TXT "v=DMARC1; p=reject; sp=quarantine; rua=mailto:dmarc@mf8.biz; adkim=r; aspf=r; pct=100"
关键标签:p 是对齐失败时的处置(none 仅报告、quarantine 进垃圾箱、reject 拒收);sp 是未单独发布 DMARC 的子域策略,默认继承 p,单独设为 quarantine 可在主域 reject 时给子域留缓冲;rua 是聚合报告地址,ruf 是取证报告地址(含原始邮件,涉及隐私,接收方支持程度不一);adkim/aspf 是对齐模式,r 为 relaxed(组织域相同即可),s 为 strict(必须完全一致);pct 是策略生效百分比;ri 是报告间隔。
rua 地址若指向外部域名(与发信域不同的域),接收方发送报告前会检查该外部域是否发布了授权记录,形如 你的域名._report._dmarc.外部域 的 TXT。漏掉这条授权,报告不会送达。
对齐:relaxed 与 strict
relaxed 对齐比较组织域(Public Suffix List 意义上的注册域),mail.mf8.biz 与 mf8.biz 视为一致;strict 要求 From 头域与认证域逐字符相同。绝大多数场景用 relaxed(默认值),只有能完全控制所有发信子域时才值得用 strict,否则一个子域配错就造成大面积失败。
分阶段上线
p=none 阶段的目标不是「什么都不做」,而是把报告读透:确认每个 source_ip 都认识、每个来源的认证结果都有 pass、不认识的来源是第三方代发(支付、CRM、工单系统)还是仿冒。给所有合法来源补齐 SPF include 与 DKIM 后,报告里的对齐通过率应接近 100%。
quarantine 阶段用 pct 灰度,例如先 p=quarantine; pct=10,观察退信量与投诉量,再逐步提到 25、50、100;pct 的作用范围与实现细节在不同接收方之间可能不同,官方文档是第一依据。最后才上 p=reject。
不能直接 p=reject 的原因:策略一旦生效,所有未对齐的合法来源都会被拒收,而这些来源往往是「你不知道自己有的」——网页表单插件、主机商 cron 发信、旧应用的 SMTP 配置、外包的邮件网关。它们被拒后不会给你明显报错,用户只会说「收不到验证码」。同时用户侧自动转发与邮件列表在 reject 下更容易丢失,这是 DMARC 设计上承认的代价,需要提前准备用户侧说明与替代流程。
读一份聚合报告
# 报告是 gzip 压缩的 XML,先确认能解析出策略与记录
zcat ~/dmarc/reports/*.xml.gz | xmllint --format - | head -60
# 统计“SPF 与 DKIM 都未通过”的来源 IP 及其邮件量
python3 - <<'PY'
import gzip, glob, xml.etree.ElementTree as ET
from collections import Counter
bad = Counter()
for f in glob.glob('dmarc/reports/*.xml.gz'):
for r in ET.parse(gzip.open(f)).getroot().iter('record'):
dkim = r.findtext('row/policy_evaluated/dkim')
spf = r.findtext('row/policy_evaluated/spf')
if dkim != 'pass' and spf != 'pass':
bad[r.findtext('row/source_ip')] += int(r.findtext('row/count') or 0)
for ip, n in bad.most_common(15):
print(ip, n)
PY
读报告要区分四种情况:SPF 与 DKIM 都 pass 且对齐,正常;只有 DKIM pass,通常是转发或信封域未对齐;只有 SPF pass,说明签名缺失或选择器错误;两者都 fail,是陌生来源或配置彻底错误。再结合 row/policy_evaluated/reason 的 type 值(forwarded、local_policy、sampled_out)判断哪些属于可接受的例外。报告开头的 policy_published 会回显接收方实际读到的策略,可用来确认 DNS 变更是否生效。
自建还是代发
| 维度 | 自建 MTA(Postfix + OpenDKIM/OpenDMARC) | 第三方 ESP 代发 |
|---|---|---|
| 初始成本 | 只需一台已有服务器,但需要配置与调优时间 | 通常按量计费,具体价格以服务商官方定价为准 |
| IP 信誉 | 完全自己承担,同 IP 上其他站点的历史行为会直接拖累你 | 共享 IP 由服务商维护,独立 IP 仍需自行预热 |
| 认证配置 | 自己生成密钥、发布 SPF/DKIM/DMARC,责任清晰 | SPF include 与 DKIM 选择器由服务商提供,仍需在自己域名下发布 |
| 可观测性 | 全量 SMTP 日志本地留存,可做任意维度分析 | 依赖服务商面板与 webhook,日志粒度受限 |
| 退信与重试 | 自己实现队列、退避、退信解析与抑制名单 | 服务商内置抑制名单,但可能屏蔽你需要重试的地址 |
| 内容与隐私 | 邮件正文不经过第三方 | 正文与收件人列表经服务商处理,需评估合规要求 |
| 上手速度 | 慢,需要处理 PTR、HELO、端口 25 出网与黑名单申诉 | 快,DNS 记录加完即可发信 |
| 适合场景 | 事务邮件为主、量不大、已有运维能力且需要完整日志 | 中小站点、事务与营销混合、无人专职盯日志 |
| 不适合场景 | 发信量大却无信誉监控体系;邮件是核心业务却没有冗余链路 | 有强数据驻留要求;需要精细控制重试与投递路径 |
常见的折中是:事务邮件走自建 Postfix(或轻量 MTA)以拿到完整日志与最小的第三方依赖,营销邮件交给 ESP,两者使用不同子域、不同 DKIM 选择器、不同 DMARC 子域策略。这样即使 ESP 侧出现信誉问题,密码重置邮件依然能到达。
另一个决定项是共享 IP 与独立 IP。共享 IP 让服务商替你承担信誉维护成本,但你无法控制同池邻居;独立 IP 信誉独享,代价是从零预热:按接收方官方建议,从很小的日发送量开始,按天或按周阶梯式增加,并优先把互动率最高的事务邮件放到新 IP 上,用真实的打开与回复建立信誉。预热曲线与阈值各家不同,必须以官方说明为准。
对大多数中小站点,理性的选择就是 ESP:一台按月付费的小主机不值得为几十封/天的邮件承担 IP 信誉、PTR、退信解析和黑名单申诉的全部工作,而这些工作一旦缺失,损失的是注册转化。
容易被忽略的关键项
PTR / rDNS 与 HELO 匹配。发信 IP 必须配置反向解析,且正反解析互相印证(FCrDNS);HELO/EHLO 主机名应当是能正向解析回该 IP 的 FQDN,而不是 localhost 或裸 IP。很多接收方在会话初期就做这个检查,不通过会被限速或拒绝。
# 反向解析与正向解析必须对得上
dig -x 47.98.123.45 +short
dig +short A mail.mf8.biz
# 两者应互相指向;HELO 名称建议与 mail.mf8.biz 一致
PTR 通常只能在 IP 提供商的云解析控制台或工单系统里设置,DNS 服务商改不了,这是自建发信最常见的卡点。
MTA-STS 与 TLS-RPT(概念层)。MTA-STS(RFC 8461)让发信方在投递前强制校验接收方的 TLS 证书,防止降级到明文:在 _mta-sts.你的域名 发布 TXT 策略标识,并在对应的 well-known HTTPS 路径提供策略文件。TLS-RPT(RFC 8460)通过 _smtp._tls.你的域名 收集 TLS 失败报告。二者都是接收侧增强,对「你发给别人」的送达率没有直接作用,但如果你自己也在收信,它们能降低被中间人降级的风险。注意那个 HTTPS 端点需要有效证书,签发通常走 ACME,此时 CAA 记录就会生效。
CAA(如相关)。CAA(RFC 8659)限制哪些 CA 可为你的域名签发证书。如果 DNS 里已有 CAA 且只为特定 CA 授权,给 mta-sts 主机申请 ACME 证书时可能被拒绝。新增 mta-sts 子域或更换 CA 之前,先确认 CAA 是否覆盖;Let’s Encrypt 160 小时与 IP 地址证书实战:Certbot 自动续期和监控 讲了短周期证书的自动化,可配合检查。
退订处理与投诉率。批量邮件必须提供可见退订入口,并按 RFC 8058 支持 List-Unsubscribe 与一键退订(List-Unsubscribe-Post: List-Unsubscribe=One-Click)。退订请求要实时生效,或按官方规定的最短时限内生效。投诉率是域名信誉最直接的消耗项,主流服务商发件人指南都给出了明确要求,具体数值与适用范围以官方文档为准;一旦接近上限,正确反应是降低发送频率、收缩收件人范围,而不是继续加量。
名单质量与再激活。只发送给有明确获取来源的地址,表单加双重确认可显著减少垃圾陷阱地址。对 6 至 12 个月无互动的订阅者,先发一轮再激活邮件,仍未互动就停止发送。硬退信地址永久抑制,abuse@、postmaster@ 这类角色地址单独处理。
从“进垃圾箱”到定位:排查顺序
按下面顺序排查,每一步都有明确判据,不要跳步去改内容。
- 先看 DMARC 聚合报告。确认同一时间段是否存在对齐失败的大批量来源。有,就去第 2 步定位是认证还是来源问题;没有,说明认证链路正常,问题更可能在信誉或内容,跳到第 4 步。
- 看接收方回的认证结果头。让能收到邮件的测试邮箱导出原始邮件(.eml),读取 Authentication-Results。这一行由接收方写入,是最权威的认证判定,比任何自测工具都可靠。
- 在线核对 DNS 记录。用 dig 查 SPF、DKIM 选择器、_dmarc,确认记录存在、选择器与签名头一致、SPF 查询次数未超限。注意 DNS 缓存与 TTL:改完记录至少等一个 TTL 周期再复测。
- 查 PTR 与信誉。核对反向解析与 HELO 一致性,通过各黑名单官方查询页面确认 IP 是否在列,并确认服务器出口 IP 是否被云厂商 NAT 成共享地址。
- 再查内容与链接。检查是否使用短链、链接域名与显示文本是否一致、是否图片超大而正文极少、HTML 是否合法闭合、是否缺少 Date、Message-ID、MIME-Version 等必需头部。
- 最后查投诉与退信率。结合接收方面板的投诉率、硬退信比例与退订率,判断是否需要收缩发送范围并清洗名单。
Authentication-Results 的读法(字段示意):
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of bounce@mail.mf8.biz designates 47.98.123.45 as permitted sender) smtp.mailfrom=bounce@mail.mf8.biz;
dkim=pass header.i=@mf8.biz header.s=s1 header.b=Xk3pQ1ab;
dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=mf8.biz
逐段解读:spf=pass 括号里是接收方认为的授权来源与 smtp.mailfrom(信封域)。dkim=pass 后面的 header.i=@mf8.biz 是签名域、header.s=s1 是选择器——若这里的 s= 与你在 DNS 发布的选择器不一致,说明签名器仍指向旧选择器。dmarc=pass 括号里的 p= 与 sp= 是接收方实际读到的策略,可验证 DNS 变更是否生效;header.from= 是 From 头域。若看到 dkim=fail 且带 header.s=,先查该选择器 TXT 是否存在;若看到 spf=fail 但 dmarc=pass,说明 DKIM 对齐兜住了,属于转发场景,不必急着改配置。
上线检查清单
- SPF:全域名下只有一条 v=spf1 TXT,包含所有真实发信出口(含 IPv6)
- SPF:DNS 查询次数实测不超过 10 次,无 void lookup 超限,已纳入季度检查
- SPF:末尾限定符与盘点进度匹配(盘点期 ~all,确认完整后改 -all)
- DKIM:至少 2048 位 RSA,c=relaxed/relaxed,选择器 TXT 可解析且 p= 非空
- DKIM:d= 域与 From 头域对齐模式已明确选定,并用实际邮件验证过
- DKIM:保留两个选择器,轮换按「先发新公钥 → 切换签名 → 观察一个报告周期 → 删旧」
- DMARC:_dmarc 记录已发布,rua 可收信;指向外部域时授权 TXT 已发布
- DMARC:p=none 观测至少 2 至 4 周,报告中所有 source_ip 已确认为自有来源
- DMARC:用 pct 灰度收紧,主域与 sp= 子域分别评估后再上 p=reject
- 身份:事务与营销邮件使用不同子域、不同 DKIM 选择器、不同 IP 或不同 ESP
- 网络:发信 IP 有 PTR,正反解析一致,HELO/EHLO 为可回解 FQDN
- 观测:SMTP 日志保留 14 天以上,业务库记录队列 ID,已接入接收方发件人面板
- 名单:硬退信永久抑制,软退信按退避重试,批量邮件提供一键退订入口
- 内容:无短链、无图文严重失衡、必需头部齐全、链接文本与实际目标一致
- 回滚:变更前已备份旧 DNS 记录原文;DKIM 出问题可切回旧选择器
- 回滚:DMARC 大面积失败时可立即把 p 改回 none,并保留 rua 继续收集
官方资料与继续阅读
外部官方文档:
- RFC 7208 — Sender Policy Framework (SPF) for Authorizing Use of Domains in Email:https://datatracker.ietf.org/doc/html/rfc7208
- RFC 6376 — DomainKeys Identified Mail (DKIM) Signatures:https://datatracker.ietf.org/doc/html/rfc6376
- RFC 7489 — Domain-based Message Authentication, Reporting, and Conformance (DMARC):https://datatracker.ietf.org/doc/html/rfc7489
- dmarc.org — DMARC 官方介绍与部署资源:https://dmarc.org/
- Google 发件人指南(Gmail 批量发信与垃圾邮件政策):https://support.google.com/mail/answer/81126
- Microsoft 发件人支持与 SNDS(Outlook 投递与信誉数据):https://sendersupport.olc.protection.outlook.com/pm/
站内相关文章:
- Let’s Encrypt 160 小时与 IP 地址证书实战:Certbot 自动续期和监控 — 短周期 TLS 证书自动化,与 MTA-STS 端点证书、CAA 配置直接相关
- WordPress 7.1 生产升级指南:新功能、兼容性检查与安全回滚 — WordPress 生产升级流程,升级后需回归验证发信插件与 SMTP 配置
- 网站切换服务器IP,如何快速快速刷新DNS以获得测试? — DNS 缓存与 TTL 排查,改完 SPF/DKIM/DMARC 记录后用来确认解析已生效
- 深入使用云监控并创建一个云监控钉钉机器人 — 用云监控告警覆盖发信队列积压、退信率与投递延迟


