Download the PHP package fomvasss/laravel-notify-templates without Composer

On this page you can find all versions of the php package fomvasss/laravel-notify-templates. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-notify-templates

laravel-notify-templates

DB-based notification templates for Laravel. Manages notification templates, role/user subscriptions, channel resolution, and delay — without coupling to any specific role package.

Українська: README.uk.md

Concepts


Installation

Requires PHP 8.2+, Laravel 10–13, and PostgreSQL or MySQL 8.0.13+ (the migration uses functional unique indexes on COALESCE(...); MariaDB does not support them).

Publish and run migrations:

Publish config (optional):


Configuration

config/notify-templates.php:


Type Registry

Auto-discovery (recommended)

By default the package scans app/Notifications on every boot — no code needed in the app. Configured via discover in the config:

Scanning is recursive — subdirectories like Notifications/Order/, Notifications/User/ are included automatically. Set to [] to disable.

The package discovers all classes that extend BaseNotify and return a non-empty typeDefinition(). Safe in production — types are registered once on boot, the singleton is read-only during requests.

Manual call is also available:

Manual registration

For dynamic types (e.g. generated from DB records), register in AppServiceProvider::boot():

Or statically via config:

typeDefinition() fields

Field Type Description
key string Unique identifier, e.g. 'OrderOrdered'
name string Human-readable label for UI
group string Grouping key for UI tables, e.g. 'order'
weight int Sort weight within the group; lower values appear first
desc string Optional description shown as tooltip in the UI table
settings array Option keys editable in the admin UI, stored in notify_role_subscriptions.options
tokens array Token hints for the template editor: [['key' => '[order:number]', 'name' => 'Номер']]
channels array Channels this notify type supports. Empty (default) — falls back to config('notify-templates.channels')
defaults array Default subject/body per channel slot, used as placeholder in the editor when no DB template exists
user_configurable bool false — the notifiable can't opt out of the type or restrict its channels, and an empty channel resolution falls back to default_channels. Default true. See Non-configurable types
log_body bool false — the delivery log stores the subject only, never the body (OTP codes, passwords). Default true. See Delivery log
buttons array Link buttons under a messenger message: [['text' => 'Pay', 'url' => '[order:payUrl]']], tokens allowed in both; text may be a locale map. See Messenger buttons
buttons_by_role array Buttons for a specific role, replacing buttons: ['admin' => [...]]; [] — no buttons for that role
buttons_columns int Buttons per row. Default 1

tokens and defaults are UI metadata — the package does not use them for sending. getBodyDefault() / getSubjectDefault() on BaseNotify read from defaults.mail automatically. Keep them in sync.

Custom keys. registerType() stores the whole array returned by typeDefinition() as-is — any key beyond the table above survives untouched and comes back from NotifyTemplates::getType($notifyKey)['your_key']. Useful for project-specific behavior without forking the package. The package itself ignores these keys: your admin controller, UI or import enforces them. Two examples from a production app:

always_sent matters only for types sent without a role resolver. A type with user_configurable = false that still goes through NotifyRoleResolverInterface is disabled for real by an inactive subscription: the resolver returns no recipients for that role.

settings field

settings declares which option keys are shown in the admin UI. The only key the package reads natively is delay:

Any other keys are project-defined — read them via $subscription->getOption('key').

Recommended controller pattern — save all settings keys generically so adding a new option requires no controller changes:

Retrieve registered types:


User model — HasNotifySettings

Add the trait to your User model with a notify_channels column (cast to array):

Override getNotifyChannels() if your column has a different name:

getNotifyChannels() defines the user's preferred channels. The result is intersected with the channels configured in notify_role_subscriptions — the user can opt out of channels but cannot add new ones beyond what the role allows. If the user returns [] or the method is absent, all subscription channels are used.

Per-type opt-out & channel override (notify_user_settings)

Two independent, optional things a notifiable can record per notify type — both on the same row, both default to "not customized":

Both work for any Eloquent model — no trait or interface required on the notifiable. Absence of a row = fully default (enabled, no channel override). A row only ever exists because something explicit was recorded — typically from a profile settings form:

To read the current state outside of a Notification (e.g. to render the toggle in a settings form), either query NotifyUserSetting directly, call the facade — NotifyTemplates::isNotifyEnabled($notifyKey, $user) / NotifyTemplates::resolveNotifyUserChannels($notifyKey, $user) — or add HasNotifySettings to the model (it already carries isNotifyEnabled() alongside getNotifyChannels()).

The notifiable_id column is a string, not an integer FK — so it works whether the host app's primary keys are auto-increment integers or UUIDs.

Non-configurable types (OTP, security codes)

Some notify types must never be opt-out-able or channel-restricted by the notifiable — e.g. an OTP/login code: if the user could turn that off in their profile, they'd lock themselves out. Opt a type out with typeDefinition():

With user_configurable: false, isNotifyEnabled() always returns true and resolveNotifyUserChannels() always returns null for that type — regardless of any notify_user_settings row that might exist for it (defense in depth, not just a UI-level hide). Use NotifyTemplates::isUserConfigurable($notifyKey) to filter such types out of a settings form's list of toggles.


NotifyRoleResolverInterface

Implement to resolve which users receive a given notify type. Bind in your ServiceProvider:

Note: personal_only, wherever it comes from (the subscription's own flag, or the whitelist force above), redirects delivery to $context's user regardless of which role the subscription row belongs to — it does not mean "send to one specific person holding this role". Enabling it on a BROADCAST_SAFE_ROLES row (e.g. admin) does not reach any actual staff member; it just re-sends the same message to $context->user again, formatted with that role's template. There is no per-notification concept of "this one specific admin" — only "the person the event is about" vs "everyone holding a role".


Artisan commands

The Notify suffix is added automatically. Nested namespaces are supported:

The generated stub includes typeDefinition() with all fields pre-filled and a prepareText() hook ready to override. To customise the stub — copy it to stubs/notify.stub in your project root:


Concrete Notify classes

Extend BaseNotify. Generate with php artisan notify:make, fill typeDefinition(), and add constructor arguments for the models you need.

getBodyDefault() and getSubjectDefault() are derived automatically from typeDefinition()['defaults']['mail'] — no need to define them.

Hooks available for the host app to override:

manager() and resolveTemplate() are protected — accessible from a trait or base class mixed into concrete classes.

For types sent directly without an event/listener (e.g. OTP):

Use only() or except() to override channels at call site:


Extending in the host app

The typical setup is one abstract base class in the app that extends BaseNotify and adds project channels + token processing; every concrete Notify then extends it:

Do not copy via() into the host app. Override mapChannel() instead. A copied via() freezes the resolution chain at the moment of copying — every package fix to it (opt-out handling, fallback semantics, …) then silently doesn't apply until you manually sync the copy.

Messenger buttons

A long link in a messenger text reads badly; put it on a button under the message instead. Defaults live in typeDefinition(), and a messenger template row can override them from your admin UI via options:

Resolution: template options.buttons → buttons_by_role[role] → buttons. An array wins even when empty, so "buttons": [] in a template removes the type's default buttons. Leave the key out to keep the defaults. Entries with an empty text or url are skipped.

The package doesn't render messages for host channels, so the buttons go into your toTelegram():

getMessengerButtons() runs prepareText() on text and url, then drops buttons whose url isn't an absolute http(s) link on a public host: an unresolved token or a *.test / localhost url makes Telegram reject the whole message, not just the button. Override isSendableButtonUrl() to allow e.g. a tunnel. Channels without buttons (SMS, WhatsApp text) can append the links to the text instead.

With the delivery log on, TelegramContentResolver appends url buttons to the logged body as [text] url.

Multilingual text. text can be a locale map instead of a string, in typeDefinition() and in template options alike. The url stays shared:

The text for the current locale is taken, then app.fallback_locale, then the first non-empty entry. Laravel switches the locale per notifiable that implements HasLocalePreference (or via ->locale()), so every recipient gets their language. NotifyTemplates::localizeButtonText($text, $locale) does the same for an admin UI. options lives on notify_templates, not in astrotomic's translation table, so this map is the way to translate buttons there.


Listeners

Overall flow, end to end:


DB data examples

notify_role_subscriptions — which roles receive which notify types:

role_key notify_key tenant_id is_active personal_only channels options
client OrderOrdered null 1 1 ["mail","sms"] {"delay": 0}
manager OrderOrdered null 1 0 ["mail","telegram"] {"delay": 0}
client OrderOrdered shop-ua 1 1 ["mail","telegram"] {"delay": 2}

personal_only=true — send only to the user from the event context (e.g. the client who placed the order).

notify_templates — subject/body per notify type, channel slot, role, tenant:

notify_key channel role_key tenant_id subject body
OrderOrdered mail null null Замовлення оформлено Ваше замовлення [order:number] прийнято...
OrderOrdered mail client shop-ua Дякуємо за замовлення Привіт, [user:name]! Замовлення [order:number]…
OrderOrdered messenger null null null Замовлення [order:number] оформлено

Channel slots in notify_templates:


Facade reference


Channel resolution flow

Every notification goes through a fixed resolution chain inside via(). Each step can only restrict channels — it cannot add ones that earlier steps excluded. typeDefinition()['channels'] / getTypeChannels() are not part of this chain — that's a separate, UI-only listing (see note at the bottom).

Practical examples:

Scenario Result
No subscriptions in DB, nothing configured nothing sent (user_configurable => false types: mail from default_channels)
Subscription active, channels [] in DB mail (from default_channels)
Subscription active, user's notify_user_settings.channels = [] for this type nothing sent — the notifiable disabled the whole type
Subscription channels ['mail','telegram'], user has no telegram id mail only
Subscription channels ['mail','telegram'], user getNotifyChannels() returns ['mail'] mail only
Subscription channels ['mail','telegram'], user prefers both, but has a notify_user_settings.channels = ['telegram'] override for this one type telegram only — for this type; other types are unaffected
notify_user_settings.is_enabled = false for this type, but typeDefinition()['user_configurable'] = false still sent — the opt-out row is ignored
->only(['telegram']) at call site telegram only, regardless of subscription

config('notify-templates.channels') and typeDefinition()['channels'] (via getTypeChannels()) are the UI listing only — they drive the checkboxes on the admin edit form. Neither has a direct effect on the send path above.


Template fallback chain

resolveTemplate('OrderOrdered', 'mail', 'client', 'shop-ua') tries in order:

  1. notify_key=OrderOrdered, channel=mail, role=client, tenant=shop-ua ← most specific
  2. notify_key=OrderOrdered, channel=mail, role=client, tenant=null
  3. notify_key=OrderOrdered, channel=mail, role=null, tenant=shop-ua
  4. notify_key=OrderOrdered, channel=mail, role=null, tenant=null
  5. notify_key=OrderOrdered, channel=null, role=client, tenant=shop-ua
  6. notify_key=OrderOrdered, channel=null, role=client, tenant=null
  7. notify_key=OrderOrdered, channel=null, role=null, tenant=shop-ua
  8. notify_key=OrderOrdered, channel=null, role=null, tenant=null ← global fallback

Returns the first match, or null — BaseNotify then falls back to getBodyDefault() / getSubjectDefault().


Delivery log

Opt-in journal of every sent notification: one notify_logs row per notification × channel × recipient. Covers BaseNotify subclasses only.

mergeConfigFrom() merges only top-level keys, so a published config needs the whole log block, including the new keys after an upgrade.

Existing installs: php artisan vendor:publish --tag=notify-templates-migrations publishes only the migrations you don't have yet (create_notify_logs_table, add_content_to_notify_logs_table).

What gets written:

Delivery status

pending → sent → delivered → read, plus failed. sent means the provider accepted the message. delivered/read exist only where the provider reports them: WhatsApp, Viber, SMS gateways with DLR, ESP webhooks. For mail over plain SMTP or Telegram bots, sent is final.

Feed provider reports (webhook or status poll) into:

The status only moves forward: reports arrive out of order (e.g. delivered after read), and a stale one is ignored. failed is accepted over sent but not over delivered/read. The method returns false when nothing matched or the report was ignored.

$channel is the channel name as via() returned it ('mail', 'telegram' or a channel class-string). Bind an id resolver for each channel whose reports you process:

Status labels

NotifyLog::statusLabels() returns translated labels keyed by status, and $log->getStatusLabel() returns the label for one row. The package ships en and uk. To change the wording or add a locale, publish the translations and edit lang/vendor/notify-templates/{locale}/log.php:

Pruning

NotifyLog is MassPrunable. Rows older than retention_days are removed by model:prune, which you need to schedule:


Queues & Octane

Queues — fully safe. Types are registered once in boot(), DB queries in resolveChannels / resolveDelay / resolveTemplate are fresh per call.

Octane — safe. The NotifyTemplatesManager singleton is intentionally long-lived: $types is populated once on boot and only read during requests — no request-scoped state is stored.

Caveat: call registerType() / registerTypes() / discoverIn() only in ServiceProvider::boot(), never during request handling — a mutation would persist across all Octane requests.

Optionally pre-resolve the singleton:


Multilingual templates (astrotomic/laravel-translatable)

Override the NotifyTemplate model via config to add translation support without touching the package.

1. Migration in your project:

2. Extend the model:

3. Point config to your model:

$template->subject now returns the current locale's translation — BaseNotify::toMail() and getMessengerBody() require no changes.

Locale in queues — implement HasLocalePreference on User:

Laravel reads this automatically and sets the locale before toMail() / toTelegram() — even in queued jobs.


All versions of laravel-notify-templates with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
illuminate/support Version ^10.0|^11.0|^12.0|^13.0
illuminate/database Version ^10.0|^11.0|^12.0|^13.0
illuminate/notifications Version ^10.0|^11.0|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package fomvasss/laravel-notify-templates contains the following files

Loading the files please wait ...