邮件发送失败时应优先检查 swift_transportexception 的 getprevious()->getmessage() 获取底层错误,确认 usefiletransport 与 transport 配置互斥、transport 参数完整、php openssl/sockets 扩展已启用,并排除 selinux、防火墙及云安全组的网络限制。

邮件发送失败时先看 Swift_TransportException 的具体错误信息
Yii2 默认用 SwiftMailer 发邮件,失败时抛出的异常类型基本就是 Swift_TransportException,但它的 getMessage() 往往只显示“Connection could not be established with host”,真正有用的线索藏在 getTraceAsString() 或更关键的 getPrevious()->getMessage() 里——比如底层是 fsockopen() 超时,还是 TLS 握手失败。
实操建议:
- 在
try...catch块中捕获Swift_TransportException,并打印$e->getPrevious() ? $e->getPrevious()->getMessage() : $e->getMessage() - 临时在
config/web.php的log组件里加一条['class' => 'yii\log\FileTarget', 'levels' => ['error'], 'categories' => ['yii\swiftmailer\Mailer']],让所有 Mailer 错误进日志 - 别只盯着
mail()函数返回值,Yii2 默认走 SMTP,mail()根本没调用
SMTP 配置里 useFileTransport 和 transport 别同时设错
开发环境常把 useFileTransport 设为 true 来避免真发信,但它和 transport 是互斥逻辑:设了 useFileTransport = true,Yii2 就直接跳过整个 SMTP 初始化,transport 配置再全也无效;反过来,如果忘了关 useFileTransport 却又在生产环境用文件传输,邮件就静默消失,连错误都不报。
实操建议:
- 检查
config/main-local.php(开发)和config/main.php(生产)是否各自覆盖了useFileTransport,且值符合当前环境预期 -
transport配置必须完整包含class、host、username、password、port、encryption,缺任意一项都可能静默失败 - 腾讯企业邮箱要设
encryption => 'tls',而 Gmail 用'ssl',端口也得对应(587 vs 465),写反了会卡在 CONNECTING
PHP 的 openssl 和 sockets 扩展没启用会导致连接直接被拒
SwiftMailer 依赖 PHP 底层 socket 连接 SMTP 服务器,如果 php.ini 里禁用了 extension=openssl 或 extension=sockets,哪怕配置全对,也会在 Swift_Transport_StreamBuffer->initialize() 阶段抛出 Call to undefined function openssl_encrypt() 或类似致命错误,但 Yii2 日志里可能只记成 “Failed to authenticate on SMTP server”。
实操建议:
- 运行
php -m | grep -E 'openssl|sockets'确认扩展已加载 - 在控制器里加一行
var_dump(function_exists('fsockopen'), extension_loaded('openssl'));快速验证 - Docker 环境容易漏装扩展,Alpine 镜像需额外
apk add php7-openssl php7-sockets,Ubuntu 则是apt-get install php-openssl php-sockets
防火墙、SELinux、云厂商安全组这三层网络限制最容易被忽略
本地能 telnet 通 SMTP 端口,不等于 PHP 进程能连——尤其是 CentOS 开启 SELinux 后,默认禁止 httpd/nginx 子进程发起 outbound 连接;阿里云/腾讯云的安全组也可能只放行 80/443,忘了开 587 或 465;甚至某些 IDC 会主动拦截发信端口防垃圾邮件。
实操建议:
- 用
sudo -u www-data php -r "fsockopen('smtp.qq.com', 587, \$errno, \$errstr, 5) && print 'OK';"模拟 PHP 进程身份测试(注意替换用户和域名) - CentOS 下执行
setsebool -P httpd_can_network_connect 1放行网络连接 - 云服务器上检查安全组规则是否双向放行,不只是入站——出站规则同样要允许目标 SMTP IP 和端口
真正卡住的地方,往往不是代码写错了,而是 PHP 进程被系统或网络策略拦在了第一步连接上。调试时先确认它能不能发出第一个 TCP SYN 包,比翻配置快得多。









