Introducing Mailables in Laravel 5.3

Posted on August 05, 2016 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

For the longest time, sending mail in Laravel has felt clumsy compared to the relatively light APIs of most other Laravel features. Here's a mail example from the Laravel 5.2 docs:

Mail::send('emails.reminder', ['user' => $user], function ($m) use ($user) {
    $m->from('hello@app.com', 'Your Application');

    $m->to($user->email, $user->name)->subject('Your Reminder!');
});

I'm not saying it's awful—it's still so much cleaner than its competitors--but it's often confusing to figure out what goes in the closure and what doesn't, what the parameter order is, etc.

Introducing Mailables

Mailables are PHP classes in Laravel 5.3 that represent a single email: "NewUserWelcome", or "PaymentReceipt". Now, similar to event and job dispatching, there's a simple "send" syntax, to which you'll pass an instance of the class that represents what you're "dispatching"; in this context, it's an email.

So now, that email above looks like this:

Mail::to($user)->send(new Reminder);

Let's take a look at that Reminder class. First, create it with an Artisan command:

php artisan make:mail Reminder

It'll now live in app/Mail directory. Let's take a look at how it looks out of the box:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class Reminder extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('view.name');
    }
}

All of the configuration you're used to doing in closures now takes place in the build() method. So let's re-create that example email again:

public function build()
{
    return $this->from('hello@app.com', 'Your Application')
        ->subject('Your Reminder!')
        ->view('emails.reminder');
}

Note: If you don't explicitly set the subject, Laravel will guess it from your class name. So if the class is named "ApplicationReminder", the default subject will be "Application Reminder".

Passing data

Now, what if we want to pass some data in to the subject or into the view? That goes into the constructor:

Mail::to($user)->send(new Reminder($event));
class Reminder extends Mailable
{
    public $event;

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

    public function build()
    {
        return $this->from('hello@app.com', 'Your Application')
            ->subject('Event Reminder: ' . $this->event->name)
            ->view('emails.reminder');
    }
}

Any public properties on our mailable class will be made available to the view, so we can now use $event in the view:

// resources/views/emails/reminder.blade.php
<h1>{{ $event->name }} is coming up soon!</h1>
<p>Lorem ipsum.</p>

But what if you'd prefer specifying the data explicitly? You can do that—pass an array to a with() call in build():

public function build()
{
    return $this->from('hello@app.com', 'Your Application')
        ->subject('Event Reminder: ' . $this->event->name)
        ->view('emails.reminder')
        ->with(['title' => $this->event->name]);
}

Customizing the delivery list

As you can see, customizing the email itself happens in the build() method and customizing who's getting it happens when we call the email. Let's take a look at cc and bcc:

Mail::to(User::find(1))
    ->cc(User::find(2))
    ->bcc(User::find(3))
    ->send(new Reminder);

// These methods also accept collections
Mail::to(Users::all())
    ->send(new Reminder);

Plaintext views

There's a new text() method to go along with the new view() method. You can pass it the view you want used for the plaintext version of this email:

public function build()
{
    return $this->view('emails.reminder')
        ->text('emails.reminder_plain');
}

Queueing mailables

One of the problems with sending mail in line with your application's execution is that it can often take a few seconds to send. Queues are the perfect answer to this. They're already easy with Laravel's pre-existing mail syntax, and it stays easy here: Just run Mail::queue instead of Mail::send.

Mail::to($user)->queue(new Reminder);

You can also use later to specify when it should be sent:

$when = Carbon\Carbon::now()->addMinutes(15);

Mail::to($user)->later($when, new Reminder);

Attachments

You'll probably get used to hearing this. Everything you can currently run within your mail closures, you can run within the build() method. This includes attach(). The first parameter is the path to the file, and the optional second parameter takes an array for customizing the details of the attached file.

public function build()
{
    $this->view('emails.reminders')
        ->attach('/path/to/file', [
            'as' => 'name.pdf',
            'mime' => 'application/pdf',
        ]);
}

You can also use attachRaw to attach raw data:

public function build()
{
    $this->view('emails.reminders')
        ->attachRaw($this->pdf, 'name.pdf', [
            'mime' => 'application/pdf',
        ]);
}

Conclusions

Mailables are not a drastic new feature. There's nothing you can do here that you couldn't already do with Laravel.

But it's one of those features you'll be glad for on a regular basis. I use mail a lot in my Laravel apps. I'm very grateful for this new system. It just makes sense.

The docs are online now if you want to read more.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.3  •  mail  •  laravel mailables