How to Send Email in Laravel Using Custom Config Per Email — Laravel 11
In Laravel, sending an email is straightforward with the Mail Facade. However, things get a bit tricky when you want to use dynamic configurations for sending emails, such as having each user send emails from their own SMTP settings. In this article, we’ll explore how to achieve this.
Config Email Dynamically from Database? No problem
With this methods, you can also load configurations from the database, allowing you to control your Mail settings dynamically without having to change the .env
file. This approach provides greater flexibility and adaptability, especially in multi-tenant applications where different users may require different email configurations.
The Issue
Sending an email in Laravel is simple and can be done using the Mail Facade:
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->send(new OrderShipped($order));
Reference: Laravel Mail Documentation
But what if you need to use dynamic configurations for sending emails? For example, you want each user to have their own SMTP settings and send emails from their own email service.
User Model
First, you need to update your User
model to include email preferences:
//User.php
...
protected $casts = [
'email_preferences' => 'json'
];
...
And in your migration file, add the following line:
$table->json('email_preferences')->nullable();
Typically, $user->email_preferences
would look like this :
{
"host": "mailpit",
"smtpPort": "1025",
"smtpUsername": null,
"smtpPassword": null,
"fromEmail": "hello@example.com",
"fromEmail": "Laravel Application"
}
Controller
Here’s a basic controller to handle email sending:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class OrderShipmentController extends Controller
{
public function store(Request $request): RedirectResponse
{
$order = Order::findOrFail($request->order_id);
// Ship the order...
Mail::to($request->user())->send(new OrderShipped($order));
return redirect('/orders');
}
}
Example from original document https://laravel.com/docs/11.x/mail#sending-mail
How to Handle Dynamic Email Configurations
There are two main ways to handle dynamic email configurations in Laravel:
Method 1: Override Configs
Step 1: Save Original Config
First, save the default mailer config using the config()
global function and restore it after sending the email.
public function getDefaultConfig(): array
{
return [
'host' => config('mail.mailers.smtp.host'),
'port' => config('mail.mailers.smtp.port'),
'username' => config('mail.mailers.smtp.username'),
'password' => config('mail.mailers.smtp.password'),
'fromEmail' => config('mail.from.address'),
'fromName' => config('mail.from.name'),
]
}
Step 2: Override Original Configs
Use the Config
Facade to set the config dynamically on demand.
protected function overrideMailConfig(array $mailConfigs): void
{
Config::set('mail.mailers.smtp.host', $mailConfigs['host']);
Config::set('mail.mailers.smtp.port', $mailConfigs['port']);
Config::set('mail.mailers.smtp.username', $mailConfigs['Username'] );
Config::set('mail.mailers.smtp.password', $mailConfigs['Password']);
Config::set('mail.from.address', $mailConfigs['fromEmail']);
Config::set('mail.from.name', $mailConfigs['fromName']);
}
Step 3: Restore to Original Configs
After sending the email, restore the default configs.
protected function restoreMailConfig(array $mailConfigs): void
{
// Assuming you have stored the original configuration somewhere
Config::set('mail.mailers.smtp.host', $mailConfigs['host']);
Config::set('mail.mailers.smtp.port', $mailConfigs['port']);
Config::set('mail.mailers.smtp.username', $mailConfigs['username']);
Config::set('mail.mailers.smtp.password', $mailConfigs['password']);
Config::set('mail.from.address', $mailConfigs['fromEmail']);
Config::set('mail.from.name', $mailConfigs['fromName']);
}
Step 4: Usage of Functions
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class OrderShipmentController extends Controller
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$user = Auth::user();
$order = Order::findOrFail($request->order_id);
$defaultMailConfig = $this->getDefaultConfig();
// Override email config
$this->overrideMailConfig($user->email_preferences);
// Ship the order...
// Send email
Mail::to($request->user())->send(new OrderShipped($order));
// Restore to Orginal configs
$this->restoreMailConfig($defaultMailConfig)
return redirect('/orders');
}
}
Why Not Use This Method?
This method overrides the original application config, which can affect other processes if you are using Laravel Octane or other stateful services. For more information, check out the Laravel Octane Documentation.
Need to Test
Method 2: Override Email Config with setSymfonyTransport
and createSymfonyTransport
Step 1: Clone `Mailer` and Create New SymfonyTransport
Create a new SymfonyTransport with $mailer->createSymfonyTransport
.
Step 2: Set SymfonyTransport
Set the SymfonyTransport with Mail::setSymfonyTransport
.
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class OrderShipmentController extends Controller
{
/**
* Ship the given order.
*/
public function store(Request $request): RedirectResponse
{
$user = Auth::user();
$order = Order::findOrFail($request->order_id);
// Get default Email Config
$mailer = clone app('mailer');
// Override email config
$mailer->setSymfonyTransport(Mail::createSymfonyTransport($mailConfigs));
// Ship the order...
// Send email
$mailer->to($request->user())->send(new OrderShipped($order));
return redirect('/orders');
}
}
How This Works
First, we need to check Illuminate\Mail\MailServiceProvider
:
protected function registerIlluminateMailer()
{
$this->app->singleton('mail.manager', function ($app) {
return new MailManager($app);
});
$this->app->bind('mailer', function ($app) {
return $app->make('mail.manager')->mailer();
});
}
In Laravel’s Mail provider, Laravel binds the mailer and each time from MailManager
singleton, it gets the default mailer for us.
//Illuminate\Mail\MailManager
public function mailer($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->mailers[$name] = $this->get($name);
}
So, we need to clone the Mailer
object: $mailer = clone app('mailer')
. Then we have an instance of Mailer
which we can modify without affecting the original app('mailer')
.
You can verify this by checking app('mailer') == $mailer
or by getting logs of it.
References
- Laravel Code Base
- Allowing Users to Send Email with Their Own SMTP Settings in Laravel — Laravel News : This method will not work at all because of changes in Laravel 11 src/Illuminate/Mail/Mailer.php#L20
- Laracast Discuss
- Change Laravel Mail Configuration During Runtime — Stack Overflow
By following these methods, you can dynamically configure email settings in Laravel, allowing each user to send emails from their own SMTP settings. This ensures that your application remains flexible and adaptable to various user requirements.