邮件发不出去但日志没报错,大概率是队列监听未启动;laravel默认同步发送,queue()需手动运行php artisan queue:work或配置supervisor,且确保redis等驱动正常、序列化数据合规、邮件内容前置加载。

邮件发不出去但日志没报错,大概率没启动队列监听
Laravel 的 Mail::to() 默认是同步发送,只有显式推入队列才异步。很多人写了 Mail::to()->queue() 却发现邮件迟迟不发,翻日志又没错误——其实是队列根本没在跑。
实操建议:
- 确认已配置
QUEUE_CONNECTION=redis(或database、supervisor等)并在.env中生效 - 运行
php artisan queue:work启动监听(开发环境可加--once测试单条) - 若用
supervisor部署,检查进程是否存活:supervisorctl status,常见坑是配置里漏了environment=APP_ENV="production"导致 .env 未加载 - Redis 队列需确保
redis-server运行,且 Laravel 配置的database和prefix与实际一致(默认laravel_database)
用了 queue() 还卡住页面?检查 Mailable 类是否序列化了闭包或资源句柄
Laravel 队列任务必须可序列化,而 Mailable 在进入队列前会把所有属性打包。一旦你往里面塞了 resource(比如文件句柄)、Closure 或未实现 __serialize() 的第三方对象,队列就会静默失败或卡死。
实操建议:
- 避免在
build()外直接赋值大数组或模型集合;改用with()传参,它只序列化基础类型和 Eloquent 模型(自动处理) - 不要在构造函数里做耗时操作(如 API 调用),队列任务实例化阶段就该轻量
- 调试时可在
app/Exceptions/Handler.php的report()里加日志,捕获SerializationException - 示例:错误写法
$this->data = fopen(...);正确写法是把路径传进去,到build()再打开
邮件内容动态拼接慢?别在 build() 里查数据库
build() 方法会在队列 worker 进程中执行,如果这里调用 User::find() 或 DB::table()->get(),每次发一封都查一次库,QPS 上不去还容易连爆。
实操建议:
- 所有数据应在投递队列前查好,通过
with()注入,比如Mail::to($user)->queue(new WelcomeMail($user->toArray(), $stats)) - 若数据量大或需缓存,先用
Cache::remember()获取,再传进队列,避免重复计算 - 注意 N+1 问题:传模型时用
load()预加载关联,别让build()里触发懒加载 - Redis 队列下,任务体大小建议控制在 100KB 内,超大会拖慢 pop 速度
本地开发用 log 驱动测试队列,但收不到邮件?不是 bug 是预期行为
MAIL_MAILER=log 时,Laravel 根本不会调 SMTP,而是把原始邮件内容写进 storage/logs/laravel.log。很多人设了 log 驱动还盯着邮箱等,结果以为“队列没生效”。
实操建议:
- 开发阶段验证队列逻辑,优先看日志是否出现
[2024-01-01 12:00:00] local.INFO: Sent email to user@example.com via log driver - 想预览 HTML 效果,可用
MAIL_MAILER=smtp+MAIL_HOST=mailhog(Docker 场景)或本地MAIL_HOST=127.0.0.1+ MailHog GUI - 切记:log 驱动下
queue()和send()行为一致(都写日志),区别只在是否走队列通道 - 上线前务必检查
config/mail.php的from地址是否合法,某些 SMTP 服务拒收空From:或未认证域名
队列不是银弹,关键在数据前置、序列化干净、驱动匹配。最容易被忽略的是:你以为在发邮件,其实只是在往 Redis 里塞字符串——得有人(worker)真去取、解、连、发。











