Updating config on runtime for a user - php

I am using Laravel 5.1
I created a function to get smtp info from db for a user $mail_config=STMPDetails::where('user_id',36)->first() and then I can just call config helper function and pass the array to set config value config($mail_config). and then I call Mail::queue function.
but before it reaches createSmtpDriver#vendor/laravel/framework/src/Illuminate/Mail/TransportManager.php where it reads the configuration again to send the mail, mail config are changed to the one specified in .env file.
Another thing to note is Mail sending function is in a Listener
I am not able to figure out where can I call the function such that configuration changes are retained before mail is sent.
Thanks,
K

This should work:
// Set your new config
Config::set('mail.driver', $driver);
Config::set('mail.from', ['address' => $address, 'name' => $name]);
// Re execute the MailServiceProvider that should use your new config
(new Illuminate\Mail\MailServiceProvider(app()))->register();

Since the default MailServiceProvider is a deferred provider, you should be able to change config details before the service is actually created.
Could you show the contents of $mail_config? I'm guessing that's the problem. It should be something like
config(['mail.port' => 587]);
Update - tested in 5.1 app:
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Sent normally to recipient.
config(['mail.driver' => 'log']);
Mail::queue('emails.hello', $data, function ($mail) use ($address) {
$mail->to($address);
});
->> Not sent; message logged.

Related

Send Laravel verification email with a custom template using aws ses

What I am trying to do.
My goal is to send a custom blade template for the built-in Laravel email verification functionality. Right now in my AuthServiceProvider inside the boot function I call VerifyEmail::toMailUsing function and attempt to create an email based on the send_email function I already have. it takes a template name, email and params, finds the blade file, and inserts the params and sends the email with aws ses.
VerifyEmail::toMailUsing(function ($notifiable, $url) {
$params = [
'first_name' => $notifiable->first_name,
'verify_url' => $url
];
$generate_email = new SendEmailController();
$generate_email->send_email('verify', $notifiable->email, $params);
});
The issue:
The email gets sent successfully but throws an error:
I understand it must have to do with the toMailUsing, but just cant point my finger to it.
Expected outcome should be: no error :)

Laravel - send mail to queue with different connection

I use this code to send invoice
Mail::send($email_template, $view_vars, static function ($m) use ($customer, $company, $email_subject, $users, $invoice, $invoice_name) {
$m->from($company->send_email, $company->name);
$m->to($users, $customer->getFullName())->subject($email_subject);
$m->attachData($invoice, $invoice_name);
});
Problem that it need some time to send email with atache.
Decision - to use Queue and change Mail::send() to Mail::queue()
But I use iron.io as as QUEUE Driver. And by default email with attached will go to iron.io - so it also need some time.
I whant to use database connection for this case.
can I use code:
Mail::queue(.......)->onConnection('database');
?
Table Jobs already exist.
After this need I some additional code that emails was realy sended in background ot it will be done automaticaly?
Tha best way is to use local connection method (databases) for all queues.
According to Laravel docs, you can use onConnection with Queues.
https://laravel.com/docs/8.x/queues#chain-connection-queue
To do extra processing after mail has been sent, you need to listen to Laravel's MailSent event.
https://laravel.com/api/5.5/Illuminate/Mail/Events/MessageSent.html
Define a listener in EventServiceProvider
'Illuminate\Mail\Events\MessageSent' => [
'App\Listeners\OnMessageSent',
]
Define handle function in the listener.
use Illuminate\Mail\Events\MessageSent;
class OnMessageSent {
// listen for MailSent event
public function handle(MessageSent $event)
{
if ($event) {
// do something here
dd($event);
}
}
}

Laravel Config::set() does not seem to work with Queued Jobs

I need to modify the mail SMTP parameters such as MAIL_HOST and MAIL_USERNAME dynamically.
For, this I am using Config::set() to set these values dynamically.
# This code works
Config::set('mail.host', 'smtp.gmail.com');
Mail::to('user#example.com')->send(new myMailable());
The above code works if I do not queue the mail.
The moment I queue it, it appears that Config::set() fails to set the values.
Test to confirm Config::set() not working with queued jobs -
I created a simple job and put the below code in the handler.
public function handle()
{
# set the config
Config::set('mail.host', 'smtp.gmail.com');
# confirm config has been set correctly
logger('Setting host to = [' . config('mail.host') . ']');
}
The above code creates the below log entry.
Setting host to = []
Why can I not change the Config on-the-fly for queued jobs? And how to solve this?
This is because the Queue worker doesn't use the current request. It is a stand-alone process, not being interferred by config settings.
To let this work, you need to use a Job. The dispatch function takes your data and sends it through to the job itself. From your controller, call:
JobName::dispatch($user, $settings);
In the job you set the variables accordingly:
public function __construct($user, $settings)
{
$this->user = $user;
$this->settings = $settings;
}
Then in the handle method:
\Notification::sendNow($this->user, new Notification($this->settings));
You can use a normal notification for this.
Do not for get to add implements ShouldQueue to your job!

Laravel - changing email settings on the fly is not working

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

Disable Laravel email during db seed

I use Mandrill mail driver for tests. I have a remote staging, that I seed after deploy. And during seeding I try to disable email sends, that are linked to certain events.
Placing this in seeder:
Config::set('mail.driver', 'log');
Config::set('mail.pretend', true);
Has no effect. I don't understand why. I place this in root DatabaseSeeder#run or/and in child seeders — the same. Calls to Mandrill are still performed.
Is there a solution for this problem?
The reason your
Config::set('mail.driver', 'log');
Config::set('mail.pretend', true);
aren't working is because the mail object doesn't check these values before sending mail. Whaaaaaaaa?. If you take a look at the sendSwiftMessage method in the mailer class
#File: vendor/laravel/framework/src/Illuminate/Mail/Mailer.php
protected function sendSwiftMessage($message)
{
if ($this->events)
{
$this->events->fire('mailer.sending', array($message));
}
if ( ! $this->pretending)
{
$this->swift->send($message, $this->failedRecipients);
}
elseif (isset($this->logger))
{
$this->logMessage($message);
}
}
You can see the class checks $this->pretending, and not the configuration, before deciding if it should send the mail or not. So what sets pretending? That's in the MailServiceProvider class's register method.
public function register()
{
//...
$pretend = $app['config']->get('mail.pretend', false);
$mailer->pretend($pretend);
//...
}
When Laravel boots up and registers each service provider, it eventually registers the mail service provider and that's when it reads the configuration, and then tells the mailer if it should "pretend" or not. By the time you're calling this in your seeder, the mailer's already loaded it's configuration value.
Fortunately, there's a pretty easy solution. The mailer object is a singleton/shared service, and has public methods available to control if it should pretend or not. Just call the pretend method yourself instead of setting configuration values
Mail::pretend(true); //using the `Mail` facade to access the mailer object.
you should be able to turn the mailer off programatically.
This is an answer for Laravel 5.7, because pretend doesn't exists:
If you want to disable mail while seeding the database, you could simply 'abuse'
Mail::fake()
I think in two possibilities, you can try:
You can set the command to enable the mail pretend on-the-fly:
Mail::pretend();
The db seed are running with more than one request:
As is write here:
Configuration values that are set at run-time are only set for the
current request, and will not be carried over to subsequent requests.
So you can try set this config over requests, like a session, than finish in the end of the seeding.

Categories