skip to content

In Laravel, how do you notify someone who has no user account, such as a new patient's phone number, and how do notifiable models route each channel?

level: middleimportance: nice to knowfreq 24%

answer

  1. Notification::route('vonage', $phone)
  2. returns an AnonymousNotifiable
  3. Notification::routes([...]) for several
  4. database channel not on demand
  5. routeNotificationFor{Channel}() on models

basics

~10 s

Notification::route('mail', $email)->route('vonage', $phone)->notify(...) builds an AnonymousNotifiable holding ad-hoc addresses. Models instead answer routeNotificationForMail() or routeNotificationForVonage(). The database channel cannot be used on demand.

solid answer

~40 s

`Notification::route($channel, $address)` returns an `Illuminate\Notifications\AnonymousNotifiable`; chain more `route()` calls, or pass them all at once to `Notification::routes([...])`, then call `notify()`. For mail the route can be `['[email protected]' => 'Ana Silva']` to include a name. Inside the notification, `$notifiable` is that anonymous object, so `via()` and the formatters must not assume a `User`; `$notifiable->routeNotificationFor('mail')` returns the address. The `database` channel is unavailable: `route('database', ...)` throws `InvalidArgumentException`, and if `via()` lists `'database'` for an anonymous notifiable the sender skips it. For models, the `Notifiable` trait routes `mail` to the `email` attribute and `database` to the `notifications()` relationship; every other channel asks for a `routeNotificationFor{Channel}()` method, such as `routeNotificationForVonage()` returning `phone_number`, and `routeNotificationForMail()` can override the email.

code

php · 8 lines
php
<?php

use App\Notifications\AppointmentReminder;
use Illuminate\Support\Facades\Notification;

Notification::route('mail', ['[email protected]' => 'Ana Silva'])
    ->route('vonage', '447700900123')
    ->notify(new AppointmentReminder($appointment));

go deeper

for a junior

Recall Notification::route() for people without accounts and routeNotificationForVonage() for giving SMS a phone number on a model.

for a middle

Explain AnonymousNotifiable, the route lookup order (method first, trait default second), and why the database channel is refused on demand.

for a senior

Show that your notifications work for both notifiable kinds: no attribute assumptions in formatters, instanceof checks in via(), and a record of on-demand sends when auditing matters.

for a principal

Decide how the product treats people without accounts — contacts, guests, guardians — including consent to SMS, retention of their details and where their message history lives.

## Two ways a channel finds its recipient Every notification channel needs an address: an email, a phone number, a Slack channel, a database relationship. Laravel gets it by asking the **notifiable** — the object you called `notify()` on — through `routeNotificationFor($channel)`. That notifiable is either one of your models using the `Notifiable` trait, or an **anonymous notifiable** built on the spot for someone who has no record. At a dental clinic both happen: registered patients get reminders from their profile, while a walk-in who only left a phone number, or a family member named as an emergency contact, gets an **on-demand** notification. ## Routing on models | Channel | Default route from the trait | Override method on the model | |---|---|---| | `mail` | the `email` attribute | `routeNotificationForMail($notification)` — a string, or `[email => name]` | | `database` | the `notifications()` morph relationship | rarely overridden | | `vonage` | none (`null`) | `routeNotificationForVonage($notification)` | | `slack` | none (`null`) | `routeNotificationForSlack($notification)` | | `broadcast` | the private `App.Models.Patient.{id}` channel | `receivesBroadcastNotificationsOn()` | The pattern is `routeNotificationFor` plus the StudlyCase channel name. When a method exists, it wins; otherwise the trait's default applies, and for most channels that default is `null` — nothing to deliver to. Overrides earn their keep when the obvious attribute is wrong: a clinic may send reminders to `contact_email` rather than the login `email`, or format `phone_number` into international form for SMS. ## On-demand notifications 1. Start from the facade: `Notification::route('mail', '[email protected]')`. 2. Add channels: `->route('vonage', '447700900123')`, or build all routes at once with `Notification::routes(['mail' => ..., 'vonage' => ...])`. 3. Send: `->notify(new AppointmentReminder($appointment))`. `route()` returns an `AnonymousNotifiable` that simply stores the routes; its `routeNotificationFor('mail')` returns what you gave it. For mail, an array `['[email protected]' => 'Ana Silva']` supplies a display name. For broadcast, the route is an array of channel objects. Queueing works as usual: if the notification implements `ShouldQueue`, the anonymous notifiable is serialized with the job. ## Writing notifications that serve both - **In `via()`**, check what you have: `$notifiable instanceof AnonymousNotifiable` has no preferences or opt-ins to read. - **In formatters**, avoid `$notifiable->first_name`; the anonymous notifiable has no attributes. Put names into the notification's constructor instead. - **When `toMail()` returns a mailable**, read the address with `$notifiable->routeNotificationFor('mail')`, since there is no `email` property. ## Limits - **No database channel.** Calling `route('database', ...)` throws `InvalidArgumentException` ("The database channel does not support on-demand notifications."), because a stored notification must belong to a model. If `via()` merely lists `'database'`, the sender skips that channel for anonymous notifiables without an error. - **No per-recipient preferences** unless you pass them in. - **Nothing in the bell.** The person has no account, so the in-app history lives nowhere; if you need an audit trail, record the send yourself. ## A worked clinic scenario A receptionist books a first visit for someone who is not yet registered and ticks "remind by SMS and email": 1. The booking stores the appointment with the contact details typed in. 2. The code calls `Notification::route('mail', [$email => $name])->route('vonage', $phone)->notify(new AppointmentReminder($appointment))`. 3. `via()` sees an `AnonymousNotifiable` and returns `['mail', 'vonage']`, never `'database'`. 4. Later, when the patient registers, their `Patient` model takes over: reminders go through `$patient->notify(...)`, `routeNotificationForVonage()` supplies the number, and `'database'` can join `via()` so the bell shows the history from then on. ## Choosing between the two - The person is a record in your system: make the model `Notifiable` and define the route methods once. - The person is a one-off address — a guardian, a referral contact, an external lab: use `Notification::route()`. - You are tempted to create a stub `User` just to call `notify()`: don't; that pollutes the accounts table and every query over it. ## Summary - `Notification::route()`/`routes()` build an `AnonymousNotifiable` for people without accounts. - Models answer `routeNotificationFor{Channel}()`; the trait defaults cover only `mail` and `database`. - The database channel is never available on demand.

  • What happens if via() returns ['mail', 'database'] for an on-demand notification?
    The email is sent, and the database channel is skipped for the anonymous notifiable without an error, so no row is stored. Only an explicit `Notification::route('database', ...)` call throws `InvalidArgumentException`.
  • Your Patient model has no routeNotificationForVonage() method. What does the vonage channel receive as the route?
    `null`. The `Notifiable` trait only supplies defaults for `mail` (the `email` attribute) and `database` (the `notifications()` relationship); every other channel gets `null` unless the model defines `routeNotificationFor{Channel}()`, so there is no number to text.

saying these in an interview costs you the question

  • On-demand notifications need a temporary User record created first.
  • Notification::route('database', ...) stores a row with a null notifiable.
  • The vonage channel finds a phone attribute on the model automatically.
  • Inside toMail(), $notifiable is always an Eloquent model.
  • routeNotificationForMail() changes the address for every channel.