Laravel Mail and Notifications Reference
A Laravel mail and notifications reference: sending email with Mailables, and multi-channel alerts with Notifications.
Laravel Reference
Mailables
A Mailable is a class representing one email — its subject, view, and data — kept separate from wherever it gets triggered. This site's own account-lockout email works exactly this way.
php artisan make:mail OrderShipped
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public Order $order,
) {}
public function envelope(): Envelope
{
return new Envelope(subject: 'Your order has shipped');
}
public function content(): Content
{
return new Content(
view: 'emails.orders.shipped',
with: ['order' => $this->order],
);
}
public function attachments(): array
{
return [
Attachment::fromPath(storage_path('invoices/1.pdf')),
];
}
}
Sending mail
use Illuminate\Support\Facades\Mail;
Mail::to($user)->send(new OrderShipped($order));
Mail::to($user)
->cc($manager)
->bcc('audit@example.com')
->send(new OrderShipped($order));
Queueing mail
Sending mail makes a real network call to your mail provider — queue it so the request/response cycle doesn't wait on that.
class OrderShipped extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
// ...
}
// or queue an already-built mailable ad hoc
Mail::to($user)->queue(new OrderShipped($order));
Notifications
Notifications generalize the idea further: one alert, sent down one or more channels (mail, database, Slack, SMS via a package) at once.
php artisan make:notification InvoicePaid
class InvoicePaid extends Notification
{
use Queueable;
public function __construct(
public Invoice $invoice,
) {}
public function via(object $notifiable): array
{
return ['mail', 'database'];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Invoice Paid')
->line("Invoice #{$this->invoice->id} has been paid.")
->action('View Invoice', route('invoices.show', $this->invoice));
}
public function toArray(object $notifiable): array
{
return ['invoice_id' => $this->invoice->id];
}
}
$user->notify(new InvoicePaid($invoice));
// Notify anyone, even without a User model (e.g. a raw email/Slack webhook)
Notification::route('mail', 'ops@example.com')
->notify(new InvoicePaid($invoice));
Reading database notifications
// Add the notifications() relation via the Notifiable trait (already on User)
$user->unreadNotifications;
$user->notifications()->latest()->take(10)->get();
$notification->markAsRead();
The database channel needs its own table — generate it once with php artisan make:notifications-table and migrate. Unlike mail, database notifications are cheap to write, which makes them a natural fit for an in-app bell icon or activity feed alongside (or instead of) an email.
Markdown mail
Rather than hand-writing HTML for every Mailable, Laravel ships a set of pre-styled Markdown components — buttons, panels, tables — so a professional-looking email can be written mostly as plain text:
php artisan make:mail OrderShipped --markdown=emails.orders.shipped
{{-- resources/views/emails/orders/shipped.blade.php --}}
@component('mail::message')
# Order Shipped
Your order #{{ $order->id }} is on its way.
@component('mail::button', ['url' => $trackingUrl])
Track Order
@endcomponent
Thanks,
{{ config('app.name') }}
@endcomponent
Because it renders both HTML and plain-text automatically, Markdown mail is usually the better starting point over a plain Blade view unless the design needs something the components can't express. Run php artisan vendor:publish --tag=laravel-mail to copy the underlying templates into the app if the default styling needs changing.
Notification channels beyond mail
The via() method isn't limited to mail and database. First-party and community packages add channels such as nexmo/vonage for SMS, slack for webhook messages, and broadcast for pushing a notification over WebSockets to a listening frontend. Each channel just needs a matching to{Channel}() method on the notification:
public function via(object $notifiable): array
{
return ['mail', 'slack', 'broadcast'];
}
public function toSlack(object $notifiable): SlackMessage
{
return (new SlackMessage)
->content("Invoice #{$this->invoice->id} has been paid.");
}
public function toBroadcast(object $notifiable): BroadcastMessage
{
return new BroadcastMessage(['invoice_id' => $this->invoice->id]);
}
Different notifiable models can also route the same notification differently — a User might prefer Slack while an external contact only has an email address on file — by defining routeNotificationForMail() etc. on the model itself.
Testing mail and notifications
Mail::fake() and Notification::fake() swap out the real senders in tests, so an assertion can confirm the right thing was sent without actually delivering anything or hitting a real mail provider:
Mail::fake();
// ... code under test that sends mail ...
Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
return $mail->order->id === $order->id;
});
Notification::fake();
Notification::assertSentTo($user, InvoicePaid::class);
Faking is preferable to letting tests send real email even in a local/testing mail driver, since it also verifies the correct Mailable or Notification class was dispatched with the expected data, rather than just that something happened.