使用 Laravel Mail facade 从多个 Mailgun 域发送
Sending from multiple Mailgun domains using Laravel Mail facade
我正在使用 Laravel 4 的 Mail::queue()
发送电子邮件,使用内置的 Mailgun 驱动程序。问题是我希望能够从多个 Mailgun 域发送电子邮件,但域必须设置为 app/config/services.php
。由于我使用的是 Mail::queue()
,因此我看不到如何动态设置该配置变量。
有什么方法可以满足我的要求吗?理想情况下,我希望能够在调用 Mail::queue()
时传入域(Mailgun api 密钥对于我要发送的所有域都是相同的)。
在运行时切换 Laravel Mailer 的配置细节并不难,但是我不知道有什么方法可以使用 Mail::queue
facade 来完成。它可以通过使用 Queue::push
和 Mail::send
的组合来完成(这就是 Mail::queue
所做的)。
Mail::queue
facade 的问题是传递给闭包的 $message
参数是 Illuminate\Mail\Message
类型,我们需要修改邮件传输,它只能访问通过 Swift_Mailer
实例(在 Message
class 中是只读的)。
您需要创建一个 class 负责发送电子邮件,使用使用您想要的域的 Mailgun 传输实例:
use Illuminate\Mail\Transport\MailgunTransport;
use Illuminate\Support\SerializableClosure;
class SendQueuedMail {
public function fire($job, $params)
{
// Get the needed parameters
list($domain, $view, $data, $callback) = $params;
// Backup your default mailer
$backup = Mail::getSwiftMailer();
// Setup your mailgun transport
$transport = new MailgunTransport(Config::get('services.mailgun.secret'), $domain);
$mailer = new Swift_Mailer($transport);
// Set the new mailer with the domain
Mail::setSwiftMailer($mailer);
// Send your message
Mail::send($view, $data, unserialize($callback)->getClosure());
// Restore the default mailer instance
Mail::setSwiftMailer($backup);
}
}
现在您可以像这样对电子邮件进行排队:
use Illuminate\Support\SerializableClosure;
...
Queue::push('SendQueuedMail', ['domain.com', 'view', $data, serialize(new SerializableClosure(function ($message)
{
// do your email sending stuff here
}))]);
虽然它没有使用 Mail::queue
,但这个替代方案同样简洁易读。此代码未经测试,但应该可以使用。
这适用于 Laravel 5.4:
// Get the existing SwiftMailer
$swiftMailer = Mail::getSwiftMailer();
// Update the domain in the transporter (Mailgun)
$transport = $swiftMailer->getTransport();
$transport->setDomain('YOUR-DOMAIN.HERE');
// Use the updated version
$mailer = Swift_Mailer::newInstance($transport);
Mail::setSwiftMailer($mailer);
我用了Macros
来添加动态配置。我不记得这是否可以在 Laravel 4 中完成,但可以在 5.
中完成
在服务提供商中注册宏 (AppServiceProvider
)
public function boot()
{
Mail::macro('setConfig', function (string $key, string $domain) {
$transport = $this->getSwiftMailer()->getTransport();
$transport->setKey($key);
$transport->setDomain($domain);
return $this;
});
}
那么我可以这样使用:
\Mail::setConfig($mailgunKey, $mailgunDomain)->to(...)->send(...)
你的情况
\Mail::setConfig($mailgunKey, $mailgunDomain)->to(...)->queue(...)
我的用例与此类似,简而言之,我只是想在运行时自动配置 mailgun 发送域,通过查看 from 地址字段中设置的域消息(我在使用 Mail::from(...)->send(...)
发送之前即时设置的)。如果他们在邮件中设置发件人地址以匹配 mailgun 发送域,这将解决 OP 的用例,这可能应该完成。
我的解决方案注册了一个备用 MailgunTransport,它会覆盖内置的 MailgunTransport 并在发送前设置域。这样我只需要在我的 mail.php
中注册新的驱动程序,然后调用 Mail::send
或 Mail::queue
.
config\mail.php:
'driver' => env('MAIL_DRIVER', 'mailgun-magic-domain')
providers\MailgunMagicDomainProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Mail\Transport\MailgunTransport;
use Swift_Mime_Message;
use Illuminate\Support\Arr;
use GuzzleHttp\Client as HttpClient;
class MailgunMagicDomainProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
$swiftTransport = $this->app['swift.transport'];
$swiftTransport->extend('mailgun-magic-domain', function($app) {
$config = $app['config']->get('services.mailgun', []);
$client = new HttpClient(Arr::add(
Arr::get($config, 'guzzle', []), 'connect_timeout', 60
));
return new MailgunTransportWithDomainFromMessage(
$client,
$config['secret'],
$config['domain'] // <- we have to pass this in to avoid re-writing the whole transport, but we'll be dynamically setting this before each send anyway
);
});
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
}
}
/**
* Overrides the built in Illuminate\Mail\Transport\MailgunTransport but doesnt pull the
* mailgun sending domain from the config, instead it uses the domain in the from address
* to dynamically set the mailgun sending domain
*/
class MailgunTransportWithDomainFromMessage extends MailgunTransport
{
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->setDomain($this->getDomainFromMessage($message));
return parent::send($message, $failedRecipients);
}
protected function getDomainFromMessage(Swift_Mime_Message $message)
{
$fromArray = $message->getFrom();
if (count($fromArray) !== 1) {
throw new \Exception('Cannot use the mailgun-magic-domain driver when there isn\'t exactly one from address');
}
return explode('@', array_keys($fromArray)[0])[1];
}
}
config/app.php:
'providers' => [
...
\App\Providers\MailgunMagicDomainProvider::class
],
也许对某些人有用,我按如下方式解决了它;
在ServiceProvider
下bootfunction/method;
public function boot()
{
Mail::macro('setConfig', function (string $key, string $domain) {
config()->set('services', array_merge(config('services'), [
'mailgun' => [
'domain' => $domain,
'secret' => $key
]
]));
});
}
呼叫队列
Mail::setConfig($key, $domain)->to(...)->queue(...)
我正在使用 Laravel 4 的 Mail::queue()
发送电子邮件,使用内置的 Mailgun 驱动程序。问题是我希望能够从多个 Mailgun 域发送电子邮件,但域必须设置为 app/config/services.php
。由于我使用的是 Mail::queue()
,因此我看不到如何动态设置该配置变量。
有什么方法可以满足我的要求吗?理想情况下,我希望能够在调用 Mail::queue()
时传入域(Mailgun api 密钥对于我要发送的所有域都是相同的)。
在运行时切换 Laravel Mailer 的配置细节并不难,但是我不知道有什么方法可以使用 Mail::queue
facade 来完成。它可以通过使用 Queue::push
和 Mail::send
的组合来完成(这就是 Mail::queue
所做的)。
Mail::queue
facade 的问题是传递给闭包的 $message
参数是 Illuminate\Mail\Message
类型,我们需要修改邮件传输,它只能访问通过 Swift_Mailer
实例(在 Message
class 中是只读的)。
您需要创建一个 class 负责发送电子邮件,使用使用您想要的域的 Mailgun 传输实例:
use Illuminate\Mail\Transport\MailgunTransport;
use Illuminate\Support\SerializableClosure;
class SendQueuedMail {
public function fire($job, $params)
{
// Get the needed parameters
list($domain, $view, $data, $callback) = $params;
// Backup your default mailer
$backup = Mail::getSwiftMailer();
// Setup your mailgun transport
$transport = new MailgunTransport(Config::get('services.mailgun.secret'), $domain);
$mailer = new Swift_Mailer($transport);
// Set the new mailer with the domain
Mail::setSwiftMailer($mailer);
// Send your message
Mail::send($view, $data, unserialize($callback)->getClosure());
// Restore the default mailer instance
Mail::setSwiftMailer($backup);
}
}
现在您可以像这样对电子邮件进行排队:
use Illuminate\Support\SerializableClosure;
...
Queue::push('SendQueuedMail', ['domain.com', 'view', $data, serialize(new SerializableClosure(function ($message)
{
// do your email sending stuff here
}))]);
虽然它没有使用 Mail::queue
,但这个替代方案同样简洁易读。此代码未经测试,但应该可以使用。
这适用于 Laravel 5.4:
// Get the existing SwiftMailer
$swiftMailer = Mail::getSwiftMailer();
// Update the domain in the transporter (Mailgun)
$transport = $swiftMailer->getTransport();
$transport->setDomain('YOUR-DOMAIN.HERE');
// Use the updated version
$mailer = Swift_Mailer::newInstance($transport);
Mail::setSwiftMailer($mailer);
我用了Macros
来添加动态配置。我不记得这是否可以在 Laravel 4 中完成,但可以在 5.
在服务提供商中注册宏 (AppServiceProvider
)
public function boot()
{
Mail::macro('setConfig', function (string $key, string $domain) {
$transport = $this->getSwiftMailer()->getTransport();
$transport->setKey($key);
$transport->setDomain($domain);
return $this;
});
}
那么我可以这样使用:
\Mail::setConfig($mailgunKey, $mailgunDomain)->to(...)->send(...)
你的情况
\Mail::setConfig($mailgunKey, $mailgunDomain)->to(...)->queue(...)
我的用例与此类似,简而言之,我只是想在运行时自动配置 mailgun 发送域,通过查看 from 地址字段中设置的域消息(我在使用 Mail::from(...)->send(...)
发送之前即时设置的)。如果他们在邮件中设置发件人地址以匹配 mailgun 发送域,这将解决 OP 的用例,这可能应该完成。
我的解决方案注册了一个备用 MailgunTransport,它会覆盖内置的 MailgunTransport 并在发送前设置域。这样我只需要在我的 mail.php
中注册新的驱动程序,然后调用 Mail::send
或 Mail::queue
.
config\mail.php:
'driver' => env('MAIL_DRIVER', 'mailgun-magic-domain')
providers\MailgunMagicDomainProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Mail\Transport\MailgunTransport;
use Swift_Mime_Message;
use Illuminate\Support\Arr;
use GuzzleHttp\Client as HttpClient;
class MailgunMagicDomainProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
$swiftTransport = $this->app['swift.transport'];
$swiftTransport->extend('mailgun-magic-domain', function($app) {
$config = $app['config']->get('services.mailgun', []);
$client = new HttpClient(Arr::add(
Arr::get($config, 'guzzle', []), 'connect_timeout', 60
));
return new MailgunTransportWithDomainFromMessage(
$client,
$config['secret'],
$config['domain'] // <- we have to pass this in to avoid re-writing the whole transport, but we'll be dynamically setting this before each send anyway
);
});
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
}
}
/**
* Overrides the built in Illuminate\Mail\Transport\MailgunTransport but doesnt pull the
* mailgun sending domain from the config, instead it uses the domain in the from address
* to dynamically set the mailgun sending domain
*/
class MailgunTransportWithDomainFromMessage extends MailgunTransport
{
/**
* {@inheritdoc}
*/
public function send(Swift_Mime_Message $message, &$failedRecipients = null)
{
$this->setDomain($this->getDomainFromMessage($message));
return parent::send($message, $failedRecipients);
}
protected function getDomainFromMessage(Swift_Mime_Message $message)
{
$fromArray = $message->getFrom();
if (count($fromArray) !== 1) {
throw new \Exception('Cannot use the mailgun-magic-domain driver when there isn\'t exactly one from address');
}
return explode('@', array_keys($fromArray)[0])[1];
}
}
config/app.php:
'providers' => [
...
\App\Providers\MailgunMagicDomainProvider::class
],
也许对某些人有用,我按如下方式解决了它;
在ServiceProvider
下bootfunction/method;
public function boot()
{
Mail::macro('setConfig', function (string $key, string $domain) {
config()->set('services', array_merge(config('services'), [
'mailgun' => [
'domain' => $domain,
'secret' => $key
]
]));
});
}
呼叫队列
Mail::setConfig($key, $domain)->to(...)->queue(...)