环境:Typecho 1.3.0 + nexmoe 主题 + CommentNotifier 1.8.3,虚拟主机(美国机房,PHP 7.4)
时间:2026-10-06 夜
一、起因
博客评论通知一直没配成功。现象很明确:
- 在插件后台点「测试发信」→ 能收到邮件
- 在文章下发一条评论 → 收不到任何邮件
同一个插件、同一套 SMTP 配置,一个通一个不通。这说明问题不在「账号密码对不对」,而在「两条发信路径不一样」。
二、排查:先看配置,再动手
2.1 用一个只读诊断脚本读出真实配置
插件的配置存在数据库的 plugin:CommentNotifier 里,从后台界面只能看到表单,看不到原值。写一个一次性 PHP 脚本上传到主题目录,用 HTTP 访问拿输出,读完立刻删除。
脚本要点:
- 用
SHOW TABLES自动识别表前缀,不要用正则猜config.inc.php - 敏感字段(密码、授权码)打码输出
- URL 带随机 token,只读不写
- 用完立即删除,并访问一次确认返回 404
读回来的关键配置:
from = 2404999487@qq.com ← 之前填成了 smtp.qq.com,用户已自行改正
fromName = 苏的碎碎念
STMPHost = smtp.qq.com
SMTPSecure = ssl
SMTPPort = 465
SMTPUserName = 2404999487@qq.com
template = Wechat
yibu = 1 ← 异步提交:开着同时测了 SMTP 端口连通性:465 / 587 / 25 全部可通,主机没有封发出站端口,授权码也有效(235 Authentication successful)。
2.2 找到可疑点:异步提交
翻插件源码,发信入口是这样分的:
if ($plugin->yibu == 1) {
Helper::requestService('send', $param); // 异步:交给 Typecho 的服务机制
} else {
self::send($param); // 同步:当场直发
}测试邮件走的是同步路径(由浏览器发起 {siteUrl}/zemail?...),而真实评论走的是异步路径。
2.3 异步路径到底做了什么
Helper::requestService() 最终会注册一个 responder,在本次请求响应结束之后,由 服务器自己 去请求:
POST {siteUrl}/action/service?do=async而这段代码里有一句致命的处理:
} catch (\Exception $e) {
return; // 出错就静默返回,不记录、不提示
}也就是说:只要服务器访问不了自己,这封邮件就会悄无声息地消失,你在任何地方都看不到报错。
2.4 验证:服务器能不能访问自己
在诊断脚本里加一段,让服务器用 siteUrl 请求自己:
服务器自访问 siteUrl → HTTP 0
错误: Certificate key usage inadequate for attempted operation证书校验失败,服务器无法通过 HTTPS 访问自己的域名 —— 这正是异步请求失败的原因。
匹配现象:
| 场景 | 谁发起请求 | 结果 |
|---|---|---|
| 测试邮件 | 浏览器 | ✅ 正常 |
| 评论通知(异步) | 服务器自己 | ❌ SSL 失败 → 静默丢弃 |
三、修复
把「异步提交」关掉,改回同步直发(就是测试邮件走的那条链路):
yibu = 0代价是评论提交时会多等 SMTP 一两秒,但换来的是确定性:失败会报错,不再静默丢失。
一句话结论:Typecho 插件的「异步提交」在虚拟主机上经常坏,因为它依赖服务器访问自己的域名。
遇到「测试邮件正常但评论通知收不到」,第一个要查的就是这个开关。
四、附带问题:发件人显示成了真名
用户反馈:邮件能收到,但收件箱里显示的发件人是自己的真名,不是配置里写的「苏的碎碎念」。
4.1 先确认到底是谁的问题
不猜,直接实测。方法:
- 用免费临时邮箱服务
mail.tm的 API 建一个测试邮箱 - 手写 SMTP 会话(Node 的
tls.connect连smtp.qq.com:465),从用户的 QQ 邮箱发一封测试信 - 读临时邮箱收到的原始邮件头
const msg = [
'From: ' + enc('苏的碎碎念') + ' <2404999487@qq.com>',
'To: <' + to + '>',
'Subject: ...',
...
].join('\r\n');注意:curl走--url smtps://时容易卡在 AUTH 阶段(返回 334),不如手写会话可控。
4.2 结果
"from": { "address": "2404999487@qq.com", "name": "苏的碎碎念" }邮件头里的发件人显示名完全正确。
4.3 结论
问题不在插件配置,在 QQ 邮箱自己的显示规则:
当你收到「由你自己的账号发出」的邮件时,QQ 邮箱会用你账号的邮箱昵称来显示,而不是邮件头里的 From 名字。改法:QQ 邮箱 → 设置 → 账号 → 发件人姓名,改成想要的名字。
五、顺手修的:邮件模板预览页
后台的模板预览页有两个毛病:灰底只铺了一半、两个头像都是裂图。
原因是 template/preview.php 里有它自己的一套占位符替换表,而模板里新加的 {avatar}、{Pavatar}、{Phide} 不在里面,于是被原样输出成了 src="{avatar}"。
修复:
- 给预览页的替换表补上这三个占位符,头像用模拟 QQ 号(
12345/123456)生成地址 - 给
<body>加background-color:#ededed; margin:0; min-height:100vh,让灰底铺满
六、重做文章目录树
原版主题自带的右侧目录有几个问题:position: fixed; top:0; height:100vh,从屏幕最顶端开始,且没有任何媒体查询 —— 手机上照样显示、直接盖住正文。
新做的目录树:
- 按正文里的
h1~h4自动生成层级结构,圆点 + 竖连线 + 层级缩进 - 点击平滑跳转,滚动时自动高亮当前小节
position: fixed固定在文章右侧,不随正文滚动
踩坑记录
坑 1:element.style.display = '' 不等于「显示」
wrap.style.display = ''; // ✗ 只是清掉了内联样式,反而回落到 CSS 里的 display:none
wrap.style.display = 'block'; // ✓ 必须显式写当时目录结构已经正确生成(21 个链接、4 层嵌套、无报错),但页面上就是看不见,就是这一行的锅。
坑 2:文章宽度固定,屏越窄右侧余量越小
一开始用固定 200px 宽的目录 + 1280px 的隐藏阈值,结果 1366 / 1440 的笔记本全都看不到。要改成按可用空间自适应宽度。
七、经验总结
- 「测试能过、实际不行」= 两条路径不一样。先找出两者分别走哪条代码路径,而不是反复改配置。
- 失败被静默吞掉是最难查的 bug。看到
catch { return; }就要警惕。 - 别靠猜,用只读脚本把真实配置读出来。后台界面显示的和数据库里存的不一定一回事。
- 改数据库前必须先备份原值。这次给插件停/启用时,第一版脚本没先
Plugin::init()就写回,把插件列表写空了,靠提前存的备份才恢复回来。 - 临时脚本用完立即删,并访问一次确认 404。
- 发件人显示名这类问题,用临时邮箱读原始邮件头,一次就能分清是「配置错」还是「收件方行为」。
0 评论