客户同一订单收到了两封确认邮件。以前我也会第一时间检查 cron,后来发现不少重复邮件根本还没走到 cron 就已经生成了两次。先把两封邮件的 Message-ID、到达时间和正文放在一起比较,别凭感觉判断。

两封邮件先比这三个值

Message-ID 不同、模板生成时间也不同,通常是 Magento 发送流程执行了两次;Message-ID 相同但邮箱收到两次,继续查 SMTP 服务商或下游转发。再对照订单状态历史,看重复邮件是否紧跟两次支付回调或两次状态变更。

数据库里检查订单的 email_sent、创建时间和更新时间,但不要为了测试直接把 email_sent 改回 0,这会主动制造下一封邮件。

在自定义代码里搜第二个OrderSender

grep -R "OrderSender" app/code -n
grep -R "->send(.*order" app/code -n

核心下单流程已经安排发送时,自定义 observer 又监听 checkout success 调一次,是最直接的重复来源。支付插件也可能在 webhook 里再次调用发送。网关重复投递回调是正常现象,回调处理必须按交易号或事件 ID 做幂等,不能假设只会收到一次。

我会在发送入口临时记录 order_id、调用栈的自定义类、消息 ID 和 hostname,不记录客户敏感信息。两封邮件分别由哪个入口产生,很快就能看出来。

如果项目开启异步销售邮件,还要分清“入队两次”和“同一条消息消费两次”。在发送进入队列的位置与真正调用邮件传输的位置分别加一次关联 ID:前面已经出现两条,去查业务事件;前面一条、后面两次,才把注意力放到消费者确认与重投。

只有证据指向队列,才查Cron和Consumer

异步邮件模式下,同一条消息可能因为 consumer 发送成功后未及时确认而被重新投递。两台节点重复运行任务、进程被强杀、连接在 ack 前中断,都需要结合消息 ID 和 worker hostname 判断。

修复后我会故意重放一次支付回调,再在发送过程中重启一个测试 consumer。订单状态可以重复查询,但确认邮件只能生成一次。如果只是把某个 cron 停掉后不再重复,却没有解释第二次调用从哪里来,问题还没有解决。

另外核对邮件模板与发件配置作用域,避免把“订单确认”和自定义的“支付成功通知”做成几乎相同的正文。两封不同业务邮件看起来一样,不属于重复投递,应该从模板和主题上明确区分,而不是删除其中一个发送流程。