Laravel makes the sending of the notifications a breeze. Practically some notifications should be processed immediately, some of them should be delayed.

The main problem with delayed notifications is that they can become irrelevant after a while because of some reasons.

Let's switch over to the example to understand what I mean.

Situation

Let's imagine that we need to notify the meeting members 6 hours before the meeting. The notification may look something like this:

<?php

namespace App\Notifications;

use App\Models\Meeting;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;

class MeetingReminder extends Notification implements ShouldQueue
{
    use SerializesModels, Queueable;

    public $meeting;

    public function __construct(Meeting $meeting)
    {
        $this->meeting = $meeting;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('Just reminding you that the meeting will take place in 6 hours.')
            ->action('Meeting Details', route('meetings.show', $this->meeting))
            ->line('Waiting for you!');
    }
}

Now we can delay this notification for the member when one joins the meeting.

$member->notify(new MeetingReminder($meeting))
    ->delay($meeting->meet_at->subHours(6));

But what if meeting time changes? Notification sending time should also be changed.

Unfortunately, I haven't found any workaround on how to modify delay of already queued job, so needed to find an alternative.

Solution

Laravel Relevance Ensurer will help us here.

This package adds ShouldBeRelevantNotification and ShouldBeRelevantJob contracts. We will use ShouldBeRelevantNotification because our topic is notification related.

That contract requires us to implement isRelevant($notifiable) method the result of which should return true if notification is still relevant and vice versa.

So in our case, we need to check whether the meeting time has changed since the moment of delaying that notification.

If the meeting time is changed, notification will become irrelevant and vice versa.

Let's translate the explanation above into the code.

<?php

namespace App\Notifications;

use App\Models\Meeting;
use Coderello\RelevanceEnsurer\Contracts\ShouldBeRelevantNotification;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\SerializesModels;

class MeetingReminder extends Notification implements ShouldQueue, ShouldBeRelevantNotification
{
    use SerializesModels, Queueable;

    public $meeting;

    public $initialMeetAt;

    public function __construct(Meeting $meeting)
    {
        $this->meeting = $meeting;

        $this->initialMeetAt = $meeting->meet_at;
    }

    public function isRelevant($notifiable): bool
    {
        return (string) $this->meeting->meet_at === (string) $this->initialMeetAt;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('Just reminding you that the meeting will take place in 6 hours.')
            ->action('Meeting Details', route('meetings.show', $this->meeting))
            ->line('Waiting for you!');
    }
}

Done!

After these changes, we will need to delay new notifications for all meeting members every time the meeting time changes and don't care about old notifications (because irrelevant notifications are not sent).

What do you think about this approach? I like it.