Adding Email Headers in Laravel Application

Adding email headers in Laravel application is quite simple. SwiftMessage can be customized using withSwiftMessage method of Mailable base class: https://laravel.com/docs/8.x/mail#customizing-the-swiftmailer-message

However you have to remember to do it every time you create a new “mailable” class. Some mail APIs require you to put a special header each time you send an email. In this and similar cases you may want to configure your application to set custom headers every time you send an email. This article willexplore how to do that.

Sending an Email without Custom Header

I will be using Laravel 8.x, smtp mail driver and Mailtrap. First, lets create HelloWorld mailable with markdown:

php artisan make:mail HelloWorld --markdown

Next, we create a route “send-email” in routes/web.php file.

use App\Mail\HelloWorld;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route;

Route::get('/send-email', function() {
    Mail::to('alex@alex.com')->send(new HelloWorld());
    return response('Email sent');
});

After hitting “send-email” route, you should see something like this in your mailbox.

Email in Mailtrap without custom headers
Email in Mailtrap without custom header

Let’s write code to add email headers in our Laravel application. Although we will be doing it in Laravel 8, the process should be similar for lower versions of Laravel. Behind the scenes Laravel uses SwiftMailer to send its emails. A while ago I wrote an article about how to use SwiftMailer in the wild, without any framework. We are going to add custom header by adding a plugin to Swift_Mailer class. Like Laravel, SwiftMailer has its own events. In order to hook into those events we need to track down Swift_Mailer class.

Adding Custom Header to Emails

First let’s figure out what is returned by Mail façade. When we take a look at Illuminate/Support/Facades/Mail we see that method getFacadeAccessor returns ‘mail.manager’. Let’s go ahead and see what class is bound to ‘mail.manager’ in Illuminate/Mail/MailService/Provider. This class is Illuminate/Mail/MailManager. This class has mailer method that will return Illuminate/Mail/Mailer class. Finally to get Swift_Mailer class we can use getSwiftMailer method. Now we can use registerPlugin method on SwiftMailer to add custom plugin.

Let’s write the plugin first. Create file app/Mail/SwiftMailerPlugins/MyCustomMailHeaderPlugin.php with the following content.

<?php

namespace App\Mail\SwiftMailerPlugins;

use Swift_Events_SendEvent;
use Swift_Events_SendListener;

class MyCustomMailHeaderPlugin implements Swift_Events_SendListener
{
    /** @var array */
    private $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function beforeSendPerformed(Swift_Events_SendEvent $evt)
    {
        $message = $evt->getMessage();
        $message->getHeaders()->addTextHeader('X-Custom-Header', $this->config['custom_header'] ?? '');
    }

    public function sendPerformed(Swift_Events_SendEvent $evt)
    {

    }
}

The class implements two methods beforeSendPerformed and sendPerformed that will be called before and after sending message correspondently. If in your case one of those methods doesn’t do anything, you don’t have to put it in the class (your IDE may complain though). There is method exists check when application is running your plugin code and if method does not exist, it will not be called. I put an empty sendPerformed method just as an example.

Now let’s bootstrap our plugin in AppServiceProvider. After adding code to register method service provider should look something like this.

<?php

namespace App\Providers;

use App\Mail\SwiftMailerPlugins\MyCustomMailHeaderPlugin;
use Illuminate\Mail\MailManager;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->resolving('mail.manager', function (MailManager $mailManager) {
            $config = $this->app['config']->get('mail');
            
            if ($config['default'] === 'smtp') {
                $swiftMailer = $mailManager->mailer()->getSwiftMailer();
                $swiftMailer->registerPlugin(new MyCustomMailHeaderPlugin($config['mailers']['smtp']));
            }
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

The prep work we did in the beginning of the article is paying off now. We tell Laravel container when it resolves ‘mail.manager’ from Mail façade it needs to grab SwiftMailer and register MyCustomMailHeaderPlugin. It is worth noting that Laravel only create a mailer instance once and then re-uses it. So no matter how many times we call the Laravel’s mail function, it will always attach the custom header.

I put simple logic in register method and added custom header to the mail.php config file.

...
    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
            'port' => env('MAIL_PORT', 587),
            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
            'timeout' => null,
            'auth_mode' => null,

            'custom_header' => 'My Custom Email Header'
        ],
...

So now, when we check Mailtrap account we can see the message with the custom header.

Email with custom header
Email in Mailtrap with custom header

References:

Laravel Documentation

SwiftMailer Events and Symfony — An Introduction

Share this article

Posted

in

,

by

Tags: