All packages
Notifications for Laravel
Configuration
The package works with zero configuration. Publish config/notifications.php only to override:
php artisan vendor:publish --tag="notifications-config"Every value is read through a strict reader: a typo throws naming the key, and a blank env value (KEY=) means “not set” — the default applies.
- Durations are ISO-8601 (PT5M, P1D); times are local HH:MM.
- Switches accept true/false, 1/0, on/off and yes/no.
- Integers must sit in the range each row gives; a value outside it throws rather than being clamped.
- Class keys (resolvers, resources) must name a class of the right kind; anything else throws, naming the key.
The config file
use RoundlyConsulting\Notifications\Http\Resources\NotificationActorResource;
use RoundlyConsulting\Notifications\Http\Resources\NotificationContextResource;
use RoundlyConsulting\Notifications\Http\Resources\NotificationSubjectResource;
use RoundlyConsulting\Notifications\Models\DigestRun;
use RoundlyConsulting\Notifications\Models\InboxItem;
use RoundlyConsulting\Notifications\Models\NotificationDelivery;
use RoundlyConsulting\Notifications\Models\NotificationReceipt;
use RoundlyConsulting\Notifications\Models\NotificationReminder;
use RoundlyConsulting\Notifications\Recipients\ContractGroupResolver;
use RoundlyConsulting\Notifications\Support\DefaultTimezoneResolver;
return [
'types' => [],
'key_type' => env('NOTIFICATIONS_KEY_TYPE', 'bigint'),
'subject_key_type' => env('NOTIFICATIONS_SUBJECT_KEY_TYPE'),
'context_key_type' => env('NOTIFICATIONS_CONTEXT_KEY_TYPE'),
'primary_key_type' => env('NOTIFICATIONS_PRIMARY_KEY_TYPE', 'bigint'),
'models' => [
'inbox_item' => InboxItem::class,
'delivery' => NotificationDelivery::class,
'reminder' => NotificationReminder::class,
'digest_run' => DigestRun::class,
'receipt' => NotificationReceipt::class,
],
'tables' => [
'inbox_items' => 'notification_inbox_items',
'deliveries' => 'notification_deliveries',
'reminders' => 'notification_reminders',
'digest_runs' => 'notification_digest_runs',
'receipts' => 'notification_receipts',
],
'queue' => [
'enabled' => env('NOTIFICATIONS_QUEUE_ENABLED', true),
'connection' => env('NOTIFICATIONS_QUEUE_CONNECTION'),
'queue' => env('NOTIFICATIONS_QUEUE_NAME'),
],
'fan_out' => [
'queue_above' => 50,
'chunk' => 100,
],
'access' => [
'ability' => 'view',
],
'recipients' => [
'group_resolver' => ContractGroupResolver::class,
],
'idempotency' => [
'ttl' => 'P30D',
],
'inbox' => [
'per_page' => 20,
'max_per_page' => 100,
'max_data_bytes' => 16384,
'recheck_on_read' => env('NOTIFICATIONS_INBOX_RECHECK', false),
'on_subject_deleted' => 'keep',
],
'preferences' => [
'inbox_mutable' => false,
'lock_store' => env('NOTIFICATIONS_LOCK_STORE'),
'lock_seconds' => 10,
],
'mail' => [
'strategy' => 'leading',
'minimal_for_sensitive' => true,
'address_resolver' => null,
'preferences_url' => env('NOTIFICATIONS_PREFERENCES_URL'),
'max_attempts' => 3,
'backoff' => [60, 300, 900],
'lease' => 'PT10M',
],
'quiet_hours' => [
'enabled' => true,
'default' => null,
],
'timezone' => [
'resolver' => DefaultTimezoneResolver::class,
'attribute' => 'timezone',
],
'digest' => [
'default' => 'off',
'time' => '08:00',
'weekly_day' => 'monday',
'max_items' => 50,
'lease' => 'PT15M',
],
'reminders' => [
'max_lateness' => 'P1D',
'batch' => 500,
'lease' => 'PT10M',
],
'interop' => [
'map' => [],
'suppress' => [],
'database' => 'redirect',
'hold' => true,
'notifiables' => [],
],
'routes' => [
'enabled' => env('NOTIFICATIONS_ROUTES_ENABLED', 'off'),
'prefix' => 'notifications',
'middleware' => ['api'],
'auth_middleware' => ['auth'],
],
'resources' => [
'actor' => NotificationActorResource::class,
'subject' => NotificationSubjectResource::class,
'context' => NotificationContextResource::class,
],
'prune' => [
'inbox_after' => 'P180D',
'deliveries_after' => 'P30D',
'digest_runs_after' => 'P90D',
'reminders_after' => 'P90D',
],
];Types and key types
| Key | Env | Default | Purpose |
|---|---|---|---|
types | — | [] | Your notification types: class-strings extending NotificationType, resolved through the container on first use. Anything else throws, naming the key. Fluent types are added with Notifications::define(). |
key_type | NOTIFICATIONS_KEY_TYPE | bigint | The key of recipients and actors — bigint, uuid or ulid. Must equal your user key type and options.key_type, because preferences are stored per user through options-for-laravel. |
subject_key_type | NOTIFICATIONS_SUBJECT_KEY_TYPE | blank = key_type | The key of every subject model. |
context_key_type | NOTIFICATIONS_CONTEXT_KEY_TYPE | blank = key_type | The key of every context model. |
primary_key_type | NOTIFICATIONS_PRIMARY_KEY_TYPE | bigint | The package tables’ own ids and the foreign keys between them. Read by the migrations — set it before migrating. |
Models and tables
| Key | Env | Default | Purpose |
|---|---|---|---|
models.inbox_item | — | InboxItem::class | Swap a model by extending the packaged class and naming the subclass here. |
models.delivery | — | NotificationDelivery::class | One mail row per recipient, type, subject and context per window. |
models.reminder | — | NotificationReminder::class | Reminder and escalation rows. |
models.digest_run | — | DigestRun::class | One row per recipient, frequency and period — the digest ledger. |
models.receipt | — | NotificationReceipt::class | Idempotency receipts. |
tables.inbox_items | — | notification_inbox_items | Lowercase letters, digits and underscores, at most 30 characters — MySQL limits index names to 64. |
tables.deliveries | — | notification_deliveries | At most 30 characters. |
tables.reminders | — | notification_reminders | At most 24 characters. |
tables.digest_runs | — | notification_digest_runs | At most 30 characters. |
tables.receipts | — | notification_receipts | At most 30 characters. |
Queue and fan-out
| Key | Env | Default | Purpose |
|---|---|---|---|
queue.enabled | NOTIFICATIONS_QUEUE_ENABLED | true | Run mail, digest and fan-out jobs on the queue. Off: they run inline after the commit. |
queue.connection | NOTIFICATIONS_QUEUE_CONNECTION | blank = default | The queue connection of those jobs. |
queue.queue | NOTIFICATIONS_QUEUE_NAME | blank = default | The queue name of those jobs. |
fan_out.queue_above | — | 50 | More resolved recipients than this are delivered by queued jobs (at least 1). |
fan_out.chunk | — | 100 | Recipients per fan-out job, 1–10000; each job re-checks access. |
Access, recipients and idempotency
| Key | Env | Default | Purpose |
|---|---|---|---|
access.ability | — | view | The Gate ability the default access check inspects on the subject. |
recipients.group_resolver | — | ContractGroupResolver::class | A ResolvesGroupMembers class behind Recipients::role() and Recipients::members(). |
idempotency.ttl | — | P30D | How long an idempotency key (->once(), reminder and escalation fires) is remembered; receipts are pruned after it. |
Inbox
| Key | Env | Default | Purpose |
|---|---|---|---|
inbox.per_page | — | 20 | The default page size, 1 up to inbox.max_per_page. |
inbox.max_per_page | — | 100 | The largest page and the cap of get(), 1–1000. |
inbox.max_data_bytes | — | 16384 | The JSON size cap of a send’s data, 256–1048576 bytes. |
inbox.recheck_on_read | NOTIFICATIONS_INBOX_RECHECK | false | Hide items whose subject the reader can no longer access, per page (counts stay raw). |
inbox.on_subject_deleted | — | keep | What happens to inbox items when their subject is deleted: keep, archive or delete. Runs for subjects using HasNotificationReminders. |
Preferences
| Key | Env | Default | Purpose |
|---|---|---|---|
preferences.inbox_mutable | — | false | Off: muting stops email only — the inbox always keeps the item. |
preferences.lock_store | NOTIFICATIONS_LOCK_STORE | blank = default cache | The cache store of the preference write lock. Must be shared across servers and support atomic locks; a store without locks throws, naming the key. |
preferences.lock_seconds | — | 10 | How long the lock is held, 1–300 seconds. |
| Key | Env | Default | Purpose |
|---|---|---|---|
mail.strategy | — | leading | leading: the first event mails, later ones in the window fold into it. trailing: one mail after the window with the final count. |
mail.minimal_for_sensitive | — | true | A type’s sensitive() subjects get a title + link mail only. |
mail.address_resolver | — | null | A ResolvesMailAddress class deciding the address of every mail — typed mail and digests. null routes through the recipient. |
mail.preferences_url | NOTIFICATIONS_PREFERENCES_URL | null | A URL or a route name for the “manage notifications” footer link. |
mail.max_attempts | — | 3 | Transport tries of a mail and of a digest run, 1–20. |
mail.backoff | — | [60, 300, 900] | Seconds between tries — a non-empty list of positive integers. |
mail.lease | — | PT10M | A mail claimed longer ago than this is failed, never re-sent (at most once). |
Quiet hours and timezone
| Key | Env | Default | Purpose |
|---|---|---|---|
quiet_hours.enabled | — | true | The global switch. |
quiet_hours.default | — | null | Quiet hours of users without their own, e.g. ['start' => '22:00', 'end' => '07:00']; equal times throw. |
timezone.resolver | — | DefaultTimezoneResolver::class | A ResolvesTimezone class. |
timezone.attribute | — | timezone | The recipient attribute holding an IANA timezone; blank disables it. |
Digests
| Key | Env | Default | Purpose |
|---|---|---|---|
digest.default | — | off | off, daily or weekly — the frequency of users without a preference. |
digest.time | — | 08:00 | The local HH:MM slot. |
digest.weekly_day | — | monday | The day of a weekly digest, monday–sunday. |
digest.max_items | — | 50 | Lines per digest mail, 1–1000; the rest is “And :count more items in your inbox”. |
digest.lease | — | PT15M | A run still sending after this fails with its entries. |
Reminders
| Key | Env | Default | Purpose |
|---|---|---|---|
reminders.max_lateness | — | P1D | A reminder overdue by more than this (after downtime) whose anchor has passed is skipped. |
reminders.batch | — | 500 | Due rows fired per sweep, 1–10000. |
reminders.lease | — | PT10M | A row still firing after this goes back to pending; its re-fire sends nothing twice. |
Interop
| Key | Env | Default | Purpose |
|---|---|---|---|
interop.map | — | [] | Notification class => one of your type keys. |
interop.suppress | — | [] | A list of classes (every channel), or class => list of channels. |
interop.database | — | redirect | The mapped database channel: redirect (into the inbox), keep or both. |
interop.hold | — | true | Hold mapped mail during quiet hours. |
interop.notifiables | — | [] | Extra recipient classes; models using ReceivesNotifications always count. |
JSON API and resources
| Key | Env | Default | Purpose |
|---|---|---|---|
routes.enabled | NOTIFICATIONS_ROUTES_ENABLED | off | Serve the JSON API — off unless you switch it on. |
routes.prefix | — | notifications | URI prefix; surrounding slashes are trimmed. Route names start with notifications. |
routes.middleware | — | ['api'] | Middleware of every endpoint. |
routes.auth_middleware | — | ['auth'] | Authentication middleware, e.g. ['auth:sanctum']. An emptied list is honoured — the controllers still answer 401 without a user. |
resources.actor | — | NotificationActorResource::class | The JsonResource an item’s actor renders through; receives the actor model. |
resources.subject | — | NotificationSubjectResource::class | Receives the subject model. |
resources.context | — | NotificationContextResource::class | Receives a ContextReference (key, label, morph type and id). Blank or null = the default; anything that is not an instantiable JsonResource subclass throws, naming the key. |
Pruning
| Key | Env | Default | Purpose |
|---|---|---|---|
prune.inbox_after | — | P180D | Read and archived (or deleted) inbox items older than this. |
prune.deliveries_after | — | P30D | Terminal deliveries. |
prune.digest_runs_after | — | P90D | Finished digest runs with no live entry. |
prune.reminders_after | — | P90D | Terminal reminders with no pending or firing escalation. Every prune duration is at least one hour. |
Environment
NOTIFICATIONS_KEY_TYPE=bigint
NOTIFICATIONS_SUBJECT_KEY_TYPE=
NOTIFICATIONS_CONTEXT_KEY_TYPE=
NOTIFICATIONS_PRIMARY_KEY_TYPE=bigint
NOTIFICATIONS_QUEUE_ENABLED=true
NOTIFICATIONS_QUEUE_CONNECTION=
NOTIFICATIONS_QUEUE_NAME=
NOTIFICATIONS_INBOX_RECHECK=false
NOTIFICATIONS_LOCK_STORE=redis
NOTIFICATIONS_PREFERENCES_URL=settings.notifications
NOTIFICATIONS_ROUTES_ENABLED=onIn phpunit.xml write on/off (or 1/0): PHPUnit turns false into an empty string, which means “not set”.
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 cryptoBy 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.