Laravel Mail & Notifications: Step-by-Step Guide

·

·

Laravel provides a unified API for sending email and multi-channel notifications. Whether you need transactional order confirmations or real-time alerts, the Mail and Notification systems integrate with queues, Markdown, and third-party drivers.

Configuring Mail Drivers

Mail settings live in config/mail.php and .env. Laravel supports SMTP, Mailgun, Postmark, SES, and log drivers for local development.

Start with MAIL_MAILER=log during development so messages are written to storage/logs/laravel.log instead of hitting a real inbox.

Step 1: Set SMTP credentials in .env

Configure MAIL_MAILER, MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORD, and MAIL_ENCRYPTION for your provider.

For Gmail or Office 365, use app-specific passwords and TLS on port 587. Never commit real credentials to version control.

MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_user
MAIL_PASSWORD=your_pass

Step 2: Test mail configuration with Tinker

Run php artisan tinker and send a raw message with Mail::raw('Test', fn($m) => $m->to('you@example.com')->subject('Test'));

If the message fails, check storage/logs/laravel.log for connection errors and verify firewall rules allow outbound SMTP.

Mail::raw('Hello', function ($message) {
    $message->to('test@example.com')->subject('Test');
});

Step 3: Use Mailtrap or Mailhog locally

Mailtrap captures outbound email in a web inbox without delivering to real users. Mailhog works similarly in Docker stacks.

Point MAIL_HOST to your sandbox host so QA teams can preview templates before production cutover.

  • Mailtrap for cloud sandboxes
  • Mailhog for Docker Compose

Step 4: Switch drivers per environment

Use env-specific .env files: log in local, smtp in staging, ses or postmark in production.

Run php artisan config:cache in production so mail settings are compiled and fast on every request.

Building Mailable Classes

Mailable classes encapsulate email content, attachments, and recipients. They are reusable and testable units separate from controllers.

Laravel 11 scaffolds Mailables with php artisan make:mail including optional Markdown templates.

Step 1: Generate a Mailable with Artisan

Run php artisan make:mail OrderShipped –markdown=emails.orders.shipped. Laravel creates app/Mail/OrderShipped.php and resources/views/emails/orders/shipped.blade.php.

The build() method chains to(), subject(), and markdown() or view() to render HTML and plain-text parts.

php artisan make:mail OrderShipped --markdown=emails.orders.shipped

Step 2: Pass data to the Markdown template

Expose public properties on the Mailable or use with() in build(). Blade components like @component('mail::message') style headers and buttons consistently.

Keep templates focused on presentation. Compute totals and formatting in the Mailable constructor or a dedicated view model.

public function __construct(public Order $order) {}

public function build()
{
    return $this->markdown('emails.orders.shipped');
}

Step 3: Queue heavy Mailables

Implement ShouldQueue on the Mailable or call Mail::to($user)->queue(new OrderShipped($order)) to defer sending.

Queued mail respects retry policies. Failed sends land in failed_jobs for inspection with php artisan queue:failed.

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

Step 4: Attach files and embed images

Chain attach() for PDF invoices or attachFromStorage() for files on the default disk. Use embed() for inline logos in HTML email.

Validate attachment paths server-side. Never pass user-supplied paths directly to attach() without sanitization.

$this->attach(storage_path('invoices/'.$this->order->invoice_path));

Notifications Across Channels

The Notification facade sends messages via mail, database, Slack, SMS, and custom channels from a single class.

Notifiable models use the Notifiable trait and receive notifications through notify() or Notification::send().

Step 1: Create a notification class

Run php artisan make:notification InvoicePaid. Implement via() returning channels and toMail(), toDatabase(), or toSlack() formatters.

Each channel method returns a channel-specific message object such as MailMessage or SlackMessage.

php artisan make:notification InvoicePaid

Step 2: Send to a user model

Add use Notifiable to your User model. Call $user->notify(new InvoicePaid($invoice)) from a controller or listener.

For guests without accounts, use Notification::route('mail', $email)->notify(new InvoicePaid($invoice)).

$user->notify(new InvoicePaid($invoice));

Step 3: Store database notifications

Run php artisan notifications:table and php artisan migrate to create the notifications table.

Return ['database'] in via() and build toDatabase() with an array payload. Users read alerts via $user->notifications.

public function via(object $notifiable): array
{
    return ['mail', 'database'];
}

Step 4: Test notifications with Notification::fake

In tests, Notification::fake() prevents real sends. Assert Notification::assertSentTo($user, InvoicePaid::class) after the action.

Combine with Mail::fake() when testing mailable content rendered inside notification mail channels.

Notification::fake();
// act...
Notification::assertSentTo($user, InvoicePaid::class);

Aadyanex builds production-ready Laravel applications with clean architecture, secure APIs, and long-term support.