Sending Emails in Laravel with Mailables and Templates
Setting up reliable, maintainable transactional emails using Laravel's Mailable class and Blade email templates.
Transactional email — the “you’ve registered”, “your order has been received”, “reset your password” kind — is something every web application eventually needs. But what starts as “just send an email” can quickly turn into an unmaintainable heap of mail() calls a few weeks later.
The Mailable classes introduced in Laravel 5.3 go a long way toward taming that mess. Moving email-sending logic into a dedicated class makes testing easier and keeps templates organized.
Creating a Mailable class
An Artisan command gets you started quickly:
php artisan make:mail OrderConfirmed
This creates app/Mail/OrderConfirmed.php. The basic structure looks like this:
namespace App\Mail;
use App\Order;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class OrderConfirmed extends Mailable
{
use SerializesModels;
public $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function build()
{
return $this->subject('Your Order Has Been Received')
->view('emails.orders.confirmed');
}
}
Declaring $order as public makes it automatically available in the template — no extra with() call needed. I like this small convention; having the data visible right on the class makes the flow much easier to follow than burying it inside method calls.
The SerializesModels trait kicks in for queued emails: instead of serializing the entire model, it stores only the ID in the queue and re-fetches the model at send time. Using this trait early on saved me from the subtle bugs that come with carrying large model objects through the queue.
Blade email template
The resources/views/emails/orders/confirmed.blade.php template is written just like any other Blade template:
<!DOCTYPE html>
<html>
<body>
<h1>Your Order Has Been Received</h1>
<p>Dear {{ $order->user->name }},</p>
<p>
Your order #{{ $order->id }} has been received.
Total amount: {{ number_format($order->total, 2) }}
</p>
<p>You can track your order details from your account.</p>
</body>
</html>
You can use @extends and @section in email templates just as you would elsewhere, so it is straightforward to create a shared email layout and reuse it across all messages. Keeping the header, footer, and brand colors in one place is a real convenience.
One detail worth noting: email clients — particularly older versions of Outlook — tend to ignore external CSS files. That means styles in email templates should be written inline (style="..."), or converted to inline styles at build time with a tool like premailer. Markdown Mailable partially solves this; its built-in templates already use inline styles.
Sending
Sending from a controller is a single line:
Mail::to($order->user)->send(new OrderConfirmed($order));
To send via the queue instead:
Mail::to($order->user)->queue(new OrderConfirmed($order));
Queuing delivery matters for responsiveness — when the mail server is slow to respond, you don’t want to hold up the HTTP request. With SMTP in particular, a server connection can stretch to several seconds, so queuing becomes nearly mandatory if you don’t want users to feel that lag.
Beyond Mail::to(), you can chain cc() and bcc() without needing to look for a separate method.
Configuration and environment differences
You can select the driver from the .env file: smtp, mailgun, sendmail, log… In development I use the log driver; emails are written to storage/logs/laravel.log and nothing is actually sent. In production, SMTP configuration takes over.
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=...
MAIL_PASSWORD=...
[email protected]
MAIL_FROM_NAME="Application Name"
Adding a testing tool like Mailtrap early in the development process prevents accidentally emailing real users. I learned this the hard way: real SMTP credentials left in a local environment caused order confirmation emails to be sent to actual users during a test run. Since then, the log driver has been a standard part of my development environment.
Markdown Mailable
The Markdown Mailable feature introduced in Laravel 5.4 generates both HTML and plain-text versions simultaneously. It comes with built-in components like mail::message and mail::button. When a custom design is not required, these templates offer a clean, fast starting point.
return $this->subject('Your Order Has Been Received')
->markdown('emails.orders.confirmed-markdown');
There is another advantage to Markdown Mailable: the vendor:publish command lets you copy the built-in templates into your project, so you can customize just the HTML/CSS to match your brand. Adapting an existing foundation rather than writing all templates from scratch saves a significant amount of time.
Managing multiple email types
As a project grows, so does the variety of emails: registration confirmation, password reset, order notification, invoice… When each one has its own Mailable class and its own Blade template, the app/Mail/ directory fills up quickly. That might look like clutter, but being able to change each email type independently is a real advantage. Having a dedicated class per type — rather than a single sendEmail($type, $data) function — means you can update a template or subject line for one email without touching any of the others.
Structuring email delivery around Mailable classes genuinely pays off as a project grows. Instead of scattered mail() calls, every message type has a home, and tracking what gets sent, when, and to whom becomes far easier.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.