0

Database notifications — notifications table, markAsRead()

Intermediate5 min read·lv-21-003
sql

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:

php
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
<?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);