Notifications & Comms
Notification channels, communication templates, customer emails, preferences, and the communication log.
Overview
The Notification & Communication Engine delivers multi-channel notifications (database, email, broadcast) with a three-layer resolution pipeline: system defaults → user preferences → per-send overrides. Customer billing communications persist to communication_log with opt-out and unsubscribe support.
Channel provider registry
Channels register through a provider registry:
| Built-in channel | Purpose |
|---|---|
| Database | In-app notification records |
| Queued SMTP/email driver delivery | |
| Broadcast | Real-time via Reverb |
Plugins can register additional channel providers via the Plugin SDK.
Test a provider configuration: POST /api/v1/channel_providers/{channel_provider}/test.
Three-layer resolution
- System settings —
/admin/settings/notificationsdefines default channels per notification type - User preferences — per-user overrides in profile settings and via API
- Dispatch-time overrides — action classes can force channels for critical sends
Event→notification wiring includes a replay guard so projection replays do not duplicate sends.
Notification bell
The header notification bell loads database notifications and refreshes on open. Real-time updates arrive via Reverb on the user's private channel. Event-specific bodies replace generic placeholders.
Communication templates
Route: /admin/settings/communication-templates/{template}/edit
Merge-field templates power billing emails (invoice, receipt, statement) and customer communications. Templates support version snapshots and restore, parallel to document templates.
API: Communication Templates — CRUD plus version restore.
Email layouts
Every outbound email is wrapped in a branded layout — the document shell, styles, header, body slot, and footer — resolved at send time by EmailLayoutRenderer:
- the layout picked on the email template, when it is still active;
- the active default layout row;
- a hard fallback to the static Blade view
resources/views/emails/layouts/signals.blade.php.
Layouts are database records edited at Admin → Preferences → Email Layouts, with a slot dialect ({{{ body }}} and friends), conditional blocks, escaped branding merge fields, version snapshots, and a live preview. Styles are inlined onto each element after rendering. The default layout is cached for an hour and busted by every layout write.
See Email Layouts.
Customer communications
Billing actions dispatch customer emails through DispatchBillingCustomerCommunication:
- Invoice issued / emailed
- Payment receipt
- Account statement
Each send is logged in communication_log. Customers can opt out via signed unsubscribe links.
| Setting | Key | Purpose |
|---|---|---|
| Unsubscribe URL TTL | notifications.unsubscribe_url_ttl_days |
Signed link expiry (default 90 days) |
| Prune safety window | Effective prune respects TTL + safety margin | Prevents premature log deletion |
Communication drafts
A draft is a customer communication that has been composed but not sent. It is a separate record from the communication log — the log says what went out, and a draft that is discarded was never a communication at all.
Drafts appear on the Communications tab of a rental, invoice, or account, above the log, showing who composed each one (including whether it was an AI agent).
Lifecycle — approving and sending are separate steps. The UI may chain them, but the model never collapses them.
| Status | Meaning | Next |
|---|---|---|
draft |
Composed, awaiting review. The only editable state. | approve / discard |
approved |
A person signed the content off. Frozen. | send |
discarded |
Rejected. Terminal — it can never be sent. | — |
sent |
Handed to the delivery pipeline; communication_log_id is populated. |
— |
Free-text drafts. template_id and merge_data are optional. A draft composed as
prose has no template at all; the approved artefact is always the rendered snapshot in
subject_line / rendered_body.
What is delivered. Sending delivers the approved snapshot verbatim rather than
re-rendering the template — a re-render at send time could produce something no human
ever read. Everything else (opt-out checks, retries, delivery tracking, logging) is the
same pipeline every other customer email uses. template_id is still recorded on the
log for provenance.
Permissions. communications.draft composes; communications.approve approves,
discards, and sends. Approving, discarding and sending are all human-only by
construction: holding the permission is not enough, the acting context must resolve to a
person (or that person's API token). An AI agent may draft, never dispose of a draft —
the MCP surface exposes create-communication-draft and deliberately nothing else.
Sending shares communications.approve rather than taking a permission of its own, on
purpose: whoever may sign off the content is trusted to release it.
Every step also requires view on the record the draft is about, so a caller only ever
sees or acts on drafts for records they could read anyway; the draft index is scoped to
the title types the caller may view.
| Endpoint | Purpose |
|---|---|
GET /api/v1/communication_drafts |
List drafts (Ransack filtering) |
GET /api/v1/communication_drafts/{id} |
Show a draft |
POST /api/v1/communication_drafts |
Compose a draft |
POST /api/v1/communication_drafts/{id}/approve |
Approve, optionally editing first |
POST /api/v1/communication_drafts/{id}/discard |
Reject permanently |
POST /api/v1/communication_drafts/{id}/send |
Deliver an approved draft |
Communication log
Administrators with appropriate permissions can browse dispatched communications — channel, template, recipient, status, and timestamps. Read-only API: Communication Log.
Notification preferences API
| Endpoint | Purpose |
|---|---|
GET/PUT /api/v1/notification_preferences |
Authenticated user's preferences |
GET/PUT /api/v1/accounts/{id}/notification_preferences |
Account-level preferences (admin) |
GET/PUT /api/v1/notification_settings |
System-wide notification settings |
See Notification Preferences API.
Using it in the app
Admin notification settings
Route: /admin/settings/notifications (Admin → Preferences → Notifications)
Permission: notifications.manage on mount and all actions
Header action Open communication log links to /admin/settings/communication-log. The same destination appears as a nested Preferences sidebar item under Notifications, and as a card on the admin landing page.
A short How delivery resolves callout explains the three-layer model (system default → tenant setting → account override) before the tab bar.
Tabs
| Tab | Contents |
|---|---|
| Tenant defaults | Editable types×channels matrix for company defaults |
| User preview | Read-only matrix for a selected account after tenant narrowing |
| Channel providers | Delivery drivers (configure / send test) |
| Recent communication log | Latest outbound rows with a link to the full log |
| Retention | Log retention, prune safety window, unsubscribe link lifetime |
Channel providers
Card per registered driver (In-App, Real-Time, Email) showing status badge (Tested / Failing / Untested).
| Action | Purpose |
|---|---|
| Configure | Slide-over with provider fields (SMTP host, credentials masked on save, and so on) |
| Send test | Fires test delivery; email providers accept an optional test recipient |
Notification types matrix
Types group by category. Groups load collapsed — expand a category to edit. Each row shows name, Required badge, audience (Internal / External / Both), and an enable switch.
For each audience channel row:
| Control | Purpose |
|---|---|
| In-App / Real-Time / Email toggles | Default channels for that type |
| Edit rules | Recipient rules modal — contact, role, specific account, title owner, trigger actor |
| Template link | Opens communication template editor for that type |
Communication template editor
Route: /admin/settings/communication-templates/{template}/edit
| Panel | Contents |
|---|---|
| title + Body | Markdown body with merge-field browser (search + insert) |
| SMS stats | Segment counter when SMS channel applies |
| Version history | Snapshots with change notes; Restore |
| Live preview | Sample vs real entity; optional rental picker; rendered title/body/HTML |
System templates are read-only — Duplicate to customise creates an editable copy.
Header notification bell
The header hosts <livewire:notifications.bell /> for every authenticated user with a linked account.
| Element | Behaviour |
|---|---|
| Bell icon | Unread count badge (caps at 99+) |
| Slide-out pane | Title "Notifications", paginated list, relative timestamps |
| Mark all read / per-item Read | Clears database notifications |
| Footer link | Notification preferences → /settings/notifications |
Real-time updates arrive via Reverb on the user's private channel; the bell also polls every 60 seconds.
My Notifications (user settings)
Route: /settings/notifications (Settings sidebar → Notifications)
Per notification type (grouped by category):
- Mute checkbox
- Channel toggles — only channels your company has enabled (In-app, Email, SMS, Slack, Teams)
Click Save to persist preferences. Users cannot enable channels the tenant has disabled.
Communication log (admin)
Route: /admin/settings/communication-log
Permission: notifications.manage + warehouse scoping on rental subjects
Navigation: Admin landing → Preferences → Communication log; Preferences sidebar nested under Notifications; Notifications page header Open communication log
Filters: Status, Channel, Type, Recipient, From, To — Clear filters
Table: When, Type, Recipient, Channel, Status, title, View
Detail modal: rendered body, status timeline (Queued / Sent / Delivered / Opened), Pin / Unpin, failure reason callout
The same log table partial appears on invoice Communications, rental Communications, and account Communications tabs — scoped to that title.
Rental and account Communications tabs
| Tab | Route | Gate |
|---|---|---|
| Rental | /rentals/{rental}/communications |
rentals.view |
| Account | /accounts/{account}/communications |
Account view policy |
Both render paginated communication log entries for that title.
Public unsubscribe
Signed URLs at GET /communications/unsubscribe/{account}?type={notification_type_key}.
The page shows masked email, explains the opt-out, and offers an Unsubscribe button (POST with CSRF). Success state confirms removal. Link lifetime follows Unsubscribe link lifetime (days) on the notifications admin page.
Permissions summary
| Permission | UI effect |
|---|---|
notifications.manage |
Admin notifications, communication log, template editor |
| Authenticated user | Bell, pane, /settings/notifications |
accounts.view |
Account communications tab (via account access) |
Related documentation
- Invoicing — billing email triggers
- Documents & Templates — PDF attachments in emails
- Email Layouts — the branded chrome around every email
- Admin Panel — notification and communication template settings