In Laravel, what does a notification class contain, how does its via() method pick delivery channels, and how do you send it?
answer
- one event, many channel formats
- php artisan make:notification
- via($notifiable) returns channel names
- toMail(), toDatabase(), toVonage()
- $user->notify() vs Notification::send()
basics
~20 sA notification, made with make:notification, is one message with a format per channel: via() returns the channel names for each recipient, and toMail(), toDatabase() or toVonage() build each version. Send it with $user->notify(...) or Notification::send($users, ...).
solid answer
~40 s`php artisan make:notification AppointmentReminder` creates a class in `app/Notifications` extending `Illuminate\Notifications\Notification`. `via(object $notifiable): array` returns channel names for *that* recipient — the stub returns `['mail']`, and it can branch, adding `'vonage'` only for patients who opted in to SMS. Each channel has a formatter: `toMail()` returning a `MailMessage`, `toDatabase()` or `toArray()` for the database channel, `toVonage()` for SMS. A model using the `Notifiable` trait — the default `User` does — sends with `$patient->notify(new AppointmentReminder($appointment))`; for many recipients use `Notification::send($patients, ...)`. The trait also routes delivery: mail goes to the `email` attribute, and other channels ask for methods such as `routeNotificationForVonage()`. `mail`, `database` and `broadcast` ship with the framework; Vonage SMS and Slack need the `laravel/vonage-notification-channel` and `laravel/slack-notification-channel` packages.
code
php · 29 lines<?php
namespace App\Notifications;
use App\Models\Appointment;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\VonageMessage;
use Illuminate\Notifications\Notification;
class AppointmentReminder extends Notification
{
public function __construct(public Appointment $appointment) {}
public function via(object $notifiable): array
{
return $notifiable->sms_opt_in ? ['mail', 'vonage', 'database'] : ['mail', 'database'];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)->subject('Your dental appointment tomorrow')
->line('Check-up at '.$this->appointment->starts_at->format('H:i').'.');
}
public function toVonage(object $notifiable): VonageMessage
{
return (new VonageMessage)->content('Dental check-up tomorrow at '.$this->appointment->starts_at->format('H:i'));
}
}go deeper
Recall make:notification, what via() returns, the toMail/toDatabase/toVonage formatters, and the two ways to send: notify() and Notification::send().
Explain how via() is evaluated per notifiable, how channels find addresses through routeNotificationFor, and which channels are built in versus packages.
Show that you guard silent drops such as a missing email, keep preference logic in via(), and choose notifyNow() or queueing deliberately.
Frame notifications as the product's messaging contract: one class per event, channel choice from user preferences, and a clear line between transactional reminders and marketing.
## What a notification is A Laravel **notification** represents *something that happened that a person should hear about* — "your appointment is tomorrow at 09:30", "your invoice is overdue". Unlike a mailable, which is one email, a notification is channel-neutral: the same class can be delivered as an email, a text message, a row in an in-app notification list or a real-time push, and it decides per recipient which of those to use. Generate one with: ```bash php artisan make:notification AppointmentReminder php artisan make:notification AppointmentReminder --markdown=mail.appointments.reminder ``` The class lands in `app/Notifications`, extends `Illuminate\Notifications\Notification` and already uses the `Queueable` trait. ## Anatomy of the class | Method | Returns | Used by | |---|---|---| | `via(object $notifiable)` | array of channel names | the notification sender, to decide where to deliver | | `toMail(object $notifiable)` | `MailMessage` (or a mailable) | the `mail` channel | | `toDatabase(object $notifiable)` | array | the `database` channel | | `toArray(object $notifiable)` | array | `database` and `broadcast`, when no more specific method exists | | `toBroadcast(object $notifiable)` | `BroadcastMessage` | the `broadcast` channel | | `toVonage(object $notifiable)` | `VonageMessage` | the `vonage` SMS channel (package) | | `toSlack(object $notifiable)` | `SlackMessage` | the `slack` channel (package) | Data the formatters need — the appointment, the clinic — goes into the constructor. The base `Notification` class uses `SerializesModels`, so model properties survive queueing as identifiers. ## Choosing channels per recipient `via()` receives the notifiable, so it can respect preferences: - `['mail', 'database']` for everyone, plus `'vonage'` when `$notifiable->sms_opt_in` is true. - An empty array means "send nothing to this recipient"; the sender skips it silently. - Channel names are strings; a custom channel can be named by its class, which the channel manager resolves from the container. Returning channels from data keeps preference logic inside the notification rather than scattered across controllers. ## Sending - `$patient->notify(new AppointmentReminder($appointment))` — through the `Notifiable` trait on the model. - `Notification::send($patients, new AppointmentReminder($appointment))` — the facade, for a collection or array of notifiables. - `notifyNow()` and `Notification::sendNow()` — deliver immediately even when the class implements `ShouldQueue`; `notifyNow()` also accepts an explicit channel list. For each notifiable, the sender calls `via()`, generates one notification id shared by all channels, and asks each channel's driver to deliver. ## How a channel finds the address 1. The channel calls `$notifiable->routeNotificationFor($channel, $notification)`. 2. If the model defines `routeNotificationFor{Channel}()` — `routeNotificationForMail()`, `routeNotificationForVonage()` — that method answers. 3. Otherwise the trait's defaults apply: `mail` uses the `email` attribute, `database` uses the `notifications()` relationship, and anything else gets `null`. That last point explains a quiet failure: if the route is empty, the mail channel returns without sending and without throwing. A patient with no email address simply gets no email. ## Common mistakes - **Branching in the controller instead of `via()`.** Code like "if the patient wants SMS, send this, else send that" belongs in `via()`, where every sender gets it for free. - **Reading attributes that may not exist.** Formatters receive the notifiable; if some recipients are not your `Patient` model, `$notifiable->first_name` fails. Pass what the message needs through the constructor. - **Heavy work in formatters.** `toMail()` and `toVonage()` run once per recipient and channel. Load related data once, before sending, rather than querying inside each formatter. - **Forgetting the table.** Adding `'database'` to `via()` without having run the migration from `make:notifications-table` fails at send time, not at deploy. - **Assuming delivery.** `notify()` returning without an exception means the notification was handed off — sent through each channel, or queued — not that the patient read anything; a skipped mail route or an SMS the provider later rejects is not reported back to the caller. ## Built-in and package channels - **Framework:** `mail`, `database`, `broadcast`. - **First-party packages:** `laravel/vonage-notification-channel` for SMS (keys in `VONAGE_KEY`/`VONAGE_SECRET`, sender in `VONAGE_SMS_FROM`) and `laravel/slack-notification-channel` for Slack Block Kit messages. - **Community channels** follow the same shape: a channel class with a `send($notifiable, $notification)` method. ## Summary - One notification class, one formatter per channel, `via()` picks channels per recipient. - `notify()` on a `Notifiable` model or `Notification::send()` for many. - Routes come from `routeNotificationFor{Channel}()` or the trait's defaults; a missing mail route skips silently.
- What happens if via() returns ['mail'] but the patient's email attribute is null?The mail channel asks for the route, gets nothing, and returns without sending or throwing, unless `toMail()` returned a mailable that carries its own recipient. The notification is silently dropped for that channel, so guard it: leave `'mail'` out of `via()` when there is no address, or define `shouldSend()`.
- What is the difference between notify() and notifyNow()?`notify()` hands the notification to the sender, which queues it when the class implements `ShouldQueue` and sends immediately otherwise. `notifyNow()` always sends in the current process, ignoring `ShouldQueue`, and accepts an optional channel list that replaces what `via()` would return.
saying these in an interview costs you the question
- via() is called once per notification, not once per recipient.
- Every notification email needs its own mailable class.
- SMS through Vonage works with no extra package installed.
- Only the User model can receive Laravel notifications.
- A missing email address makes the mail channel throw an exception.