SMTP 配置必须写在 .env 文件中,config/mail.php 仅为模板;Gmail 需用应用专用密码,端口与加密方式需匹配(587+tls 或 465+ssl);本地开发推荐 MAIL_MAILER=log 调试。

SMTP 配置写在哪?别碰 .env 以外的文件
Laravel 的邮件配置默认只从 .env 读取,改 config/mail.php 不生效——这是最常踩的坑。框架启动时会把 .env 里的值注入到配置数组里,config/mail.php 只是模板,不是运行时配置源。
实操建议:
- 所有 SMTP 参数必须写进
.env,包括MAIL_MAILER=smtp、MAIL_HOST=smtp.gmail.com、MAIL_PORT=587、MAIL_USERNAME=your@gmail.com、MAIL_PASSWORD=app_password - Gmail 用户注意:
MAIL_PASSWORD必须是「应用专用密码」,不是邮箱登录密码;开启两步验证后才可生成 - 端口选
587(TLS)或465(SSL),别混用:配MAIL_ENCRYPTION=tls就得用 587,配MAIL_ENCRYPTION=ssl才能用 465 - 本地开发用 Mailtrap 测试时,
MAIL_HOST填smtp.mailtrap.io,端口填2525,加密方式填tls
Mail::to() 发不出去?检查队列和驱动是否匹配
调用 Mail::to('x@y.z')->send(new WelcomeMail) 没报错但收不到邮件,大概率是驱动没走 SMTP,或者被塞进队列但没处理。
实操建议:
- 先确认当前驱动:
php artisan tinker→config('mail.default')应该返回'smtp',不是'log'或'array' - 如果用了队列(
QUEUE_CONNECTION=database或类似),发信会进队列,必须运行php artisan queue:work才真正发送;不跑 worker 就永远卡在数据库里 - 临时绕过队列调试:在
Mail::to(...)->send(...)前加Mail::fake()看是否报错;或直接用Mail::raw('test', function ($m) { $m->to('x@y.z'); })快速验证基础通路
本地开发收不到?log 驱动比 smtp 更适合调试
开发阶段硬连真实 SMTP 容易触发频率限制、被拦截,也难查内容是否正确。用 log 驱动能把邮件内容输出到日志,既安全又直观。
实操建议:
- 改
.env:把MAIL_MAILER=smtp换成MAIL_MAILER=log,其他 SMTP 参数不用删,留着切回线上方便 - 发一封测试邮件后,看
storage/logs/laravel.log,搜索"To:"或"Subject:",能直接看到原始 MIME 内容 - 注意:日志驱动不校验收件人格式,
Mail::to('invalid')->send(...)也不会报错,只是默默记进 log —— 所以正式部署前务必切回smtp并做一次真实发送验证
邮件模板里引用资产失败?别用 asset(),改用 url()
在 Blade 邮件模板里写 <img src="{{ asset('images/logo.png') }}">,发出去的 HTML 里路径还是相对地址,导致图片 404。因为邮件客户端不执行 Laravel 的 asset 版本控制逻辑,也不共享 Web 请求上下文。
实操建议:
- 所有静态资源链接必须用绝对 URL:
<img src="{{ url('images/logo.png') }}">,url()会拼出完整域名(如https://example.com/images/logo.png) - 确保
APP_URL在.env中设置正确(比如APP_URL=https://yourdomain.com),否则url()生成的链接会指向 localhost 或空协议 - CSS 内联优先:邮件客户端对
<style>标签支持极差,关键样式(如宽度、颜色)全写进属性里,避免依赖外部 CSS
真实环境里,SMTP 连接超时、认证失败、内容被标记为垃圾邮件,这些都不会抛出明显异常,只会静默失败。多看 storage/logs/laravel.log 里带 Swift_TransportException 的那一行,比反复重试更省时间。










