Laravel

Mail & Notifications

Send emails using Laravel's built-in Mail system with SMTP configuration, Mailable classes, and Blade templates. Also covers Database notifications, queued mail, and the Notification class with toMail() and toDatabase() channels.

Mail Config in .env: Set your SMTP credentials. Use Mailtrap for development or configure Gmail, Mailgun, SES for production.
📄.env
ENV
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_mailtrap_user
MAIL_PASSWORD=your_mailtrap_pass
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="no-reply@myapp.com"
MAIL_FROM_NAME="${APP_NAME}"
Create a Mailable Class: Use Artisan to generate a Mailable. The class holds all the data and logic for building the email.
📄terminal
BASH
php artisan make:mail OrderConfirmationMail --markdown=emails.order-confirmation
📄app/Mail/OrderConfirmationMail.php
PHP
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class OrderConfirmationMail extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public Order $order)
    {
        //
    }

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: "Order Confirmed – #{$this->order->id}",
        );
    }

    public function content(): Content
    {
        return new Content(
            markdown: 'emails.order-confirmation',
            with: [
                'order'    => $this->order,
                'customer' => $this->order->user,
            ],
        );
    }
}
Mail Blade Template (Markdown): Laravel's markdown mail components provide a clean, responsive layout out of the box.
📄resources/views/emails/order-confirmation.blade.php
HTML
<x-mail::message>
# Order Confirmed!

Hi {{ $customer->name }},

Thank you for your order. Here's a summary:

<x-mail::table>
| Product | Qty | Price |
|:--------|:---:|------:|
@foreach($order->items as $item)
| {{ $item->product->name }} | {{ $item->qty }} | Rp {{ number_format($item->price, 0, ',', '.') }} |
@endforeach
</x-mail::table>

**Total: Rp {{ number_format($order->total_price, 0, ',', '.') }}**

<x-mail::button :url="route('orders.show', $order->id)">
View Order
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
Send Mail in Controller: Use the Mail facade with to() and send(). Pass the recipient user or email address directly.
📄app/Http/Controllers/OrderController.php
PHP
<?php

namespace App\Http\Controllers;

use App\Mail\OrderConfirmationMail;
use App\Models\Order;
use Illuminate\Support\Facades\Mail;

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $order = Order::create([
            'user_id'        => auth()->id(),
            'total_price'    => $request->total,
            'payment_status' => 'pending',
        ]);

        // Attach items...

        // Send confirmation email immediately
        Mail::to($order->user->email)
            ->send(new OrderConfirmationMail($order));

        return redirect()->route('orders.show', $order)->with('success', 'Order placed!');
    }
}
Queue Mail for Async Sending: Replace send() with queue() to dispatch the mail to a background queue. Make sure your queue worker is running.
📄app/Http/Controllers/OrderController.php
PHP
// Queue the email (non-blocking)
Mail::to($order->user->email)
    ->queue(new OrderConfirmationMail($order));

// Or delay by 5 minutes
Mail::to($order->user->email)
    ->later(now()->addMinutes(5), new OrderConfirmationMail($order));

// In .env, set the queue connection:
// QUEUE_CONNECTION=database

// Run the queue worker:
// php artisan queue:work
Create a Notification Class: Notifications can be sent via multiple channels simultaneously. Generate one with Artisan.
📄terminal
BASH
php artisan make:notification OrderShippedNotification

# Create notifications table for database channel
php artisan notifications:table
php artisan migrate
Notification Class with toMail & toDatabase: Implement both channels in a single Notification class. via() determines which channels to use.
📄app/Notifications/OrderShippedNotification.php
PHP
<?php

namespace App\Notifications;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderShippedNotification extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(public Order $order)
    {
        //
    }

    // Define which channels to use
    public function via(object $notifiable): array
    {
        return ['mail', 'database'];
    }

    // Email channel
    public function toMail(object $notifiable): MailMessage
    {
        return (new MailMessage)
            ->subject("Your Order #{$this->order->id} Has Shipped!")
            ->greeting("Hello, {$notifiable->name}!")
            ->line("Great news! Your order has been shipped and is on its way.")
            ->line("Estimated delivery: 2–3 business days.")
            ->action('Track Order', route('orders.show', $this->order->id))
            ->line('Thank you for shopping with us!');
    }

    // Database channel – stored in notifications table
    public function toDatabase(object $notifiable): array
    {
        return [
            'order_id'   => $this->order->id,
            'message'    => "Your order #{$this->order->id} has been shipped.",
            'action_url' => route('orders.show', $this->order->id),
        ];
    }
}
Send a Notification: Call notify() on a notifiable model (User), or use the Notification facade to send to multiple users at once.
📄app/Http/Controllers/ShipmentController.php
PHP
use App\Notifications\OrderShippedNotification;
use Illuminate\Support\Facades\Notification;
use App\Models\Order;
use App\Models\User;

// Send to a single user
$order = Order::findOrFail($id);
$order->user->notify(new OrderShippedNotification($order));

// Send to multiple users at once (e.g., admins)
$admins = User::where('role', 'admin')->get();
Notification::send($admins, new OrderShippedNotification($order));
Read Database Notifications: Access unread notifications from the authenticated user's notifications relationship and mark them as read.
📄app/Http/Controllers/NotificationController.php
PHP
<?php

namespace App\Http\Controllers;

class NotificationController extends Controller
{
    public function index()
    {
        $user = auth()->user();

        // All notifications (paginated)
        $notifications = $user->notifications()->paginate(15);

        // Unread count for badge
        $unreadCount = $user->unreadNotifications()->count();

        return view('notifications.index', compact('notifications', 'unreadCount'));
    }

    public function markAsRead($id)
    {
        $notification = auth()->user()->notifications()->findOrFail($id);
        $notification->markAsRead();

        return back();
    }

    public function markAllRead()
    {
        auth()->user()->unreadNotifications->markAsRead();

        return back()->with('success', 'All notifications marked as read.');
    }
}