---
title: "Configuration — Notifications for Laravel | Roundly"
description: "Every key of config/notifications.php with its env variable and default — key types, tables, queue, inbox, mail, quiet hours, digests, routes, pruning."
url: https://roundly-consulting.com/open-source/docs/notifications-for-laravel/configuration
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Notifications for Laravel](https://roundly-consulting.com/open-source/docs/notifications-for-laravel.md)

# Configuration

The package works with zero configuration. Publish config/notifications.php only to override:

```bash
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

```php
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. |

## Mail

| 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

```bash
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=on
```

In 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 crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/notifications-for-laravel.md)
