4

All of my email settings for my app are stored in the database. The user has the option to change those settings, and it all works great. But I am trying to setup a "Send Test Email" function to allow users to test their settings before saving them. When they submit the form for to send the test email, the email is sent via the original settings rather than the new settings.

The form is submitted to SettingsController.php

//  Send a test email
public function sendTestEmail(Request $request)
{
   Log::info(config('mail.host'));  
   //  Just to check the current email host - shows the proper host 
   //  from the database - i.e. smtp.mailtrap.io

   //  Make sure that all of the information properly validates
   $request->validate([
       'host'       => 'required',
       'port'       => 'required|numeric',
       'encryption' => 'required',
       'username'   => 'required'
   ]);

   //  Temporarily set the email settings
   config([
       'mail.host'       => $request->host,
       'mail.port'       => $request->port,
       'mail.encryption' => $request->encryption,
       'mail.username'   => $request->username,
   ]);
   //  Only update the password if it has been changed
   if(!empty($request->password))
   {
       config(['mail.password' => $request->password]);
   }

   //  Try and send the test email
   try
   {
       Log::info(config('mail.host'));
       //  Just to check the new setting - this also shows the correct
       //  email host - which is the newly assigned one via the form 
       //  i.e. smtp.google.com

       Mail::to(Auth::user()->email)->send(new TestEmail());
       return response()->json([
           'success' => true,
           'sentTo'  => Auth::user()->email
       ]);
   }
   catch(Exception $e)
   {
       Log::notice('Test Email Failed.  Message: '.$e);
       $msg = '['.$e->getCode().'] "'.$e->getMessage().'" on line '.
            $e->getTrace()[0]['line'].' of file '.$e->getTrace()[0]['file'];
       return response()->json(['message' => $msg]);
   }
}

In my TestEmail class, I have brought it down to the basics

namespace App\Mail;

//use Illuminate\Bus\Queueable;  //  Commented out to be sure it is not queuing
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
//use Illuminate\Contracts\Queue\ShouldQueue;  //  Commented out to be sure it is not queuing

class TestEmail extends Mailable
{
//    use Queueable, SerializesModels;  //  Commented out to be sure it is not queuing

 /**
 * Create a new message instance.
 *
 * @return void
 */
public function __construct()
{
    //
}

/**
 * Build the message.
 *
 * @return $this
 */
public function build()
{
    return $this->subject('Test Email From '.config('app.name'))->markdown('email.testEmail');
}
}

Even though the logs are showing the updated smtp host for the config setting, the message is still being sent out via the original setting - i.e. smtp.mailtrap.io.

Ron Butcher
  • 489
  • 7
  • 16

4 Answers4

3

TL;DR

An immediate answer to your question, please refer to this following code tested on Laravel 5.8:

$transport = app('swift.transport');
$smtp = $transport->driver('smpt');
$smpt->setHost('PUT_YOUR_HOST_HERE');
$smpt->setPort('THE_PORT_HERE');
$smpt->setUsername('YOUR_USERNAME_HERE');
$smpt->setPassword('YOUR_PASSWORD_HERE');
$smpt->setEncryption('YOUR_ENCRYPTION_HERE');

Why setting up config on the fly does not work?

In laravel architecture, it is first had all the service providers registered and that includes the MailServiceProvider. See your config/app.php

    // inside config/app.php
    ...
    Illuminate\Mail\MailServiceProvider::class,
    ...

And the config will be loaded before it reaches your route, see Illuminate\Mail\TransportManager

    /**
     * Create an instance of the SMTP Swift Transport driver.
     *
     * @return \Swift_SmtpTransport
     */
    protected function createSmtpDriver()
    {
        $config = $this->app->make('config')->get('mail');

        // The Swift SMTP transport instance will allow us to use any SMTP backend
        // for delivering mail such as Sendgrid, Amazon SES, or a custom server
        // a developer has available. We will just pass this configured host.
        $transport = new SmtpTransport($config['host'], $config['port']);

        if (isset($config['encryption'])) {
            $transport->setEncryption($config['encryption']);
        }
        //... rest of the code
   }

so the way I deal with this using TransportManager's drivers method to pick up desired driver and set the config I want, since above code we can see lots of usage of its api.

Hope this helps

umar_
  • 491
  • 5
  • 17
2

I am facing the same issue I just use

config(['mail.driver' => 'smtp']);

when I Debug

dd(config('mail.driver'));

Configuration is all fine

I just review a comment here by kfirba

I'm pretty sure that the issue is caused because when laravel registers the mailer key in the IoC Container it uses the original config. Changing that won't cause laravel to re-define the mailer key. If you take a look at MailerServiceProvider you will see that it is deferred which means that once you call it, it will instantiate the object and it uses a singleton. I believe that you've used the Mail::send() method elsewhere in the application which led to registering the mailer key in the application. Since it's a singleton it won't read your configuration files again when you re-use it.

Solution:

config(['mail.driver' => 'smtp']);
(new Illuminate\Mail\MailServiceProvider(app()))->register();

Works on Laravel 8

Raj Omer Mustafa
  • 81
  • 1
  • 1
  • 5
0

You can set/change any configuration on the fly using Config::set:

Config::set('key', 'value'); So, to set/change the port in mail.php you may try this:

Config::set('mail.port', 587); // default

Note: Configuration values that are set at run-time are only set for the current request, and will not be carried over to subsequent requests

Youssef mahmoed
  • 373
  • 2
  • 9
  • I tried changing the way I am assigning the config variables to Config::set, but that did not change anything. What is really weird is that when I log the settings after changing, it does show the new setting. – Ron Butcher Mar 30 '19 at 16:38
0

When your app instance sends its first email the Laravel MailManager resolves a new mailer, using the current config values, and caches it by adding it to an internal mailers array. On each subsequent mail that you send, the same cached mailer will be used.

The MailManager has a method to clear its mailer cache:

Mail::forgetMailers();

Now when you send another mail the MailManager must resolve a new mailer, and it will re-read your config while doing so.

Martijn
  • 73
  • 1
  • 6