1
votes

I'm developing a Multi Tenant (multiple database) with Laravel v5.7 and I'm successful in sending queue emails.

In some specific situations, I'd like to send on-demand notifications with 'delay', similar to the guide On-Demand Notifications, but informing the SMTP settings that should be used before sending.

I've developed a class that changes the values of config().

app/Tenant/SmtpConfig.php

class SmtpConfig
{
    public static function setConnection(SmtpConta $conta = null)
    {
        // get connection default settings
        $config = config()->get("mail");

        // populate connection default settings
        foreach ($config as $key => $value) {
            if ( $key == 'host' )      { $config[$key] = $conta->mail_host ?? $config[$key]; }
            if ( $key == 'from' )      { $config[$key] = [
                'address' => ( $conta->mail_host === 'smtp.mailtrap.io' ) ? $config[$key]['address'] : $conta->mail_username,
                'name' => $conta->conta ?? $config[$key]['name']
            ]; }
            if ( $key == 'username' )  { $config[$key] = $conta->mail_username ?? $config[$key]; }
            if ( $key == 'password' )  { $config[$key] = !empty($conta->mail_password) ? $conta->mail_password : $config[$key]; }
        }

        $config['encryption'] = ( $conta->mail_host === 'smtp.mailtrap.io' ) ? null : 'ssl';

        // set connection default settings
        config()->set("mail", $config);
    }

}

... and I call this SmtpConfig class in notification:

/**
  * Create a new notification instance.
  *
  * @param $conta
  * @param $subject
  * @return void
  */
  public function __construct(SmtpConta $conta = null, $subject = null)
  {
        $this->conta = $conta;
        $this->subject = $subject;

        $when = \Carbon\Carbon::now()->addSecond(100);

        $this->delay($when);

        app(\App\Tenant\SmtpConfig::class)::setConnection($this->conta);
  }

I can send the 'delayed' notification successfully, but apparently it always uses the default values of the .env file.

Now I'm not sure if where I'm calling the class makes any sense or even how can I tell the notification what SMTP configuration it should use.

2

2 Answers

3
votes

I'm currently facing a similar challenge, on a Laravel 5.2 codebase using the Notification backport library.

This is an example of my solution, similar to Kit Loong's suggestion. We just extend the Illuminate\Notifications\Channels\MailChannel class and override the send() method.

You'll need to be able to determine the SMTP config from the recipient(s), or notification objects, so you'll need to edit my example as necessary.

Also this assumes your app is using the default Swift_Mailer so YMMV...

<?php

declare (strict_types = 1);

namespace App\Notifications\Channels;

use Illuminate\Notifications\Channels\MailChannel;
use Illuminate\Notifications\Notification;

class DynamicSmtpMailChannel extends MailChannel
{
    /**
     * Send the given notification.
     *
     * @param  mixed  $notifiable
     * @param  \Illuminate\Notifications\Notification  $notification
     * @return void
     */
    public function send($notifiable, Notification $notification)
    {
        //define this method on your model (note $notifiable could be an array or collection of notifiables!)
        $customSmtp = $notifiable->getSmtpConfig(); 

        if ($customSmtp) {
            $previousSwiftMailer = $this->mailer->getSwiftMailer();

            $swiftTransport = new \Swift_SmtpTransport(
                $customSmtp->smtp_server, 
                $customSmtp->smtp_port,
                $customSmtp->smtp_encryption
            );
            $swiftTransport->setUsername($customSmtp->smtp_user);
            $swiftTransport->setPassword($customSmtp->smtp_password);

            $this->mailer->setSwiftMailer(new \Swift_Mailer($swiftTransport));
        }

        $result = parent::send($notifiable, $notification);

        if (isset($previousSwiftMailer)) {
            //restore the previous mailer
            $this->mailer->setSwiftMailer($previousSwiftMailer);
        }

        return $result;
    }
}

It may also be beneficial to keep an ephemeral store of custom swift mailers so you can re-use them in the same invokation/request (think about long-running workers) - like a collection class where a hash of the smtp config is used as the item key.

Best of luck with it.

Edit: I should probably mention you may need to bind this in the service container. Something like this should suffice:

// in a service provider
public function register()
{
    $this->app->bind(
        \Illuminate\Notifications\Channels\MailChannel::class
        \App\Notifications\Channels\DynamicSmtpMailChannel::class
    );
}

Or alternatively, register it as a seperate notification channel.

0
votes

I think you can also refer to this implementation.

https://stackoverflow.com/a/46135925/6011908

You could execute by passing custom smtp configs.

$transport = new Swift_SmtpTransport(
    $customSmtp->host, 
    $customSmtp->port, 
    $customSmtp->encryption
);