Mail Logging in Laravel 5.3: Extending the Mail Driver

Younes Rafie
Younes Rafie
Share

This article was peer reviewed by Viraj Khatavkar. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

One of the many goodies Laravel offers is mailing. You can easily configure and send emails through multiple popular services, and it even includes a logging helper for development.

Mail::send('emails.welcome', ['user' => $user], function ($m) use ($user) {
    $m->to($user->email, $user->name)->subject('Welcome to the website');
});

This will send an email to a new registered user on the website using the emails.welcome view. It got even simpler with Laravel 5.3 using mailables (but the old syntax is still valid).

Laravel Logo

Here’s an example:

# Generate a new mailable class
php artisan make:mail WelcomeMail
// app/Mail/WelcomeMail.php

class WelcomeUser extends Mailable
{

    use Queueable, SerializesModels;

    public $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function build()
    {
        return $this->view('emails.welcome');
    }
}
// routes/web.php

Route::get('/', function () {
    $user = User::find(2);

    \Mail::to($user->email)->send(new WelcomeUser($user));

    return "done";
});

Laravel also provides a good starting point for sending mails during the development phase using the log driver, and in production using smtp, sparkpost, mailgun, etc. This seems fine in most cases, but it can’t cover all the available services! In this tutorial, we’re going to learn how to extend the existing mail driver system to add our own.

To keep our example simple and straightforward, we’re going to log our emails to a DB table.

Creating a Service Provider

The preferred way to accomplish this is by creating a service provider to interact with our application at boot time and register our services. Let’s start by generating a new service provider using the artisan command line helper.

php artisan make:provider DBMailProvider

This will create a new class inside our app/Providers folder. If you’re familiar with Laravel service providers, you’ll know that we extend the ServiceProvider class and define the boot and register methods. You can read more about providers in the documentation.

Using the Mail Provider

Instead of using the parent service provider class, we can take a shortcut and extend the existing Illuminate\Mail\MailServiceProvider one. This means that the register method is already implemented.

// vendor/Illuminate/Mail/MailServiceProvider.php

public function register()
{
    $this->registerSwiftMailer();

    // ...
}

The registerSwiftMailer method will return the appropriate transport driver based on the mail.driver config value. What we can do here is perform the check before calling the registerSwiftMailer parent method and return our own transport manager.

// app/Providers/DBMailProvider.php

function registerSwiftMailer()
{
    if ($this->app['config']['mail.driver'] == 'db') {
        $this->registerDBSwiftMailer();
    } else {
        parent::registerSwiftMailer();
    }
}

Using the Transport Manager

Laravel resolves the swift.mailer instance from the IOC, which should return a Swift_Mailer instance of SwiftMailer. We need to bind our Swift mailer instance to the container.

private function registerDBSwiftMailer()
{
    $this->app['swift.mailer'] = $this->app->share(function ($app) {
        return new \Swift_Mailer(new DBTransport());
    });
}

You can think of the transport object as the actual driver. If you check the Illuminate\Mail\Transport namespace, you’ll find the different transport classes for every driver (like: LogTransport, SparkPostTransport, etc.).

The Swift_Mailer class requires a Swift_Transport instance, which we can satisfy by extending the Illuminate\Mail\Transport\Transport class. It should look something like this.

namespace App\Mail\Transport;

use Illuminate\Mail\Transport\Transport;
use Illuminate\Support\Facades\Log;
use Swift_Mime_Message;
use App\Emails;

class DBTransport extends Transport
{

    public function __construct()
    {
    }


    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
    {
        Emails::create([
            'body'    => $message->getBody(),
            'to'      => implode(',', array_keys($message->getTo())),
            'subject' => $message->getSubject()
        ]);
    }

}

The only method we should implement here is the send one. It’s responsible for the mail-sending logic, and in this case, it should log our emails to the database. As for our constructor, we can leave it empty for the moment because we don’t need any external dependencies.

The $message->getTo() method always returns an associative array of recipients’ emails and names. We get the list of emails using the array_keys function and then implode them to get a string.

Log Email to DB

The next step is to create the necessary migration for our database table.

php artisan make:migration create_emails_table --create="emails"
class CreateEmailsTable extends Migration
{

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('emails', function (Blueprint $table) {
            $table->increments('id');
            $table->text('body');
            $table->string('to');
            $table->string('subject');
            $table->timestamps();
        });
    }


    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('emails');
    }
}

Our migration only contains the email body, subject and recipient email, but you can add as many details as you want. Check the Swift_Mime_Message class definition to see the list of available fields.

Now, we need to create a new model to interact with our table, and add the necessary fields to the fillable array.

php artisan make:model Emails
class Emails extends Model
{

    protected $fillable = [
        'body',
        'to',
        'subject'
    ];
}

Sending Emails

Ok, it’s time to test what we’ve got so far. We start by adding our provider to the list of providers inside the config/app.php file.

return [
    // ...

    'providers' => [
        // ...

        App\Providers\DBMailProvider::class,

        // ...
    ],

    // ...
];

Then we register our mail driver to db inside the config/mail.php file.

return [
    'driver' => 'db',

    // ...
];

The only remaining part is to send a test email and check if it gets logged to the database. I’m going to send an email when the home URL is hit. Here’s the code.

// routes/web.php

Route::get('/', function () {
    $user = User::find(2);

    Mail::send('emails.welcome', ['user' => $user], function ($m) use ($user) {
        $m->to($user->email, $user->name)->subject('Welcome to the website');
    });

    return "done";
});

After hitting the home route, we can run the php artisan tinker command to check the emails table records.

Emails records

Conclusion

In this article, we saw how to extend the mail driver system to intercept emails for debugging purposes. One of the things I admire in Laravel is its unparalleled extensibility: you can alter or extend almost any functionality from router and IOC, to Mail and beyond.

If you have any questions or comments, be sure to post them below and I’ll do my best to answer them!

Frequently Asked Questions (FAQs) about Mail Logging in Laravel 5.3

How can I extend the mail driver in Laravel 5.3?

Extending the mail driver in Laravel 5.3 involves creating a new service provider. This service provider will extend the existing mail driver and allow you to add additional functionality. You can create a new service provider using the php artisan make:provider command. Once the provider is created, you can register it in the config/app.php file. In the provider, you can use the extend method to add your custom functionality to the mail driver.

What is the purpose of mail logging in Laravel?

Mail logging in Laravel is a feature that allows you to keep track of all outgoing emails sent by your application. This can be useful for debugging purposes, as it allows you to see exactly what emails are being sent, when they are sent, and who they are sent to. It can also be useful for auditing purposes, as it provides a record of all email communication sent by your application.

How can I configure Laravel to log all outgoing emails?

To configure Laravel to log all outgoing emails, you need to modify the config/mail.php file. In this file, you can set the log option to true. This will instruct Laravel to log all outgoing emails. The logs will be stored in the storage/logs directory.

How can I view the mail logs in Laravel?

The mail logs in Laravel are stored in the storage/logs directory. You can view these logs by navigating to this directory and opening the log files. The log files are named according to the date, so you can easily find the logs for a specific day.

Can I customize the format of the mail logs in Laravel?

Yes, you can customize the format of the mail logs in Laravel. This can be done by extending the mail driver and overriding the log method. In this method, you can specify the format of the log messages.

How can I send test emails in Laravel?

You can send test emails in Laravel using the Mail facade’s send method. This method accepts three parameters: the view that should be used as the email’s body, the data that should be passed to the view, and a closure that defines the message’s recipients and subject.

How can I handle errors when sending emails in Laravel?

Laravel provides several ways to handle errors when sending emails. One way is to use the try/catch block to catch any exceptions that are thrown when sending an email. Another way is to use the failed method on the Mail facade, which allows you to define a callback that will be executed if the email fails to send.

Can I use third-party services for sending emails in Laravel?

Yes, Laravel supports several third-party services for sending emails, including Mailgun, Postmark, Amazon SES, and Sendmail. You can configure Laravel to use these services by modifying the config/mail.php file.

How can I schedule emails to be sent at a later time in Laravel?

Laravel’s task scheduling feature can be used to schedule emails to be sent at a later time. You can define a scheduled task in the app/Console/Kernel.php file. In this task, you can use the Mail facade’s send method to send the email.

How can I attach files to emails in Laravel?

You can attach files to emails in Laravel using the attach method on the Mail facade. This method accepts the path of the file as its first parameter. You can also specify the display name of the file and its MIME type as optional second and third parameters.