You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 9队列执行时动态SMTP配置不生效问题求助

动态SMTP配置在Laravel队列任务中失效的解决方案

问题背景

  • 基于Laravel 9开发应用,支持组织在数据库中设置自定义SMTP配置
  • 使用Notification类发送邮件时,无队列场景下动态SMTP配置可正常生效
  • 执行queue:work --stop-when-empty处理队列任务时,动态SMTP配置无法正确读取
  • queue:listen命令下配置可正常工作,但顾虑在共享主机及AWS-Plesk环境持续运行该命令会引发稳定性或资源占用问题

问题原因

Laravel的queue:work命令会以常驻内存的方式启动进程,进程初始化时会一次性加载并缓存所有配置。队列任务执行时,在toMail方法中修改的config()仅为当前请求级别的临时修改,无法更新常驻进程的缓存配置。而queue:listen会在每次处理任务时重新加载框架,所以配置修改能生效,但性能损耗更高,且在受限环境中持续运行稳定性较差。

解决方案

方案1:为每个组织创建独立邮件驱动(最优解)

利用Laravel多邮件驱动特性,动态注册专属驱动而非修改默认SMTP配置,避免进程缓存干扰:

修改Notification类的toMail方法:

public function toMail($notifiable)
{
    // 用组织ID作为动态驱动的唯一标识
    $mailerName = 'dynamic_smtp_' . $this->data->mailerConfig->id;

    if (!empty($this->data) && isset($this->data->mailerConfig)) {
        $mailConfigs = $this->data->mailerConfig;
        
        // 动态注册新的邮件驱动
        config()->set("mail.mailers.{$mailerName}", [
            'transport' => 'smtp',
            'host' => $mailConfigs->MAIL_HOST,
            'port' => $mailConfigs->MAIL_PORT,
            'encryption' => $mailConfigs->MAIL_ENCRYPTION,
            'username' => $mailConfigs->MAIL_USERNAME,
            'password' => $mailConfigs->MAIL_PASSWORD,
            'timeout' => null,
            'auth_mode' => null,
        ]);

        // 设置全局发件人信息
        config()->set("mail.from", [
            'address' => $mailConfigs->MAIL_FROM_ADDRESS ?? 'project@domain.io',
            'name' => $mailConfigs->MAIL_FROM_NAME ?? 'compliance',
        ]);
    }

    // 批量替换模板变量
    $subject = $this->notification->title;
    $email_body = str_replace(
        ['{user}', '{reviewer}', '{task}', '{feedback}'],
        [ucfirst($this->data->user), $this->data->reviewer, $this->data->task, $this->data->feedback],
        $this->notification->message
    );

    // 指定使用动态驱动发送邮件
    return (new MailMessage)
        ->mailer($mailerName)
        ->subject($subject)
        ->markdown('emails.mail', ['email_body' => $email_body]);
}

方案2:在队列任务执行前重置邮件配置

通过队列钩子或中间件,在每次任务执行前清除邮件配置缓存,强制重新加载:

方法A:使用Notification的队列钩子

在Notification类中添加beforeQueued方法:

public function beforeQueued()
{
    // 清除邮件管理器实例和配置缓存
    app()->forgetInstance('mail.manager');
    config()->forget('mail.mailers.smtp');
}

方法B:创建队列中间件

  1. 生成中间件:
php artisan make:middleware ResetMailConfig
  1. 编写中间件逻辑:
<?php

namespace App\Http\Middleware;

use Closure;

class ResetMailConfig
{
    public function handle($job, Closure $next)
    {
        // 重置邮件管理器实例和配置
        app()->forgetInstance('mail.manager');
        config()->forget('mail.mailers.smtp');
        
        return $next($job);
    }
}
  1. 在app/Providers/QueueServiceProvider.php中注册全局队列中间件:
public function boot()
{
    $this->app['queue']->pushMiddleware(\App\Http\Middleware\ResetMailConfig::class);
}

方案3:自定义队列任务直接发送邮件

绕过Notification的队列序列化限制,直接使用Mail facade创建自定义邮件实例:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use Illuminate\Mail\Mailer;

class SendTaskActionEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $notification;
    public $data;

    public function __construct($notification, $data)
    {
        $this->notification = $notification;
        $this->data = is_array($data) ? (object)$data : $data;
    }

    public function handle()
    {
        $mailConfigs = $this->data->mailerConfig;
        
        // 创建自定义邮件实例并配置SMTP参数
        $mailer = app(Mailer::class)->setMailer([
            'transport' => 'smtp',
            'host' => $mailConfigs->MAIL_HOST,
            'port' => $mailConfigs->MAIL_PORT,
            'encryption' => $mailConfigs->MAIL_ENCRYPTION,
            'username' => $mailConfigs->MAIL_USERNAME,
            'password' => $mailConfigs->MAIL_PASSWORD,
        ]);

        $subject = $this->notification->title;
        $email_body = str_replace(
            ['{user}', '{reviewer}', '{task}', '{feedback}'],
            [ucfirst($this->data->user), $this->data->reviewer, $this->data->task, $this->data->feedback],
            $this->notification->message
        );

        // 发送邮件
        $mailer->send('emails.mail', ['email_body' => $email_body], function ($message) use ($subject, $mailConfigs) {
            $message->to($this->data->recipient_email)
                ->subject($subject)
                ->from(
                    $mailConfigs->MAIL_FROM_ADDRESS ?? 'project@domain.io',
                    $mailConfigs->MAIL_FROM_NAME ?? 'compliance'
                );
        });
    }
}

注意事项

  • 方案1为最优选择,每个组织的SMTP配置独立隔离,完全适配queue:work的常驻进程特性
  • 禁止在队列任务中直接修改默认smtp驱动配置,否则常驻进程会缓存错误配置影响后续任务
  • 在共享主机或Plesk环境中,建议用Cron定时任务周期性触发queue:work --stop-when-empty,替代持续运行的queue:listen,平衡配置有效性与资源占用

内容的提问来源于stack exchange,提问作者gaurav amoli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 07:06:21