How to Send Email in Laravel Using Custom Config Per Email — Laravel 11

Amin Sharifi
4 min readJun 8, 2024

--

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

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.

--

--

Amin Sharifi
Amin Sharifi

Written by Amin Sharifi

As a software engineer, I've used PHP/Laravel and Python/FastAPI to build powerful backends and cloud systems. With 4 years of AI experience.