很多人对邮件送达的理解停留在「SMTP 发出去就算成功」这个层面,直到某一天发现注册验证码进不了用户收件箱、或者自己发给客户的报价单全部落进垃圾箱,才开始正视这件事。实际情况是:一封邮件从你的服务器出去,要经过对方网关的一连串自动判定,其中任何一个环节不信任你,它就被静默丢弃或降级。投递失败几乎不会给你报错,这才是它难的地方。
这篇写的是自建站最常见的场景:站点跑在自己服务器上,需要用 PHP 或脚本发注册验证、订单通知、密码重置这类事务邮件。不涉及群发营销,也不推荐你自建全功能邮件服务器——那是另一件成本高得多的事。下面这四层,是我帮人排查「验证码收不到」时实际用的顺序。
一、先分清三种「没收到」
用户说没收到,可能是三件完全不同的事,处置方式也完全不同,所以第一件事永远是分流。
一是被对方服务器拒绝。这时你的发信日志里会立刻出现 4xx 或 5xx 返回码,比如 550 5.7.1 rejected。二是被接收成功但投进了垃圾箱。日志显示 250 正常接收,但用户在主收件箱里看不到。三是根本没发出去。PHP 的 mail() 返回 true 但邮件卡在本地队列、或者压根被本机策略拦截。
判据只有一条:看发信端的完整会话日志,不要看应用层的「发送成功」提示。PHP 的 mail() 返回值只代表「交给了本地 MTA」,连队列都还没出。下面的命令能拿到真实返回码:
# Postfix 场景:查最近发出的邮件与对方的响应
tail -n 200 /var/log/mail.log | grep -E "status=(sent|bounced|deferred)"
# 查某封具体邮件为什么被拒
grep "to=<user@example.com>" /var/log/mail.log | grep -E "reject|defer"
# 查看队列里积压了什么
mailq | head -40status=sent 表示接收方 250 确认,但只说明「进了对方的收件系统」,不代表进了收件箱。status=deferred 是临时失败在重试,常见于对方限流或你的 IP 被临时列入灰名单。status=bounced 是永久失败,必须看 reason 里的 5xx 详情。
二、IP 信誉:比你想象的更决定成败
如果你用的是共享云主机,很可能这个 IP 在你之前已经被别人用滥过。IP 信誉是一个缓慢修复、瞬间崩塌的资产。判断方法有两条,都不需要付费:
# 1. 查 IP 是否在任何主流黑名单上(RBL)
IP=203.0.113.10
REV=$(echo $IP | awk -F. '{print $4"."$3"."$2"."$1}')
for bl in zen.spamhaus.org bl.spamcop.net b.barracudacentral.org; do
host "$REV.$bl" >/dev/null 2>&1 && echo "$bl: LISTED" || echo "$bl: clean"
done
# 2. 看 IP 的 PTR 反向解析是否与域名匹配
dig +short -x $IP第一条 zen.spamhaus.org 命中是最致命的:大量接收方直接用它做拒绝依据,你的邮件连垃圾箱都进不去。host 返回 NXDOMAIN 说明干净,返回 127.0.0.x 说明被列(后面的数字代表列入原因)。
第二条 PTR 常被忽略。很多接收方会检查「声称是从 mail.example.com 发来的连接,其 IP 的反向解析是否也指向 example.com 或同域」。云厂商默认的 PTR 往往是 ec2-x-x-x-x.compute.amazonaws.com 这类,与你的域名毫无关系,这在反垃圾评分里是扣分项。多数云厂商支持在控制台或工单里设置弹性 IP 的 PTR,值得花十分钟配置。
如果 IP 已经被列入,处置顺序是:先解决导致被列的原因(通常是开放中继或脚本被注入发垃圾),再提交移除申请。不修原因就申诉,几天后会被重新列入,这是最常见的无效努力。
三、SPF、DKIM、DMARC:三条记录各管一件事
这三条 DNS 记录是投递率的地基,但它们经常被混为一谈。用一句话分清:SPF 管「谁有资格用我的域名发信」,DKIM 管「这封信有没有被改过」,DMARC 管「上面两条都没通过时怎么办,以及把结果汇报给我」。
SPF 是一段 TXT 记录,列出允许代表你发信的 IP 或服务器:
# 名称: @ 类型: TXT
v=spf1 ip4:203.0.113.10 include:_spf.example-mail-provider.com -all末尾的 -all 表示「不在列表里的一律判失败」(Hard Fail),~all 是软失败(标记但不拒收)。新配置时建议先用 ~all 观察两周,确认所有正常发信源都覆盖到了,再改成 -all。很多人一上来就写 -all,结果某个还在用的第三方服务被误杀,用户收不到通知,排查半天才发现是自己禁的。
还有两个硬性限制要记住:一个域名只能有一条 SPF 记录(多条会直接导致 SPF 判定为 permerror,等于没有);SPF 的 DNS 查询次数上限是 10 次,每多一个 include: 就消耗若干次,超限同样判为 permerror。这也是为什么「把所有第三方都 include 进来」的做法会在中途崩掉。
DKIM 是签名机制,私钥在你手里,公钥放在 DNS 里。用 OpenDKIM 或者你所用发信服务的后台生成,然后把公钥记录发布出去:
# 名称: default._domainkey 类型: TXT
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ...DKIM 的核心价值在于它不依赖 IP:即使你的邮件经过转发、经过邮件列表,只要签名还完整,接收方仍能验证这封信确实来自你。这也是为什么转发场景下 DKIM 比 SPF 更有用——转发的信源 IP 变了,SPF 必然失败,但 DKIM 通常还能过。
DMARC 是策略层,同时提供了一个汇报机制,让你第一次能「看到」别人怎么评价你的邮件:
# 名称: _dmarc 类型: TXT
v=DMARC1; p=none; rua=mailto:dmarc@example.com; ruf=mailto:dmarc@example.com; pct=100; adkim=s; aspf=sp=none 是只观察不处置,配合 rua 收集汇总报告。跑两到四周,看看报告里有哪些 IP 在用你的域名发信、通过率如何,再逐步升级到 p=quarantine、最后 p=reject。跳过观察期直接上 reject,是把自己站点的通知邮件全部干掉的最快方式。
DMARC 报告是 XML 附件,直接看很痛苦。建议用第三方解析工具(很多免费)汇总成「每天每来源的通过情况」表格,一眼就能看出是否有未知来源在冒用你的域名。
四、事务邮件的三个工程细节
上面三条记录解决的是「能不能送达」,下面三条决定「送达率稳不稳」。
细节一:返回路径与 From 域要一致。很多脚本用 noreply@example.com 做 From,但服务器实际以 www-data@localhost 身份投递,Envelope From 是 root@vps-hostname。SPF 校验看的是 Envelope From 的域,于是永远失败。修法是在发信时显式指定 envelope sender,PHP 里对应 mail() 的第五个参数 -f:
// 错误:Envelope From 会变成 www-data@localhost
mail($to, $subject, $body, $headers);
// 正确:-f 指定 envelope sender,与 From 域保持一致
mail($to, $subject, $body, $headers, '-fnoreply@example.com');更稳妥的做法是完全不用 mail(),改用 SMTP 客户端库(如 PHPMailer)通过认证连接你的发信服务。这样信封、认证、TLS 都在一层里解决,日志也更好查。
细节二:事务邮件走专线,不要和批量邮件共用 IP。注册验证码、密码重置这类邮件的时效性极强,用户等三十秒没收到就放弃注册了。如果和通知类、订阅类邮件共用同一个发信通道,一次批量发送的投诉率上升会把整个 IP 的评分拉下来,连验证码一起受影响。规模够的话,用独立子域(如 mail.example.com)承担事务邮件,另一个子域承担其他类型,各自独立设置 SPF/DKIM/DMARC。
细节三:内容里别出现高风险的组合。反垃圾引擎看重「文本 + 链接 + 图片」的组合特征。一封只有一张大图、正文只有一个短链接、还带跟踪参数的邮件,即使你完全合规,评分也低。事务邮件的最佳实践是:一段说明文字 + 一个明确的按钮链接 + 一个大致的失效时间说明,不要加无关的营销图片和多个外链。
五、用一个 10 分制的自查表收口
每次新建站点或更换发信供应商,我都会跑一遍下面这张表。十分制,低于八分不要上线。
SPF 记录存在且只有一条 +2
SPF 内 DNS 查询次数不超限 +1
DKIM 公钥已发布且验证通过 +2
DMARC 记录存在(p=none 起步) +1
PTR 指向发信域名同域 +1
IP 不在主流 RBL 上 +2
Envelope From 与 From 域一致 +1
--------------------------------
合计 10 分,低于 8 分先别发信验证 SPF 与 DKIM 是否正确,最直接的方法是真发一封到 check-auth@verifier.port25.com 这类验证地址,它会自动回信并逐项告知 SPF、DKIM、SpamAssassin 的判定结果。这比反复猜、反复改记录快得多。
六、别自建完整邮件服务器
最后说一个方向性的判断。自建收发一体的邮件服务器(Postfix + Dovecot + 全套反垃圾)在技术上可行,但在实践中极度不划算:你需要长期维护 IP 信誉、处理各种反垃圾反馈、应对客户端的兼容问题,而这一切对一个内容站来说没有任何产出。合理的分工是——收信可以用自建或者直接用域名邮箱服务;发信一律走成熟的 SMTP 服务商,自建服务器只做「转发给服务商」这一层。
这样你的 SPF 只需要 include 服务商的记录,DKIM 由服务商签名,DMARC 照样由你自己控制策略。投递率交给专业方,域名信誉留在自己手里,这是目前自建站最省心的组合。
七、小结:顺序错了,努力就白费
排这个问题一定要按顺序走:先看发信日志分清是哪一类没收到,再查 IP 信誉与 PTR,然后核对三条 DNS 记录,最后才动内容和通道。反过来做——先反复改邮件模板、换字体换排版——你会发现怎么改都没用,因为问题从来不在内容上。
还有一个心态上的提醒:投递率不是一个能「一次配好、永久有效」的东西。IP 信誉会变,反垃圾规则会升级,DNS 记录的解析商也可能在某次迁移中丢掉你的 TXT 记录。把上面那张自查表放进季度检查清单,比出事之后再救火便宜得多。