Laravel is shipped with a Notifications system that makes it super easy to send notifications to your users through different notification channels, here's what a notification object might look like:
class TestNotification extends Notification
{
public function via($notifiable)
{
return ['mail', 'database'];
}
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
}
The via()
method is used to set the channels Laravel should send the notification through, and you can define multiple methods to customize how the notification should be sent in each channel.
It all starts in Illuminate\Notifications\ChannelManager
which implements two interfaces:
Illuminate\Contracts\Notifications\Dispatcher
Illuminate\Contracts\Notifications\Factory
You can send notifications using the Illuminate\Support\Facades\Notification
facade which uses the ChannelManager
internally:
Notification::send($users, new TestNotification());
The send()
method accepts a single notifiable or an array of notifiables, inside that method Laravel creates an instance of Illuminate\Notifications\NotificationSender
which handles the actual action of sending the notification, it has three main tasks:
- Prepares the list of notifiables
- Decides if the notification should be queued or sent right away
- Handles the sending/queueying process
The first task is pretty simple, it just formats the given $notifiables
value into an array of a Collection, that insures the value of notifiables is iteratable for later use.
The second task is simple as well, it checks if the Notification we're passing implements the Illuminate\Contracts\Queue\ShouldQueue
interface, if so then it means that Notification should be dispatched to queue instead of sending right away.
The third task is where the actual work happens, let's first discover the scenario where a Notification should be queued.
Sending Notifications right away
If the notification should be sent right away the Channel Manager calls the sendNow()
method of the NotificationSender
, this method does the following:
- Makes sure a notification ID is set
- Send the notification instance to the different notification drivers/channels
- Fire a couple of events
First, Laravel fires the Illuminate\Notifications\Events\NotificationSending
, if any of the listeners to that event returned false
the notification won't be sent, you can use that to do any final checks.
And after the sending process a Illuminate\Notifications\Events\NotificationSent
event is fired which you can use to do any logging or housekeeping.
To send the notification, the sender calls the build()
factory method on the channel manager to build an instance of the channel that should be used and then calls the send()
method on that channel.
Also I'd like to mention that if you take a look at the sendNow()
method you'll find that it accepts a third parameter which is the channels that should be used to send the notification, you can use this to simply override the channels specified in the notification class itself, you can actually call sendNow()
instead of send()
to make laravel send the notification right away even if the notification class implements the shouldQueue
interface:
Notification::sendNow($users, new TestNotification(), ['slack', 'mail']);
Sending Jobs To Queue
The simplest way for laravel to queue notifications is to create a single job that sends all the notifications to all the notifiers, but in case one notification failed this would cause the whole Job to be reported as failed even if some notifications were actually sent, and retrying the job would mean that notifications that were already sent successfully will be re-sent again.
To prevent this Laravel creates a queued job for every single notifiable and channel, for example let's say we want to notify 10 customers that the Privacy Policy was updated, and we need to send them an email as well as an SMS message, for that laravel is going to create 20 jobs that sends the notification to 10 clients through 2 different channels.
Laravel also assigns a unique ID for the notification per notifiable, it uses ramsey/uuid, this unique ID is used when we use the Database channel as the primary key for the notification record, or for when we broadcast the notification.
Long Story short:
- Laravel dispatches a notification to the queue once per channel per notifiable
- Laravel assigns a unique ID to the notification object per notifiable
Inside the Illuminate\Notifications\NotificationSender
the queueNotification()
method is called if the notification should be queued, and inside that method laravel dispatches an instance of Illuminate\Notifications\SendQueuedNotifications
to the queue, this instance is the actual job the worker runs and it holds a reference to the notifiable, notification, channel, and some information about how the notification should be queued which includes:
- The queue connection to be used
- The queue to be used
- Any delay that should be applied before sending
How can I define these values?
You can set these values as public properties inside the Notification class:
class PolicyUpdateNotification extends Notification implements ShouldQueue
{
public $connection = 'redis';
public $queue = 'urgent';
public $delay = 60;
}
Or you can use the methods of the Illuminate\Bus\Queueable
trait if you use that trait inside your notification.
Notification::send($users,
(new TestNotification())->onConnection(...)->onQueue(...)->delay(...)
);
What happens inside the SendQueuedNotifications job?
What happens inside the dispatched job is fairly simple, it calls the sendNow()
method of the notification manager which sends the notification right away.
It also does some queue-related housekeeping that we won't be looking into in this dive.