Introduction
Beyond mail and database, Laravel notifications can deliver to Slack, SMS, push services, and any custom channel you invent — Telegram, Discord, Microsoft Teams, an internal webhook. The channel machinery is open enough that adding one is usually a single class plus a toChannelName() method.
Key Concepts
- First-party channel: Slack (
laravel/slack-notification-channel), Vonage SMS (laravel/vonage-notification-channel), broadcast. - Custom channel: A class with a
send($notifiable, Notification $notification)method. routeNotificationFor{Channel}(): A method on the notifiable that returns the delivery address for a given channel.- Notification events:
NotificationSentandNotificationFailedfire for every send.
Real World Context
A team chat bot that posts deploy notifications needs Slack. A two-factor SMS needs Vonage or Twilio. A Telegram alerting bot needs a custom channel because there's no first-party package. Each of these slots into the same notification API.
Deep Dive
Slack Notifications
bashcomposer require laravel/slack-notification-channel
phpuse Illuminate\Notifications\Messages\SlackMessage; public function via(object $notifiable): array { return ['slack']; } public function toSlack(object $notifiable): SlackMessage { return (new SlackMessage) ->success() // or ->warning(), ->error() ->content('Invoice Paid!') ->attachment(function ($attachment) { $attachment->title('Invoice #' . $this->invoice->number) ->fields([ 'Amount' => '$' . $this->invoice->amount, 'Customer' => $this->invoice->customer->name, ]); }); }
Configure Slack webhook:
php// In User model or as on-demand route public function routeNotificationForSlack(): string { return 'https://hooks.slack.com/services/...'; }
SMS Notifications (Vonage)
bashcomposer require laravel/vonage-notification-channel
phpuse Illuminate\Notifications\Messages\VonageMessage; public function via(object $notifiable): array { return ['vonage']; } public function toVonage(object $notifiable): VonageMessage { return (new VonageMessage) ->content('Your order has shipped! Track: ' . $this->order->tracking); } // In User model public function routeNotificationForVonage(): string { return $this->phone_number; }
Broadcast Notifications
For real-time notifications via WebSockets:
phpuse Illuminate\Notifications\Messages\BroadcastMessage; public function via(object $notifiable): array { return ['broadcast', 'database']; } public function toBroadcast(object $notifiable): BroadcastMessage { return new BroadcastMessage([ 'invoice_id' => $this->invoice->id, 'amount' => $this->invoice->amount, 'message' => 'New invoice paid!', ]); }
Listen in JavaScript:
javascriptEcho.private(`App.Models.User.${userId}`) .notification((notification) => { console.log(notification.message); });
Creating Custom Channels
php<?php namespace App\Channels; use Illuminate\Notifications\Notification; class TelegramChannel { public function send($notifiable, Notification $notification) { $message = $notification->toTelegram($notifiable); $chatId = $notifiable->routeNotificationFor('telegram'); // Send to Telegram API Http::post('https://api.telegram.org/bot'.config('services.telegram.token').'/sendMessage', [ 'chat_id' => $chatId, 'text' => $message->content, ]); } }
Use in notification:
phpuse App\Channels\TelegramChannel; public function via(object $notifiable): array { return [TelegramChannel::class]; } public function toTelegram(object $notifiable) { return new TelegramMessage('Your order has shipped!'); }
Queued Notifications
phpclass InvoicePaid extends Notification implements ShouldQueue { use Queueable; // Configure queue public $connection = 'redis'; public $queue = 'notifications'; public $delay = 60; // seconds // Conditional queueing public function shouldSend(object $notifiable, string $channel): bool { return $notifiable->wants_notifications; } }
Notification Events
Listen for notification events:
phpuse Illuminate\Notifications\Events\NotificationSent; use Illuminate\Notifications\Events\NotificationFailed; // In AppServiceProvider Event::listen(NotificationSent::class, function ($event) { Log::info('Notification sent', [ 'notifiable' => $event->notifiable, 'notification' => $event->notification, 'channel' => $event->channel, ]); }); Event::listen(NotificationFailed::class, function ($event) { Log::error('Notification failed', [ 'exception' => $event->exception, ]); });
Complete Multi-Channel Example
php<?php namespace App\Notifications; use App\Models\Order; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; class OrderShipped extends Notification implements ShouldQueue { use Queueable; public function __construct( public Order $order ) {} public function via(object $notifiable): array { $channels = ['mail', 'database']; if ($notifiable->slack_webhook_url) { $channels[] = 'slack'; } if ($notifiable->phone && $this->order->total > 100) { $channels[] = 'vonage'; } return $channels; } public function toMail(object $notifiable): MailMessage { return (new MailMessage) ->subject('Your Order Has Shipped!') ->greeting('Great news, ' . $notifiable->name . '!') ->line('Your order #' . $this->order->number . ' has been shipped.') ->line('Tracking: ' . $this->order->tracking_number) ->action('Track Order', url('/orders/' . $this->order->id)) ->line('Thank you for shopping with us!'); } public function toSlack(object $notifiable): SlackMessage { return (new SlackMessage) ->success() ->content('Order shipped!') ->attachment(function ($attachment) { $attachment->title('Order #' . $this->order->number) ->fields([ 'Customer' => $this->order->user->name, 'Total' => '$' . $this->order->total, 'Tracking' => $this->order->tracking_number, ]); }); } public function toArray(object $notifiable): array { return [ 'order_id' => $this->order->id, 'message' => 'Order #' . $this->order->number . ' shipped!', 'tracking' => $this->order->tracking_number, ]; } }
Common Pitfalls
- Hardcoding webhook URLs in the notification class — they belong in
config/services.phpor on the notifiable viarouteNotificationForSlack(). Hardcoded URLs break staging and leak into version control. - Forgetting to queue Slack and SMS sends — both are slow network calls. Mark the notification
ShouldQueueso the HTTP response doesn't wait on them.
Best Practices
- Route channel addresses from the notifiable —
$user->routeNotificationForSlack()is clearer and more testable than passing URLs around. - Wire
NotificationFailedto your alerting channel — a notification that never arrives is a silent bug until a customer complains.
Summary
- Slack, Vonage SMS, and broadcast are first-party channels; others live in community packages.
- Custom channels are a class with a
send()method plus ato{ChannelName}()method on the notification. routeNotificationFor{Channel}()on the notifiable returns the delivery address per channel.NotificationSentandNotificationFailedevents let you observe delivery centrally.