本文约 3,579 字,预计阅读 9 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:「已发送」三个字,掩盖了整条链路的失败
WordPress 的很多关键动作都靠邮件完成:密码找回、评论通知、订单确认、表单线索。一旦发信出问题,前台往往只给一句「邮件已发送」,用户却什么都没收到。这时候如果只看插件界面,你会陷入无止境的猜测——是服务器问题?是收件箱问题?还是被判成垃圾邮件?
邮件日志插件就是把这条黑盒链路打开的工具:它记录每一封邮件的收件人、主题、发送时间、使用哪个连接、成功还是失败、失败原因是什么。有了这些信息,排错从「猜」变成「查」。目前常见的选择有三类:WP Mail SMTP 的 Email Log(高级版功能)、FluentSMTP 自带的邮件日志(免费版即可用)、以及独立的 WP Mail Logging / Email Log 插件(与发信方式解耦,任何 SMTP 配置下都能记录)。
原理分析:一封邮件会经过哪些关卡
| 关卡 | 失败表现 | 日志里能看到什么 |
|---|---|---|
| 本地生成 | 插件未调用 wp_mail() |
根本没有记录 |
| SMTP 认证 | 535 / Invalid login | 认证错误,凭据或用户名格式问题 |
| 建立连接 | Connection timed out | 端口不通、出网被限制 |
| 对方接收 | 550 / 554 | 退信内容含对方服务器的原因说明 |
| 投递到收件箱 | 无报错但用户看不到 | 进了垃圾箱,SPF/DKIM/DMARC 问题 |
理解这张表后,日志的价值就清晰了:它能立刻把问题定位到某一关。认证失败就去查凭据,连接失败就去查出网策略,收到 550 就去读对方的退信说明——而不是盲目地换插件、换端口、换服务商。
需要注意,WordPress 侧的日志只能记录到「本站点把邮件交给了 SMTP 服务」这一步。之后的投递过程发生在对方服务器上,站点日志看不到;要看那一段,得靠服务商的后台(SendGrid、Mailgun 等都提供投递状态与退信详情)。所以完整的排查是两侧日志对照:站点日志确认「发出去了」,服务商日志确认「对方收下了」。
保姆级实操:装上日志,按图索骥
步骤1:选择并安装日志方案。 已经用 FluentSMTP 的站点直接用它的日志;用 WP Mail SMTP 的站点需要其高级版的日志功能;都不想依赖发信插件的,装独立的 WP Mail Logging 或 Email Log。注意别同时装两个,会出现重复记录。
步骤2:开启日志并确认记录内容。 发一封测试邮件,然后到日志页确认能看到:收件人、主题、时间、状态、使用的连接。若日志为空,说明插件没接管到发信,检查是否有其他插件覆盖 wp_mail()。
步骤3:用代码补一层兜底日志。 插件日志存在数据库里,数据库本身出问题时反而看不到。挂一个轻量钩子把失败信息写进 PHP 错误日志,双保险。
<?php
// 记录每封邮件的收件人与主题(不含正文,避免记录敏感信息)
add_filter( 'wp_mail', function ( $atts ) {
error_log( sprintf(
'[mail] to=%s subject=%s',
is_array( $atts['to'] ) ? implode( ',', $atts['to'] ) : $atts['to'],
$atts['subject']
) );
return $atts;
} );
// 失败时记录原因
add_action( 'wp_mail_failed', function ( $wp_error ) {
error_log( '[mail-failed] ' . $wp_error->get_error_message() );
} );
步骤4:统一邮件格式,减少被判垃圾的概率。 纯文本与 HTML 混用、缺少 Content-Type 声明,都会影响评分。
<?php
// 统一为 UTF-8 文本邮件(需要 HTML 邮件时改为 text/html)
add_filter( 'wp_mail_content_type', function () {
return 'text/plain; charset=UTF-8';
} );
add_filter( 'wp_mail_from_name', function ( $name ) {
return '站点名称';
} );
步骤5:从日志里统计失败原因。 把失败记录按错误文本分组,一眼看出是认证问题还是投递问题。
-- 统计邮件日志表中的失败记录(表名与字段以实际插件为准)
SELECT LEFT(error_message, 60) AS reason, COUNT(*) AS cnt
FROM wp_fsmtp_email_logs
WHERE status = 'failed'
GROUP BY LEFT(error_message, 60)
ORDER BY cnt DESC
LIMIT 10;
步骤6:对照退信码处理。 常见 SMTP 响应码的含义与处理方向如下:
535 / 530 认证失败 —— 用户名必须是完整邮箱地址,密码用客户端专用密码
550 收件人被拒 —— 常见「Mailbox not found」(地址不存在)
或「5.7.26 Authentication required」(SPF/DKIM 未通过)
553 发件人地址不合法 —— 发件人与认证账号不一致
554 被判为垃圾邮件 —— 检查内容、发信域名声誉与 DNS 记录
421 / 450 服务端暂时不可用 / 邮箱忙 —— 队列重试即可,持续出现需联系服务商
步骤7:检查服务器本地邮件队列。 若站点使用本地 Postfix 发信,日志里看不到退信时,去队列里找。
# 查看邮件队列
postqueue -p
# 查看最近的邮件日志
tail -n 50 /var/log/maillog 2>/dev/null || tail -n 50 /var/log/mail.log
# 检查域名 MX 与 SPF 记录
nslookup -type=MX example.com
nslookup -type=TXT example.com
步骤8:建立长期监控。 给关键邮件(订单、线索)加一条每日统计:若某类邮件发送量为 0 而平时有量,立即告警。日志只有被定期看才有价值。
常见踩坑
- 日志插件与 SMTP 插件冲突。两个插件都尝试接管
wp_mail(),导致邮件重复发送或一方不生效。只保留一套发信与日志方案。 - 日志表无上限增长。邮件日志会持续写入数据库,一年下来可能几百 MB。设置保留期(如 30 天)并定期清理,清理思路与Advanced Database Cleaner 清理冗余数据一致。
- 日志里记录了完整正文,造成信息泄露。联系表单的内容、密码重置链接都在邮件正文里。日志插件若存正文,等于在数据库里又存了一份个人信息,需要纳入留存管理,参考表单数据存储与导出(GDPR 考量)。
- 只看站点日志,不看服务商后台。站点日志显示「已发送」,但对方根本没收下。两侧要对照。
- 测试邮件成功,真实邮件失败。测试邮件内容简单,真实邮件带了外链或附件被判垃圾。用真实场景再测一次。
- 案例:某站点的订单确认邮件长期丢失,站点日志一切正常。转到服务商后台才发现大量
550 5.7.26 This mail is unauthenticated退信,原因是域名迁移后 SPF 记录没同步,而 DKIM 密钥根本没配。补上 TXT 记录后送达率从不足 40% 回到 99%。这个案例说明:站点日志只能证明「我发了」,不能证明「对方收了」。 - 把密码写进日志。调试时把 SMTP 密码打印到 error_log,日志文件被其他用户读取。日志只记状态与原因,不记凭据。
- 邮件队列堆积无人察觉。本地 Postfix 队列积压几千封,站点前台毫无提示。需要监控队列长度。
总结
排查收不到信的正确姿势是:先装日志定位关卡(本地生成 / 认证 / 连接 / 对方接收 / 投递),再按关卡查具体原因,最后用服务商后台交叉验证。退信码是最直接的线索,务必读全文本而不是只看数字。长期来看,给关键邮件建立「每日发送量」监控,比事后救火有效得多。SMTP 接入参数本身的正确填法,见WP Mail SMTP / FluentSMTP 插件详细配置。
