32

I configured laravel's mail service with mandrill driver. No problems here!

Now, at certain point of my application, I need to send a mail via gmail.

I did something like:

// backup current mail configs
$backup = Config::get('mail');

// rewrite mail configs to gmail stmp
$new_configs = array(
    'driver' => 'smtp',
    // ... other configs here
);
Config::set('mail', $new_configs);

// send the email
Mail::send(...

// restore configs
Config::set('mail', $backup);

This doens't work, laravel always uses the mandrill configurations. Looks like he initiates mail service at script startup and ignores whatever you do during execution.

How do you change mail service configs/behaviour during execution?

cmancre
  • 1,061
  • 3
  • 11
  • 23

7 Answers7

60

You can create a new Swift_Mailer instance and use that:

// Backup your default mailer
$backup = Mail::getSwiftMailer();

// Setup your gmail mailer
$transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 465, 'ssl');
$transport->setUsername('your_gmail_username');
$transport->setPassword('your_gmail_password');
// Any other mailer configuration stuff needed...

$gmail = new Swift_Mailer($transport);

// Set the mailer as gmail
Mail::setSwiftMailer($gmail);

// Send your message
Mail::send();

// Restore your original mailer
Mail::setSwiftMailer($backup);
Bogdan
  • 43,166
  • 12
  • 128
  • 129
  • 3
    it works like a charm. for the ones who might look for, SmtpTransport comes from use Swift_SmtpTransport as SmtpTransport; – cmancre Oct 24 '14 at 12:36
  • I used this to make a difference between "regular" e-mails and "system" e-mails. Works great, thanks! – Jurgen Apr 03 '15 at 06:44
  • 3
    That's great, but doesn't work for `Mail::queue($mail)`, why? – Suge Nov 14 '16 at 14:47
  • Emails sent with Mail::queue are sent on the worker process, not the current context where you are switching the mailer, to queue emails and send them like this, use jobs. – Alexandru Eftimie Mar 27 '17 at 14:47
  • @Suge please see my answer below which extends the accepted (and correct) answer. – dev7 Sep 09 '17 at 23:14
  • @bogdan I was using config::set and config::get for this purpose and it was working fine for me but the problem in it was the configurations were sharing among concurrent requests. You are setting swift mailer here. Do this setting be shared among any concurrent request trying to do mail::send?? – developer34 Mar 02 '18 at 14:33
  • It seems that there does not exists anymore a function `newInstance` in `Swift_SmtpTransport`. I had to do call it with `$transport = new Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl');` – Adam Jul 09 '19 at 17:37
  • Also, it seems that the mail address and the name are taken from `config/mail.php` from `from`. How do you override this? – Adam Jul 09 '19 at 18:08
  • I figured out how to fix it: https://stackoverflow.com/a/56965347/2311074 and sorry I just realized this answer is 5 years old.. – Adam Jul 10 '19 at 07:18
20

A bit late to the party but just wanted to extend the accepted answer and throw in my 2 cents, in case it saves someone time. In my scenario each logged in user had their own SMTP settings BUT I was sending mails using a queue, which caused the settings to go back to default after setting them. It also created some concurrent emails issues. In short, the problem was

$transport = Swift_SmtpTransport::newInstance($user->getMailHost(), $user->getMailPort(), $user->getMailEncryption());
$transport->setUsername($user->getMailUser());
$transport->setPassword($user->getMailPassword());
$mailer = new Swift_Mailer($transport);
Mail::setSwiftMailer($mailer);
//until this line all good, here is where it gets tricky

Mail::send(new CustomMailable());//this works
Mail::queue(new CustomMailable());//this DOES NOT WORK

After few moments of keyboard bashing I realized that the queue is running on a separate process and therefore Mail::setSwiftMailer does not affect it at all. It simply picks up the default settings. Therefore the configuration change had to happen at the actual moment of sending the email and not when queuing it.

My solution was to extend the Mailable Class as following.

app\Mail\ConfigurableMailable.php

<?php

namespace App\Mail;

use Illuminate\Container\Container;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Mail\Mailable;
use Swift_Mailer;
use Swift_SmtpTransport;

class ConfigurableMailable extends Mailable
{
    /**
     * Override Mailable functionality to support per-user mail settings
     *
     * @param  \Illuminate\Contracts\Mail\Mailer  $mailer
     * @return void
     */
    public function send(Mailer $mailer)
    {
        $host      = $this->user->getMailHost();//new method I added on User Model
        $port      = $this->user->getMailPort();//new method I added on User Model
        $security  = $this->user->getMailEncryption();//new method I added on User Model

        $transport = Swift_SmtpTransport::newInstance( $host, $port, $security);
        $transport->setUsername($this->user->getMailUser());//new method I added on User Model
        $transport->setPassword($this->user->getMailPassword());//new method I added on User Model
        $mailer->setSwiftMailer(new Swift_Mailer($transport));

        Container::getInstance()->call([$this, 'build']);
        $mailer->send($this->buildView(), $this->buildViewData(), function ($message) {
            $this->buildFrom($message)
                 ->buildRecipients($message)
                 ->buildSubject($message)
                 ->buildAttachments($message)
                 ->runCallbacks($message);
        });
    }
}

And then changed CustomMail to extend ConfigurableMailable instead of Mailable:

class CustomMail extends ConfigurableMailable {}

This makes sure that even calling Mail::queue(new CustomMail()) will set the per-user mail settings right before sending. Of course you will have to inject the current user to the CustomMail at some point i.e Mail::queue(new CustomMail(Auth::user()))

While this may not be the ideal solution (i.e if trying to send bulk email it is better to config the mailer once and not on every email sent), I like its simplicity and the fact that we do not need to change the global Mail or Config settings at all, only the $mailer instance is being affected.

Hope you find it useful!

dev7
  • 6,259
  • 6
  • 32
  • 65
  • Why do you need a for each user a unique SMTP setting? – Adam Dec 10 '18 at 11:52
  • 1
    @Adam the host, port and encryption stay the same usually but the username & pwd are needed to retrieve the emails for that user from the SMTP server. Do you have a better way? – dev7 Dec 10 '18 at 17:03
  • I can think of various scenarios why a web app will send mails from different mail accounts like "register@example.com", "info@example.com" or "billing@example.com". But I wonder what kind off web app needs for each user a seperate mail. Do you write user XYZ from the mail "userXYZ@example.com"? – Adam Dec 10 '18 at 17:11
  • 1
    @Adam yes it's for a CRM like application where the user communicates directly with the client, then the app needs to monitor for a response. – dev7 Dec 10 '18 at 18:41
  • @dev7 Great answer. This is ideal if you don't want to mess up your main config. Side question for you: How are you monitoring for a response? I have had zero luck in finding any libraries that listen for incoming email. – Symphony0084 Dec 10 '19 at 00:08
  • @dev7 Also - I found that adding `@throws \ReflectionException` to the PHPDoc was useful for your ConfigurableMailable class – Symphony0084 Dec 10 '19 at 00:18
  • 1
    @dev7 Thanks, this is what I was looking for. Is my use case: an application that needs to set dinamically smtp settings. A lot of time saved. – paulmartimx Jun 19 '20 at 21:38
  • How to pass variables from a CustomMail class to the send () method? – Andris Briedis Jan 24 '21 at 21:31
  • 1
    Been really useful for a different use case for me. MailGun service is nowadays blocked by microsoft mail services like hotmail.com, live.com, outlook.com and I needed to use a separate custom SMTP sender for our microsoft email users. I checked `$this->to` see if the email domain is blocked and then used a custom Swift_SmtpTransport instance to send the email. Otherwise I use my default email config simply calling `parent::send($mailer);` – İlter Kağan Öcal Jul 30 '21 at 22:47
  • How to send in Queue? The current solution not working with Queue. I'm using Laravel 9. – Suresh Ramani Nov 16 '22 at 09:32
16

For Laravel version 7.x and above, you can now state the mail driver to use while sending an email. All you need to configure all your connections & credentials properly in config/mail.php. Once configured, you can specify the name of the driver via mailer() function as below:

Mail::mailer('postmark')
    ->to($request->user())
    ->send(new OrderShipped($order));

I hope it helps someone.

Choxx
  • 945
  • 1
  • 24
  • 46
  • 2
    Thanks for the update, it really helped, just a quick note, the path is config/mail.php, without app/. – Clone Jun 10 '20 at 12:04
  • 1
    For anyone with older codebases: Laravel is still backwards-compatible with config/mail.php files from <= 6.x. To use this functionality, make sure you have a 7.x+ config/mail.php file (you should have mail.default, not mail.driver). See https://github.com/laravel/framework/blob/9535a80f18dd097ddd8f9a9fc6eb131cae83f9f4/src/Illuminate/Mail/MailManager.php#L397 – AlbinoDrought Feb 09 '22 at 18:48
5

You can set on the fly mail settings:

Config::set('mail.encryption','ssl');
Config::set('mail.host','smtps.example.com');
Config::set('mail.port','465');
Config::set('mail.username','youraddress@example.com');
Config::set('mail.password','password');
Config::set('mail.from',  ['address' => 'youraddress@example.com' , 'name' => 'Your Name here']);

Maybe you can store settings values in config/customMail.php and retrive them whith Config::get('customMail')

Giacomo
  • 79
  • 1
  • 2
  • 2
    But its not always works, because "$app" may already defined. So it won't affects the flow. – Shankar Thiyagaraajan Dec 26 '16 at 10:38
  • This will probably not work as expected with concurrent users sending emails at the same time or especially when using queues to send emails. – dev7 Sep 09 '17 at 23:18
  • 1
    @yani Can you explain your comment why this will not work as expected with concurrent users and queues? – developer34 Mar 05 '18 at 13:49
  • @ShankarThiyagaraajan Yes you're right, you need to define your new config before using it. How to define mail config? see the details answer here https://stackoverflow.com/a/48382559/4494207 – ibnɘꟻ Mar 07 '19 at 05:06
3

For Laravel 6 you should use it like this:

// Backup your default mailer
$backup = Mail::getSwiftMailer();

// Setup your gmail mailer
$gmail = new \Swift_SmtpTransport('smtp.gmail.com', 465, 'ssl');

// Set the mailer as gmail
Mail::setSwiftMailer(new \Swift_Mailer($gmail));

// Send your message
Mail::send();

// Restore your original mailer
Mail::setSwiftMailer($backup);
Jakub Adamec
  • 953
  • 16
  • 22
1

Using only setSwiftMailer as explained by Bogdan didn't work for me, because then the from and adress options where still taken from config/mail.php. Also it wasn't working with queues.

I created a package called multiMail to solve this.

One can setup the mail adress and host/provider/username/passwort etc in /config/multimail.php and then one can send the mails using

\MultiMail::from('office@example.com')->send(new MailableDummy()));
\MultiMail::from('contact@otherdomain.de')->send(new MailableDummy()));

or queue it

\MultiMail::from('office@example.com')->queue(new MailableDummy()));
Adam
  • 25,960
  • 22
  • 158
  • 247
0

Even easier it is to execute following code, just before sending an email, after you have written over the mail-configuration with config :

app()->forgetInstance('swift.transport');
app()->forgetInstance('swift.mailer');
app()->forgetInstance('mailer');