Laravel Mailable 默认从 resources/views/emails/xxx.blade.php 加载模板,需显式调用 $this->view('emails.xxx');传参应使用 public 属性或 with() 方法;队列发送时避免序列化 Closure、Model 等不可序列化对象。

邮件模板文件放哪、怎么命名才被 Laravel 自动识别
Laravel 的 Mailable 类默认会去 resources/views 下找 Blade 模板,路径和类名强绑定。比如你写了个 WelcomeEmail 类,它默认渲染 resources/views/welcome-email.blade.php 或 resources/views/emails/welcome.blade.php —— 但后者必须显式指定,否则不生效。
常见错误是把模板随便丢在 resources/views/mail/ 里却不改 Mailable 的 build() 方法,结果发信时抛出 ViewNotFoundException。
- 推荐做法:统一用
resources/views/emails/xxx.blade.php,并在 Mailable 构造或build()中调用$this->view('emails.xxx') - 别依赖“自动推导”:Laravel 不会根据类名驼峰转 kebab 去猜路径,
WelcomeEmail≠welcome-email,除非你重写view()的返回值 - 模板里不要直接 echo 用户输入,Blade 默认已转义,但若用了
{!! $content !!},得自己确保内容可信
Mailable 类里怎么传数据、哪些方式安全又清晰
传参不是越灵活越好。Mailable 构造函数接收的数据,会被自动绑定到模板里,但绑定逻辑只认 public 属性或 with() 显式传入的键值对。
容易踩的坑是:在 build() 里临时 set 一个属性,以为模板能访问,结果是 null —— 因为只有构造时传进来的 public 属性,或 with() 的内容才会被注入视图。
- 安全做法:把必要字段全写成 public 属性,在构造函数里赋值,例如
public $user, $order; - 动态数据用
$this->with(['token' => Str::random(32)]),避免污染类状态 - 别在
build()里调用 Eloquent 查询:Mailable 可能被队列延迟执行,那时模型实例可能已过期或丢失上下文
本地开发时邮件发不出去?关键配置项检查清单
本地发不出邮件,90% 是 .env 里这几个配置没对齐,而且 Laravel 不报错,只静默失败(尤其用 log 驱动时)。
运行 php artisan tinker 手动试发一封,比看日志更快定位问题:
Mail::to('test@example.com')->send(new App\Mail\WelcomeEmail);
- 确认
MAIL_MAILER=smtp(不是mail或sendmail,后者在 macOS/Linux 上常因权限失败) -
MAIL_HOST别写localhost:很多 SMTP 服务(如 Mailgun、Gmail)要求真实域名或特定中继地址 -
MAIL_PASSWORD如果含特殊字符(如/、@),要 URL 编码,否则 .env 解析失败 - 开发阶段建议先切到
MAIL_MAILER=log,看storage/logs/laravel.log里有没有Sent to记录,排除队列或配置加载问题
队列发送邮件时,Mailable 序列化失败怎么办
当你把邮件塞进队列(Mail::to(...)->queue(new WelcomeEmail)),Laravel 会序列化整个 Mailable 实例。如果里面存了 Closure、Resource、Eloquent Model 实例,就会报 Serialization of 'Closure' is not allowed。
这不是模板或驱动的问题,而是对象生命周期管理没做好。
- 永远不要在 Mailable 属性里存
$request、$response、UploadedFile或未转数组的 Model 对象 - 需要模型数据?在构造函数里取
$user->only(['id', 'name', 'email']),或用$user->toArray() - 附件路径别用
storage_path()动态拼接:队列 worker 运行时工作目录可能不同,应传绝对路径或 base64 内容
最隐蔽的坑是用了第三方 SDK 客户端实例(比如 Stripe client)作为属性——它内部含 resource 或 closure,序列化必炸。这类东西必须在 build() 里按需创建,别提前存。










