Introducing Mailables in Laravel 5.3

(This is part of a series of posts on New Features in Laravel 5.3.)

  1. Introducing Laravel Echo: An In-Depth Walk-Through
  2. The new $loop variable in Laravel 5.3
  3. Customizing additional parameters in FirstOrCreate in Laravel 5.3
  4. The new cache() global helper in Laravel 5.3
  5. New JSON-column where() and update() syntax in Laravel 5.3
  6. Advanced operations with Collection::where in Laravel 5.3
  7. Image dimension validation rules in Laravel 5.3
  8. Customizing pagination templates in Laravel 5.3
  9. 5.3 feature announcement notes from Laracon
  10. Routing changes in Laravel 5.3
  11. Introducing Laravel Scout
  12. Introducing Laravel Passport
  13. Introducing Mailables in Laravel 5.3
  14. Directory structure changes in Laravel 5.3
  15. The new Notification system in Laravel 5.3
  16. Update to queue workers in Laravel 5.3
  17. Using Vue in Laravel 5.3, with the Vue bootstrap and sample component
  18. Defining console commands via closure in Laravel 5.3

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

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.