【问题标题】:Sending from multiple Mailgun domains using Laravel Mail facade使用 Laravel Mail 外观从多个 Mailgun 域发送
【发布时间】:2015-04-28 03:46:59
【问题描述】:

我正在使用 Laravel 4 的 Mail::queue() 发送电子邮件,使用内置的 Mailgun 驱动程序。问题是我希望能够从多个 Mailgun 域发送电子邮件,但域必须设置为 app/config/services.php。由于我使用的是Mail::queue(),所以我看不到如何动态设置该配置变量。

有什么方法可以做我要求的吗?理想情况下,我希望能够在调用Mail::queue() 时传入域(Mailgun api 密钥对于我要发送的所有域都是相同的)。

【问题讨论】:

    标签: email laravel dns queue mailgun


    【解决方案1】:

    我使用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(...)
    

    【讨论】:

      【解决方案2】:

      在运行时切换 Laravel Mailer 的配置细节并不难,但是我不知道使用 Mail::queue 外观可以做到什么。这可以通过使用Queue::pushMail::send 的组合来完成(无论如何Mail::queue 就是这样做的)。

      Mail::queue 门面的问题在于传递给闭包的$message 参数是Illuminate\Mail\Message 类型,我们需要修改邮件传输,它只能通过Swift_Mailer 实例访问(以及在Message 类中是只读的)。

      您需要创建一个负责发送电子邮件的类,使用使用您想要的域的 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,但这个替代方案同样紧凑且易于阅读。此代码未经测试,但应该可以工作。

      【讨论】:

      • 这看起来很棒。但我收到exception 'InvalidArgumentException' with message 'Callback is not valid.' in /Illuminate/Mail/Mailer.php:370
      • 看起来 Mail::queue 序列化回调闭包并在运行作业时反序列化它。由于我没有这样做,所以回调只是变成了一个空数组。
      • 是的,你是对的,需要序列化,因为传递给队列的有效负载被编码为 JSON,并且闭包需要序列化,以便它可以作为 JSON 属性值传递.我会在几分钟内更新我的答案并对此进行修复。
      • 我已经更新了我的答案,以便它通过一个序列化的闭包。这应该可以解决问题,尽管它使队列推送代码的可读性降低了一些。如果您想让它更具可读性,您可以创建一个自定义 Facade
      • 是的,我做到了。我还添加了:if (Config::get('mail.driver') !== 'mailgun') { Mail::send($view, $data, $callback); return; },这样即使在使用测试环境时,我也可以对所有电子邮件使用相同的外观。
      【解决方案3】:

      这适用于 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);
      

      【讨论】:

      • 对于 Laravel 5.6(或者也可能在上面),它可以通过将 $mailer = Swift_Mailer::newInstance($transport); 的行更改为 $mailer = new \Swift_Mailer($transport); 来工作,如针对此:github.com/yiisoft/yii2/issues/7848#issue-63849469
      【解决方案4】:

      我的用例与此类似,简而言之,我只是想在运行时自动配置 mailgun 发送域,方法是查看消息的 from 地址字段中设置的域(我在使用Mail::from(...)->send(...) 发送之前即时设置。如果他们将消息中的发件人地址设置为与 mailgun 发送域匹配,这将解决 OP 的用例,这很可能应该这样做。

      我的解决方案注册了一个替代 MailgunTransport,它会覆盖内置的 MailgunTransport 并在发送前设置域。这样我只需要在我的mail.php注册新驱动,然后调用Mail::sendMail::queue即可。

      配置\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
      ],
      

      【讨论】:

        【解决方案5】:

        也许对某人有用,我解决了如下;

        ServiceProvider下的boot函数/方法;

        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(...)
        

        【讨论】:

          猜你喜欢
          • 2015-09-15
          • 1970-01-01
          • 2023-03-18
          • 2017-10-12
          • 2016-04-24
          • 1970-01-01
          • 2016-11-20
          • 2014-04-14
          • 2018-07-01
          相关资源
          最近更新 更多