“模板明明改了,客户收到的还是旧版”听起来像缓存问题,但直接清全站缓存往往只是碰运气。异步邮件多了一段时间差:下单发生在 T1,邮件真正渲染和发送可能在 T2。要解决它,必须先确认系统在 T1 保存了什么、在 T2 又读取了什么。
先做一个不会误导你的测试
选一个只在新模板中出现的短语,例如“售后编号”,不要用颜色或页脚年份这种可能被邮件客户端缓存的元素。然后完成三次发送:
- 直接从后台发送模板预览;
- 创建一张新订单并立即处理队列;
- 处理一张修改模板之前就已进入队列的旧订单。
如果预览都是新的,但新订单邮件仍旧,问题在 Store 配置或发送进程;如果只有旧队列任务使用旧内容,要检查队列是否保存了已经渲染好的正文。不要把三种结果混在一起。
Magento 内置销售邮件与自定义队列的行为可能不同
Magento 的销售邮件异步发送主要以订单/发票等实体的发送状态驱动,发送时再根据 Store 配置选择模板。很多第三方模块为了重试方便,会提前渲染主题和 HTML,把整封邮件作为队列消息保存。前者改模板后通常影响尚未发送的邮件,后者不会,因为旧 HTML 已经成为历史快照。
所以第一件事是查清实际发送类。搜索自定义模块是否把 TransportInterface、模板正文或序列化后的消息写入表或消息队列:
grep -RniE "setTemplateIdentifier|TransportBuilder|message_queue|email_body" app/code vendor/YourVendor
php bin/magento config:show sales_email/general/async_sending
php bin/magento config:show sales_email/order/template
php bin/magento config:show sales_email/order/guest_template
最容易看漏的是配置作用域
后台编辑的是一个模板,实际发送配置可能仍指向另一个模板 ID;登录客户与访客订单还可能使用不同配置。更常见的是:你在 Default Config 改了值,但目标 Website 或 Store View 早就保存了覆盖值。
检查时带上作用域参数,并从问题订单读取真实 store_id。不要以当前后台右上角选中的 Store 代替订单所属 Store。
php bin/magento config:show sales_email/order/template --scope=stores --scope-code=cn_zh
php bin/magento config:show sales_email/order/guest_template --scope=stores --scope-code=cn_zh
为什么重启消费者有时真的有效
如果发送由常驻 PHP 进程完成,发布新代码、修改依赖注入或更换模板解析逻辑后,旧进程可能一直使用启动时加载的类和配置对象。此时 Web 请求已经是新版本,消费者仍在旧版本。先确认进程由 Supervisor、systemd 还是容器编排管理,再做有序重启;不要在高峰期直接杀掉所有 worker,让处理中消息处于未知状态。
纯数据库模板文本通常不要求重启 PHP,但模板选择逻辑、插件和自定义变量提供器变更需要。这个区别能避免每次改文案都把队列服务重启一遍。
把一次发送追到具体模板
临时增加脱敏日志,记录订单号、Store ID、客户是否访客、最终模板标识、区域和队列消息 ID。不要记录收件人完整邮箱、邮件正文或重置链接。拿到“最终模板标识”后,问题就从模糊的旧缓存,缩小成了明确的配置选择或内容快照。
修复完成后,我会故意保留一条修改前任务,再创建一条修改后任务,分别观察系统是否符合设计。如果产品要求“所有未发送邮件立刻采用新模板”,而当前队列保存的是 HTML 快照,那就不是清缓存能解决的,需要调整队列的数据模型或提供受控的重建任务。

