Yii发邮件需严格配置mailer组件并执行compose()->send()链路,键名必须为'mailer'且transport为数组,敏感信息用getenv(),开发时启用useFileTransport,535错误多因授权码或配置不当,模板路径和命名须匹配,默认HTML邮件需显式setHtmlBody,附件与内嵌图片须分清attach()和embed()。

直接说结论:Yii 框架发邮件不是“调个函数就完事”,核心是配对 mailer 组件 + 正确走 compose()->send() 链路,错一步就静默失败——不报错、不发信、日志里也常没痕迹。
mailer 组件必须注册在 config/main.php(或 main-local.php)的 components 里
硬塞进 config/web.php 或在控制器里 new Swift_SmtpTransport 是常见反模式。Yii 不会识别非标准键名(比如叫 email 或 smtp),Yii::$app->mailer 就拿不到实例。
- 配置必须用
'mailer'作为键名,且整个结构放在'components'数组下 -
'transport'必须是数组,不能提前 new 成对象(Yii 内部会根据这个数组自动实例化) - 敏感字段如
password务必用getenv('SMTP_PASSWORD'),别写死在配置里 - 开发时可设
'useFileTransport' => true,邮件会生成在runtime/mail/下的.eml文件,方便验证内容
535 错误 = 认证失败,但真实原因往往藏得深
Expected response code 250 but got code "535" 是 Gmail、Outlook、163 等服务商返回的典型认证拒绝码,表面看是账号密码错,实际踩坑点更多:
- Gmail 必须开两步验证,再生成「应用专用密码」填进
password,原邮箱密码直接失效 - QQ 邮箱 / 163 邮箱要用「授权码」,不是登录密码;且 QQ 的
host是smtp.qq.com,端口 465(ssl)或 587(tls),混用会失败 - 企业邮箱常要求
username填完整邮箱地址(如user@company.com),只填user会被拒 -
password含@、/等字符时,若从配置文件读取可能被 URL 解析逻辑截断——所以坚持用getenv() - 某些环境(如本地 Apache + IPv6)可能触发
501 5.5.2 <::1>: helo command rejected,加'localdomain' => '[127.0.0.1]'到 transport 数组里能绕过
compose() 不 send() = 白写,模板路径和视图命名有默认规则
Yii::$app->mailer->compose('welcome') 只是构建消息对象,不调 ->send() 就等于没发;而且它默认去 @app/mail/ 目录找 welcome.php,不是 welcome.html 也不是 views/mail/welcome.php。
- 模板文件名要跟
compose()第一个参数严格一致,扩展名固定为.php - 如果项目是 Advanced 模板,
viewPath常配成@common/mail,那模板就得放common/mail/welcome.php - 带参数传入模板:
compose('welcome', ['name' => 'Alice']),模板里直接用= $name ?> - 发 HTML 邮件必须显式调
setHtmlBody(),只设setTextBody()会导致收件箱只显示纯文本
附件和内嵌图片要分清 attach() 和 embed()
attach() 是常规附件(PDF、CSV),embed() 才是让图片出现在 HTML 正文里(比如 logo)。用错就变成一堆乱码链接或附件图标。
-
attach('/path/to/file.pdf'):传绝对路径,文件需 PHP 进程有读权限 -
attachContent($csvData, ['fileName' => 'data.csv', 'contentType' => 'text/csv']):适合动态生成内容 - 内嵌图片先
$message->embed('/path/to/logo.png'),再在 HTML 里用引用,SwiftMailer 会自动处理 cid - 注意 MIME 类型:非标准后缀(如
.webp)建议显式传contentType,否则可能被当application/octet-stream
最容易被忽略的是 transport 层的复用机制——每次 compose() 都复用同一个 Swift_SmtpTransport 实例,但如果你在多个地方手动 new transport 或改配置,连接池就乱了;还有 messageConfig 里设的 from 是全局兜底,单次发送仍可用 setFrom() 覆盖,别以为配了就一劳永逸。











