Backstory
In my company website, almost every user(employee) has their own email configuration and some models also have their own email configuration. All configurations are saved to a database table so they could be changed/added during runtime. All emails are sent to queue and they are processed by queue worker.
I will try to provide a clean example of how I managed to change configuration during runtime. I cropped out most of the unnecessary code and changed some variables and text to make it easily readable.
Logic
For example, if I wish that an email is sent out from authenticated user's email, after they submit a certain form, then I will do it like so:
$email_data = array(
'header'=>__('Info') ,
'subheader'=>__('This is an automated email. Do not reply.'),
'mail_to'=> 'test@test.test'
'content'=> ...
);
sendEmailFromConfig(new mailTemplate($email_data), $auth_user->email_config_id);
The sendEmailFromConfig is function is from helper class so it can be called from anywhere.
For the first argument I pass in mailTemplate, which derives from mailable, with the custom data that I want it to use. The the second argument is email configuration id.
The mailtemplate class:
class mailTemplate extends Mailable
{
use Queueable, SerializesModels;
public $data;
public function __construct($data)
{
$this->data = $data;
}
public function build()
{
// optional data (for view)
$email_data['url'] = $this->data['url'];
$email_data['data1'] = $this->data['data1'];
$email_data['data2'] = $this->data['data2'];
$email_data['data3'] = $this->data['data3'];
// required
$email_data['view_name'] = 'emails.content.mailTemplate';
$email_data['reply_to'] = isset($this->data['reply_to']) ? $this->data['reply_to'] : '';
$email_data['cc'] = [];
$email_data['bcc'] = [];
$email_data['email_to'] = isset($this->data['email_to']);
$email_data['email_subject'] = $this->data['email_subject'];
logEmail($this->data); // Another helper function to log sent emails
return $this
->subject($email_data['email_subject'])
->to($email_data['email_to'])
->cc($email_data['cc'])
->bcc($email_data['bcc'])
->replyTo($email_data['reply_to'])
->view($email_data['view_name'], ['email_data' => $email_data]);
}
}
The sendEmailFromConfig function, among other unrelated things, just generates a new job like this:
function sendEmailFromConfig($data, $config_id) {
$config = EmailConfiguration::find($config_id);
dispatch(new SendEmailJob($data, $config));
}
Notice, the $data value comes from the mailable which was passed as first the argument.
The SendEmailJob syntax is like any other job you can find in laravel documentation, but what makes the magic happen is this:
$temp_config_name = 'smtp_rand_' . str::random(5); // every email is sent from different config to avoid weird bugs
config()->set([
'mail.mailers.' . $temp_config_name . '.transport' => 'smtp',
'mail.mailers.' . $temp_config_name . '.host' => $config->host,
'mail.mailers.' . $temp_config_name . '.port' => $config->port,
'mail.mailers.' . $temp_config_name . '.username' => $config->username,
'mail.mailers.' . $temp_config_name . '.password' => $config->password,
'mail.mailers.' . $temp_config_name . '.encryption' => $config->encryption,
'mail.mailers.' . $temp_config_name . '.from' =>
[
//FIXME TWO BOTTOM LINES MUST BE GIVEN A DO OVER PROBABLY
'address' => $config->from_address,
'name' => $config->from_name),
],
'mail.mailers.' . $temp_config_name . '.auth_mode' => $config->auth_mode,
]);
Mail::mailer($temp_config_name)->send($data); // sends email
This sets a new config in cache just before sending the job to the worker service (which handles queues). This should also work without queues - in that case you should first try without the $temp_config_name variable.
This solution might be considered wrong and it's definitely not pretty but this is the only way that I managed to get it working properly. Notice how the $temp_config_name is changed in every new job, even if same data is being sent from same email config - this fixed a bug. The bug was that, after first successful email from a configuration, the next email wouldn't be sent out. I don't know why this bug happened, but setting a different config name every time fixed the issue.
I should mention that these temporary configs will start piling up in the cache every time you send an email. I haven't had the time to find a good solution to this yet, if anyone does know what to do, then please tell (or if you have a better solution than what I'm doing). I know that restarting the worker service will automatically delete these temporary configs. I guess one way would be to to restart the worker service after every x jobs.
I also want to say that I'm a beginner in PHP and Laravel and I do think that there might be a better solution out there, I just wasn't able to find it. I also want to say that I left out a lot of code (for example some try catches, logging function calls, some applicaiton specific functionality etc ..), I just wanted to show the core logic.