Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

simonw's avatar

Change Email Driver inside Mailable

Hi all,

I've written a Transport driver for SendGrid. Very simple. It's a fully working driver, that I set up by extending swift.transport. Like so :

$this->app['swift.transport']->extend('sendgrid', function ($app) {
            $config = $this->app['config']->get('services.sendgrid', []);

            $httpclient = new \GuzzleHttp\Client(
                Arr::add($config['guzzle'] ?? [], 'connect_timeout', 60)
            );

            return new \App\Mail\Transport\SendGridTransport(
                $httpclient,
                $config['secret'],
                $config['endpoint'] ?? null
            );
        });

Tested it and everything works beautifully. However, there are many locations across my app that I _don't _ want to use SendGrid. I don't want production based dev emails going through SendGrid. It's a waste of the quota.

Rather than finding every single location I do Mail:to()->send(new MyEmailMailable()); is it possible to change the driver on the Mailable object itself?

I was thinking maybe a trait that overrides send() and just changes the driver. I can't seem to figure out how to do it though. Any ideas?

0 likes
2 replies
simonw's avatar

To add to this, understandably a Mailable shouldn't really know about it's Transport, but it does take a mailer object in send, so it seemed as good as any: send(MailerContract $mailer).

Another option could be to create a 3rd Transport class which acts as a Transport router. I could have that class delegate to any of the other drivers as I wish. This might be more favourable, but if my first option is doable that might be quicker for now.

simonw's avatar

No answer so here is my solution. Others may find this useful.

Use ->extend as above to create a custom Transport driver. Make this driver act as a resolver. When ->send() is called, resolve the transport property and delegate to the driver that you set.

Example:

/**
     * {@inheritdoc}
     */
    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
    {
        // forward this on to the declared transport (driver)
        return $this->transport->send($message, $failedRecipients);
    }
/**
     * Change the mail driver at run-time
     */
    public function setDriver($driver, $config = [])
    {
        if (is_string($driver) && $driver != 'smtp') {
            $driver = new $driver($this->client, $config['secret'], $config['endpoint'] ?? null);
            $this->transport = $driver;
        }

        // set to smtp driver
        if ($driver === 'smtp') {
            $this->transport = $this->createSmtpDriver();
        }

        if (!($this->transport instanceof \Swift_Transport)) {
            $name = get_class($this->transport);
            throw new \Swift_TransportException("$name not found or is not an instance of \Swift_Transport");
        }

        return $this;
    }

And, finally if I want a specific set of Mailable's to use a different transport (Maybe admin emails don't want to be run through SendGrid)

use App\Mail\Transport\SmtpDriver;

class AdminEmailError extends Mailable
{
    use Queueable, SerializesModels, SmtpDriver;
    
    ...
}
trait SmtpDriver
{
    /**
     * Send the message using the given mailer.
     *
     * @param  \Illuminate\Contracts\Mail\Mailer  $mailer
     * @return void
     */
    public function send(MailerContract $mailer)
    {
        // if we are using our custom Transport Router, set the driver to use smtp
        $transport = $mailer->getSwiftMailer()->getTransport();
        if (method_exists($transport, 'setDriver')) {
            $transport->setDriver('smtp');
        }

        parent::send($mailer);
    }
}

Please or to participate in this conversation.