Laravel Magazine
Building a Notification System with Laravel Notifications

Building a Notification System with Laravel Notifications

Eric Van Johnson ·

Almost every app needs to tell users that something happened: an order shipped, a payment failed, someone mentioned them. The naive approach scatters mail calls, database inserts, and Slack webhooks all over your codebase. Laravel Notifications consolidate all of that into a single class per event that can deliver across multiple channels at once. This tutorial builds a complete notification system.

Step 1: Generate a Notification

php artisan make:notification InvoicePaid

This creates app/Notifications/InvoicePaid.php. A notification is a small class that knows what to say and which channels to say it on.

Step 2: Choose the Delivery Channels

The via() method returns the channels a notification should go out on. You can decide dynamically based on the notifiable's preferences:

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    public function __construct(public Invoice $invoice) {}

    public function via(object $notifiable): array
    {
        // Respect each user's notification preferences
        return $notifiable->prefers_sms
            ? ['mail', 'database', 'vonage']
            : ['mail', 'database'];
    }
}

Implementing ShouldQueue means every channel is delivered in the background. Notifications that send email or hit external APIs should always be queued so they never block the request.

Step 3: Define Each Channel's Content

Each channel has its own method that shapes the message for that medium. The mail channel uses the same fluent builder as Mailables:

public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject("Payment received for invoice #{$this->invoice->number}")
        ->greeting("Thanks, {$notifiable->first_name}!")
        ->line("We received your payment of \${$this->invoice->total}.")
        ->action('View Invoice', route('invoices.show', $this->invoice))
        ->line('Thank you for your business.');
}

public function toArray(object $notifiable): array
{
    // Stored as JSON in the notifications table
    return [
        'invoice_id' => $this->invoice->id,
        'amount'     => $this->invoice->total,
        'message'    => "Invoice #{$this->invoice->number} was paid.",
    ];
}

The toArray() data is what gets stored for the database channel and shown in your in-app notification bell.

Step 4: Set Up the Database Channel

For in-app notifications you need the notifications table:

php artisan make:notifications-table
php artisan migrate

Make sure your User model uses the Notifiable trait (new apps already do):

use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}

Step 5: Send the Notification

There are two ways to send. Use the notifiable directly, or the Notification facade for on-demand and bulk sends:

// To a single user
$user->notify(new InvoicePaid($invoice));

// To many at once
Notification::send($admins, new InvoicePaid($invoice));

// On-demand, to someone who is not a stored model
Notification::route('mail', 'accounting@example.com')
    ->notify(new InvoicePaid($invoice));

Step 6: Display In-App Notifications

The Notifiable trait gives you relationships for reading stored notifications. Build a notification bell by querying the unread ones:

// In a controller
$unread = $request->user()->unreadNotifications;

// Mark them as read when the user opens the panel
$request->user()->unreadNotifications->markAsRead();

// Or mark a single one
$notification = $user->notifications()->find($id);
$notification->markAsRead();

In your Blade view:

@foreach (auth()->user()->unreadNotifications as $note)
    <div class="notification">
        {{ $note->data['message'] }}
        <span>{{ $note->created_at->diffForHumans() }}</span>
    </div>
@endforeach

Step 7: Add a Slack or SMS Channel Later

The payoff of this architecture shows up when requirements change. Want to ping a Slack channel when a high-value invoice is paid? Add 'slack' to via() and write one method — every place that already sends the notification picks it up automatically:

public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->success()
        ->content("💰 Invoice #{$this->invoice->number} paid: \${$this->invoice->total}");
}

No hunting through controllers, no duplicated logic. One class owns everything about "what happens when an invoice is paid," across every channel.

That is the whole value of Laravel Notifications: a single source of truth per event, queued delivery, multi-channel output, stored in-app history, and trivial extensibility. Build your next user-facing event this way and adding a channel six months from now is a five-minute change instead of a refactor.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.