NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Notifications for Laravel

Recipient and subject models

Two traits and four small contracts connect your models to the package. Each is optional — implement only what your types use.

ReceivesNotifications — the recipient

Use it on your User next to Laravel’s Notifiable. Every call goes through the manager, so Notifications::fake() sees it:

$user->inbox()->unread()->paginate();               // = Notifications::inbox($user)
$user->notificationPreferences()->mute('comment.posted');   // = Notifications::preferences($user)
$user->unreadNotificationCount();                   // 3

HasNotificationReminders — the subject

Use it on a model whose dates drive reminders (a contract, a task, a trial). Reminders of every type whose subject() is this class — or a parent of it — follow its date attributes:

use RoundlyConsulting\Notifications\Traits\HasNotificationReminders;

class Contract extends Model
{
    use HasNotificationReminders;
}

$contract->update(['expires_at' => now()->addYear()]);   // pending reminders move; sent ones re-arm
$contract->delete();                                      // pending reminders and escalations are cancelled
$contract->restore();                                     // and scheduled again
$contract->notificationReminders();                       // MorphMany<NotificationReminder>
$contract->syncNotificationReminders();                   // int — a manual sync

It syncs on created, on updated when an anchor attribute of a registered rule (or of a pending ad-hoc reminder) changed, and on restored — after your commit, so a lock wait or a lost race never fails or rolls back your save() (errors are reported). A delete, soft or force, cancels with subject_deleted. It is also the subject-lifecycle hook for notifications.inbox.on_subject_deleted (keep, archive or delete the subject’s inbox items).

Contracts

use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Notifications\Contracts\HasNotificationContext;
use RoundlyConsulting\Notifications\Contracts\HasNotificationOwner;
use RoundlyConsulting\Notifications\Contracts\HasNotificationTimezone;
use RoundlyConsulting\Notifications\Contracts\ProvidesNotificationRecipients;

// A recipient that names its own timezone — the first source the default resolver asks
class User extends Authenticatable implements HasNotificationTimezone
{
    use Notifiable;
    use ReceivesNotifications;

    public function notificationTimezone(): ?string
    {
        return $this->settings['timezone'] ?? null;
    }
}

// A group behind Recipients::role() / members(), and an inbox context with its own label
class Company extends Model implements ProvidesNotificationRecipients, HasNotificationContext
{
    public function notificationRecipients(?string $role = null): iterable
    {
        return $role === null
            ? $this->users
            : $this->users()->wherePivot('role', $role)->get();
    }

    public function notificationContextLabel(): string
    {
        return $this->legal_name;   // "Acme s.r.o."
    }
}

// A subject that names its owner, for Recipients::owner()
class Contract extends Model implements HasNotificationOwner
{
    use HasNotificationReminders;

    public function notificationOwner(): Model|iterable|null
    {
        return $this->accountManager;
    }
}
ContractMethodUsed by
HasNotificationTimezonenotificationTimezone(): ?stringThe default timezone resolver — its first source.
ProvidesNotificationRecipientsnotificationRecipients(?string $role = null): iterableThe default group resolver behind Recipients::role() and members().
HasNotificationOwnernotificationOwner(): Model|iterable|nullRecipients::owner().
HasNotificationContextnotificationContextLabel(): stringThe label of a model context, else its name attribute, else its basename and key.
RecipientResolverresolve(?Model $subject, array $args): iterableRecipients::using().
ResolvesGroupMembersmembers(Model $group, ?string $role = null): iterablenotifications.recipients.group_resolver.
ResolvesMailAddressaddress(Model $recipient, ?NotificationPayload $payload): ?stringnotifications.mail.address_resolver.
ResolvesTimezonetimezone(Model $recipient): stringnotifications.timezone.resolver.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.