symfony邮件队列本质由messenger组件实现异步投递,mailer仅构造邮件;关键在路由配置正确(如symfony\component\mailer\messenger\sendemailmessage)、附件需存共享存储传id、消费者需手动启动并守护运行。

Symfony 邮件队列本质是靠 Messenger 组件 实现异步投递,不是 Mailer 自己排队——Mailer 负责构造邮件,Messenger 负责把它“发出去”(实际是存进队列、再由消费者取出发送)。关键不在怎么“发”,而在怎么让“发邮件”这个动作走队列而不是立刻执行。
确保邮件真走异步队列
很多开发者配置完发现邮件还是秒发,根本原因是路由没对上。Messenger 只看消息类名,不看你处理器叫什么:
- 检查
config/packages/messenger.yaml的routing键是否严格等于Symfony\Component\Mailer\Messenger\SendEmailMessage(注意大小写、反斜杠、完整命名空间) - 别写成
App\MessageHandler\...或SendEmailHandler——这些是处理器类,不是消息类 - 运行
php bin/console debug:messenger,确认该消息类对应的 transport 列显示为async,不是空或sync - 如果用了自定义邮件消息(如
App\Message\SendWelcomeEmail),那 routing 键就必须是那个自定义类的全名,不能混用
附件处理要特别小心
带附件时异步容易出错,核心矛盾是:文件路径在消费者进程里可能不存在,而二进制内容塞进消息又会让队列膨胀:
- 用
$email->attachFromPath('/path/to/file.pdf')?不行——生产者有路径,消费者机器上大概率没有这个路径 - 用
$email->attach($content, 'file.pdf', 'application/pdf')?可以,但整个文件内容会序列化进消息,大附件直接拖慢队列、撑爆 Redis 内存 - 推荐做法:把附件存在共享存储(如 S3、NFS 或数据库 BLOB),消息里只传唯一标识(如
attachment_id: "abc123"),消费者收到后再去取文件并附加
启动消费者才能真正“发”邮件
配置好只是第一步,Messenger 不会自动轮询队列。必须手动或自动运行消费者命令:
- 本地测试:
php bin/console messenger:consume async --limit=10(消费 10 条后退出) - 生产环境:
php bin/console messenger:consume async --time-limit=3600(持续运行 1 小时) - 建议用进程管理器(如 Supervisor)守护该命令,保证它常驻运行
- 注意 DSN 中的路径部分(如
redis://localhost:6379/myapp_messages)要和 consume 命令指定的 transport 名一致,否则读不到消息
需要部分同步、部分异步?得自己分消息类型
默认所有 MailerInterface::send() 都走同一个 SendEmailMessage,所以一配就全异步。想灵活控制:
- 保留默认行为用于普通通知类邮件(如注册欢迎信)
- 对紧急邮件(如密码重置)改用同步方式:
$this->mailer->send($email)前先临时禁用总线:$mailer->setBus(null),或单独注入一个无 bus 的 mailer 实例 - 更规范的做法是定义专用消息类(如
App\Message\UrgentEmail),不走SendEmailMessage,然后在 routing 中只把非紧急类映射到 async











