允许用户在 Laravel 中使用自己的 SMTP 设置发送电子邮件
发布日期 作者 Andrés Santibáñez
在最近的一个项目中,我需要一种方法来允许用户使用其 SMTP 凭据发送电子邮件。这样做是为了使所有发出的电子邮件更具个性化,看起来像是直接来自组织,而不是来自 Web 应用程序的通用电子邮件。
我开始寻找能够提供这种功能的选项和包,因为我只在项目中通过配置任何 Laravel 项目中提供的 **邮件环境变量** 来发送电子邮件。
MAIL_DRIVER=MAIL_HOST=MAIL_PORT=MAIL_USERNAME=MAIL_PASSWORD=MAIL_ENCRYPTION=
使用有效的 SMTP 凭据设置这些环境变量将使您能够在您的项目中立即发送电子邮件。但是,在我的情况下,我需要根据登录的用户在运行时切换这些凭据。这种解决方案并不适合,因为框架使用提供的环境变量配置一次 Mail
门面,并且每次调用 Mail::send()
都会使用这些凭据。Mail
门面配置可以在框架的 MailServiceProvider
类中看到,特别是在 registerIlluminateMailer() 方法中,该方法配置 Mailer
类的 **单例** 实例。每次我们使用 Mail
门面时都会使用此 Mailer
实例。
Laravel 的 Mail 门面易于使用。使用简单直观的 API 可以配置收件人和电子邮件内容。由于我希望在使用运行时配置的凭据发送电子邮件时实现相同的表达能力,因此我研究了框架是如何配置 Mailer
实例的,以及如何获取该类的实例,但使用其他 SMTP 凭据。
深入研究 Mailer
类后,我发现可以使用 新的实例构建 Swift_Mailer
对象。反过来,Swift_Mailer
类使用 Swift_SmtpTransport
对象构建,该对象包含用于发送电子邮件的 SMTP 凭据。Swift_Mailer
和 Swift_SmtpTransport
都是 Laravel 在幕后使用发送电子邮件的 SwiftMailer 包的一部分。
确定了这种依赖关系链后,我需要一种方法来构建这些对象,以便能够指定要使用的电子邮件凭据。首先,我们使用我们的凭据设置一个新的 Swift_SmtpTransport
对象。
$transport = new Swift_SmtpTransport('SMTP-HOST-HERE', SMTP-PORT-HERE);$transport->setUsername('SMTP-USERNAME-HERE');$transport->setPassword('SMTP-PASSWORD-HERE'); // extra configurations here if needed
然后,我们使用 Swift_SmtpTransport
构建我们的 Swift_Mailer
对象。
$swift_mailer = new Swift_Mailer($transport);
有了这两个对象,我们现在需要一个具有我们新配置的 Mailer
对象。如 MailServiceProvider
类所示,Mailer
对象是 使用三个参数构建的:视图工厂、Swift_Mailer
和可选的事件分发器。我们可以通过 app()->get()
辅助函数获得额外的参数,这要归功于 Laravel 的 服务容器。
$view = app()->get('view');$events = app()->get('events');$mailer = new Mailer($view, $swift_mailer, $events);
我们还设置了 **replyTo** 和 **from** 属性,因此使用 Mailer
发送的每封电子邮件都具有这些签名。
$mailer->alwaysFrom('from-email', 'from-name');$mailer->alwaysReplyTo('from-email', 'from-name');
有了新的 Mailer
对象,我们现在可以使用运行时配置的凭据发送电子邮件。
$mailer->to('recipient')->send(new SomeMailable());
简化我们的自定义 Mailer 设置
现在我们有了自定义的 Mailer
设置,我们可以将此代码放在服务类中或在将为我们返回准备使用的 Mailer
对象的函数中。但是,获取自定义 Mailer
的更优雅方式是使用 Laravel 的服务容器在我们每次需要它时构建我们的对象。在项目的 AppServiceProvider
类中,我们可以使用任何我们想要用来获取自定义 Mailer
的键来注册一个新的绑定。在我的情况下,我使用 user.mailer
作为键。
// AppServiceProvider.class public function register(){ $this->app->bind('user.mailer', function ($app, $parameters) { $smtp_host = array_get($parameters, 'smtp_host'); $smtp_port = array_get($parameters, 'smtp_port'); $smtp_username = array_get($parameters, 'smtp_username'); $smtp_password = array_get($parameters, 'smtp_password'); $smtp_encryption = array_get($parameters, 'smtp_encryption'); $from_email = array_get($parameters, 'from_email'); $from_name = array_get($parameters, 'from_name'); $from_email = $parameters['from_email']; $from_name = $parameters['from_name']; $transport = new Swift_SmtpTransport($smtp_host, $smtp_port); $transport->setUsername($smtp_username); $transport->setPassword($smtp_password); $transport->setEncryption($smtp_encryption); $swift_mailer = new Swift_Mailer($transport); $mailer = new Mailer($app->get('view'), $swift_mailer, $app->get('events')); $mailer->alwaysFrom($from_email, $from_name); $mailer->alwaysReplyTo($from_email, $from_name); return $mailer; });}
绑定到位后,每次我们需要我们的客户 Mailer
时,我们都可以使用服务容器为我们构建一个实例,并传递必要的配置。
$configuration = [ 'smtp_host' => 'SMTP-HOST-HERE', 'smtp_port' => 'SMTP-PORT-HERE', 'smtp_username' => 'SMTP-USERNAME-HERE', 'smtp_password' => 'SMTP-PASSWORD-HERE', 'smtp_encryption' => 'SMTP-ENCRYPTION-HERE', 'from_email' => 'FROM-EMAIL-HERE', 'from_name' => 'FROM-NAME-HERE',]; $mailer = app()->makeWith('user.mailer', $configuration); // Use $mailer here...
注意事项
在 Laravel 中,Mailables
可以排队,以便稍后由项目的配置的队列系统发送。排队的 Mailables
总是使用 Mail
门面调度,这意味着如果我们使用自定义 Mailer
排队电子邮件,它将使用项目的邮件凭据而不是自定义凭据发送。为了防止这种情况发生,我们可以将每个将使用自定义凭据的 Mailable 设置为默认情况下不可排队。为此,我们应该从 Mailable
类中删除 ShouldQueue
实现。
更改
class SomeMailable extends Mailable implements ShouldQueue{ // Mailable code here}
为
class SomeMailable extends Mailable{ // Mailable code here}
接下来,我们将创建一个可排队且在内部使用自定义 Mailer
的作业类。我们将这个类命名为 UserMailerJob
,它将接收我们的自定义配置、电子邮件收件人和要发送的 Mailable
对象。
class UserMailerJob implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $configuration; public $to; public $mailable; /** * Create a new job instance. * * @param array $configuration * @param string $to * @param Mailable $mailable */ public function __construct(array $configuration, string $to, Mailable $mailable) { $this->configuration = $configuration; $this->to = $to; $this->mailable = $mailable; } /** * Execute the job. * * @return void */ public function handle() { $mailer = app()->makeWith('user.mailer', $this->configuration); $mailer->to($this->to)->send($this->mailable); }}
有了这个作业,我们就可以调度我们的 UserMailerJob
作业,使用项目的队列系统发送 Mailables
。
$configuration = [ 'smtp_host' => 'SMTP-HOST-HERE', 'smtp_port' => 'SMTP-PORT-HERE', 'smtp_username' => 'SMTP-USERNAME-HERE', 'smtp_password' => 'SMTP-PASSWORD-HERE', 'smtp_encryption' => 'SMTP-ENCRYPTION-HERE', 'from_email' => 'FROM-EMAIL-HERE', 'from_name' => 'FROM-NAME-HERE',]; UserMailerJob::dispatch($configuration, 'recipient', new SomeMailable());
结论
有了这种设置,我们就可以使用在运行时配置的自定义凭据发送电子邮件。这种实现的优点是,我们没有使用任何解决这个问题的包,并且我们使用了 Laravel 在幕后用来发送电子邮件的相同对象和编排。
这是解决此类需求的众多可能实现之一。