Database notifications — notifications table, markAsRead()
Concept
Queueable notifications defer sending to a background worker, preventing the web request from waiting on slow mail servers or external APIs. Email sending can take 1-3 seconds — 10 simultaneous notifications would block a request for 30 seconds without queuing.
Making a notification queueable: Implement Illuminate\Contracts\Queue\ShouldQueue on the notification class. Add use Queueable trait. That's it.
Queue configuration on notifications:
public string $connection: Queue connection.public string $queue: Queue name (default:'default').public int $delay: Delay in seconds before processing.$this->afterCommit(): In the constructor — queue only after DB transaction commits.
notifyNow(): Bypass queuing and send synchronously, even if ShouldQueue is implemented.
Per-channel queuing: Override shouldQueue() or use viaQueues() to specify different queues per channel:
public function viaQueues(): array
{
return ['mail' => 'high', 'database' => 'low'];
}Notification delay: $user->notify((new OrderShipped())->delay(now()->addMinutes(5))) — chainable delay on the notification instance.
onQueue(), onConnection(), delay(): Methods on the notification (via Queueable trait) that can be called at dispatch time.
Code Example
<?php
namespace App\Notifications;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
class WeeklyReport extends Notification implements ShouldQueue
{
use Queueable;
// Queue configuration
public string $connection = 'redis';
public string $queue = 'reports';
public function __construct(private readonly array $reportData)
{
// Queue after transaction commits
$this->afterCommit();
}
public function via(object $notifiable): array
{
return ['mail', 'database'];
}
// Different queues per channel
public function viaQueues(): array
{
return [
'mail' => 'emails', // high priority queue for emails
'database' => 'default', // normal queue for DB writes
];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('Your Weekly Report')
->markdown('mail.reports.weekly', ['data' => $this->reportData]);
}
public function toDatabase(object $notifiable): array
{
return ['report' => $this->reportData, 'type' => 'weekly_report'];
}
}
// Usage
$user->notify(new WeeklyReport($data)); // queued (ShouldQueue)
$user->notifyNow(new WeeklyReport($data)); // sync, bypasses queue
// Delayed notification
$user->notify((new WeeklyReport($data))->delay(now()->addHour()));
// Override queue at dispatch time
$user->notify((new WeeklyReport($data))->onQueue('high')->onConnection('sqs'));
// Send to multiple users — each gets their own queued job
\Illuminate\Support\Facades\Notification::send(
User::subscribed()->get(),
new WeeklyReport($data)
);
// N users = N queued jobs — each processed independently by workers
// Testing queueable notifications
\Illuminate\Support\Facades\Notification::fake();
$user->notify(new WeeklyReport($data));
\Illuminate\Support\Facades\Notification::assertSentTo($user, WeeklyReport::class);