Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Notifications Laravel Package

liberusoftware/notifications

A Laravel notifications package offering a unified API to send alerts across multiple channels, with templating, queued delivery, and flexible configuration. Integrates with Laravel's queue, mail, and database systems for app-wide messaging.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Liberu ecosystem module (composer package liberusoftware/notifications, repo module-notifications) that layers four things on top of Laravel's native notification system: localized templates, per-user channel preferences, a persistent inbox, and delivery governance (retries, delivery status, quiet hours). Treat it as a routing + persistence layer over Illuminate\Notifications, not a replacement for it.

Minimal path to first value:

composer require liberusoftware/notifications
php artisan vendor:publish --provider="Liberu\Notifications\NotificationsServiceProvider"
php artisan migrate

Because this is a modular package with zero dependents and a pre-1.0 feel (last release dated in the future, no ecosystem usage to copy from), read the source before writing code. Budget ~30 minutes in this order:

  1. src/NotificationsServiceProvider.php — what it registers, what's publishable (config/notifications.php, migrations, translations), and whether it auto-discovers or needs manual registration in bootstrap/providers.php (Laravel 11+) or config/app.php (older).
  2. config/notifications.php — the real contract surface. Look for: default channels, queue connection/name, template disk/path, quiet-hours defaults, retry/backoff values, and the model/table names it expects.
  3. database/migrations/* — the inbox and delivery-status tables. This is where the actual data model lives; read it before you design around it.
  4. resources/lang/ (or lang/) — the template keys and placeholder conventions. Localization is the headline feature, so the key format matters more than anything else.

First use case: transactional notification to a known user — e.g. "order shipped" — rendered from a localized template, respecting the user's channel preferences, with the result visible in their inbox. That single flow exercises templates, preferences, inbox, and delivery status at once. Do not start with quiet hours or retries; those are the parts most likely to behave surprisingly.

Sanity check before building on it: confirm the package's notification class is actually discoverable (php artisan about, or php artisan tinker and inspect the container binding), and that migrations run cleanly in your modular app's migration order.

Implementation Patterns

Route everything through the module, not Notification::send() directly. The value of this package is the middleware-ish path between "you want to notify someone" and "the channel driver fires." If you call Laravel's facade directly you bypass preferences, quiet hours, inbox persistence, and delivery status — you get none of the features you installed it for and a half-populated inbox. Pick one entry point (a notifier service, a trait on your User, or the module's notification base class) and standardize on it.

Template-driven notifications. The idiomatic pattern is: one notification class per event (shipping confirmation, password reset), one template key per class, with the template doing locale lookup and placeholder substitution. Keep the class thin:

// Pseudocode — verify the actual base class and method names in src/
class OrderShipped extends TemplateNotification
{
    public function templateKey(): string { return 'orders.shipped'; }

    public function payload(Model $notifiable): array
    {
        return ['order_id' => $this->order->reference, 'carrier' => $this->order->carrier];
    }
}

Placeholder payloads should be scalars or pre-formatted strings, not Eloquent models. Passing models into a template layer invites lazy-loading inside a queued job after the model has changed, plus serialization surprises.

Channel preferences are a resolution step, not a config flag. Expect preferences to resolve in a cascade — explicit user preference → notification-class default → config default → forced channels. When you add a new channel (SMS, push), you'll need to register it in both the module's channel list and Laravel's notification channel manager. Adding it in one place only is the most common integration bug.

Inbox as a read model. The inbox table is a persisted record of delivered/sent notifications for display. Treat it as append-mostly read model: don't write business logic against it, and don't assume read_at semantics — check whether "read" is a column, a separate table, or a status enum. Queries you'll write constantly:

  • unread count for a badge (cache it; don't count() on every page render)
  • paginated list ordered by created_at desc for the inbox page
  • mark-all-read bulk update (do it as one UPDATE, not per-row saves)

Eager-load anything the inbox view needs for display (the related notifiable/subject) or you'll get N+1s on the exact page users visit most.

Retries and delivery status: let the queue do the work. Queue the notification and rely on the module's retry/backoff config rather than wrapping sends in your own try/catch + retry() loops. Two retry mechanisms stacked on top of each other produce duplicate deliveries and confusing status transitions. Configure tries/backoff in the module config, ensure failed_jobs is migrated, and monitor the delivery-status table for rows stuck in a non-terminal state — that's your actual observability signal.

Quiet hours = deferral, not suppression. The pattern to implement is: if the send falls inside a quiet window, schedule it for the window's end rather than dropping it. Reserve immediate/unsuppressed delivery for security-critical notifications (password reset, 2FA, payment failure) — check whether the module exposes a bypass and use it explicitly for those classes only.

Testing pattern. Use Notification::fake() and Queue::fake() in feature tests, and Carbon::setTestNow() for quiet-hours and scheduling tests — quiet hours are the single hardest thing to test and the easiest to get wrong. Assert on the module's own records (template key resolved, locale resolved, preference resolution outcome) rather than on rendered HTML, which will churn.

Integration tips for the Liberu context. In a modular app, keep the module's config in config/notifications.php rather than reaching into the package vendor directory, and register your app's own templates in the app's lang/vendor/notifications/ overrides so they don't get blown away by composer update.

Gotchas and Tips

Zero dependents means you are the QA team. No Stack Overflow answers, no example apps, no issue history to learn from. Pin the version in composer.json, commit composer.lock, and be prepared to read src/ for every question. Wrap the module's public surface in your own thin service class so a breaking change in a patch release touches one file instead of forty.

Quiet-hours timezone bugs. Quiet hours must be evaluated in the user's timezone, not app.timezone. Check where the timezone comes from (user column? config? missing entirely?) before shipping, and confirm whether windows crossing midnight (22:00–07:00) are handled — this is the classic off-by-one in quiet-hours implementations. Test it explicitly.

Queued vs sync divergence. Preferences, locale, and quiet hours resolve differently depending on whether the notification runs in the request cycle or a worker. Locale in particular: the queued job may not carry the notifiable's locale unless the module explicitly serializes it. Verify with a real queue worker (not QUEUE_CONNECTION=sync) before you trust it.

Retry idempotency. If a notification partially succeeds (sent to mail, failed on SMS) and retries, does it re-send mail? Unless the module tracks per-channel delivery status, it will. This is the most likely source of "user got three emails" reports. Check the schema for per-channel rows vs. one row per notification — the answer determines whether you need your own dedupe.

Preference cache staleness. Preference lookups are usually cached. If a user changes their preferences and nothing happens until the cache expires, look for the cache key TTL and an invalidation hook. Have a documented way to flush it (a console command or Cache::forget) and call it after preference writes.

Template key mismatches fail quietly. A missing template key often degrades to a blank or default message rather than throwing — which means broken notifications reach production silently. Add a test that asserts every notification class resolves a non-empty template in every configured locale, and fail CI on it.

Locale fallback order. Confirm the fallback chain (user locale → app locale → config fallback → hardcoded default) and that missing translations don't render raw keys to end users. Add one locale you don't fully translate to your test matrix to see what the fallback actually looks like.

Migration ordering in modular apps. Published migrations with future-ish timestamps can sort oddly relative to your app's migrations. If migrate fails on a foreign key, check ordering first.

Pruning. Inbox and delivery-status tables grow monotonically. Confirm whether the module ships a prune command / MassPrunable model; if not, write a scheduled job to delete read inbox entries older than N days.

Extension points to look for (in priority order): the channel resolution / preference resolver binding (swap to implement custom rules), the template renderer (swap for a DB-backed or CMS-backed source), and the delivery-status model (add observers for alerting). These three are where the package is most likely designed to be overridden and where your custom requirements will land.

Escalation trigger. If you find yourself patching vendor/, that's the signal to fork to a private repo or reimplement on native Illuminate\Notifications + a small preferences table. The feature set described here is maybe 400–600 lines of custom code; with no upstream community, the maintenance tradeoff is worth evaluating honestly around month three.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console