Skip to main content
The notifications module is infrastructure, not a messaging platform. It owns one canonical send model: a notification describes the delivery intent (recipient, channel, template, data) and a registered provider turns that into a transport-specific request and sends it. Specialized sends (invoices, payment sessions, reminders, booking documents) are orchestration wrappers over that one shared service, never separate notification systems. It ships as @voyant-travel/notifications, with a provider abstraction, first-party providers for local development and Voyant (email and SMS), database-backed templates and delivery logs, reminder rules and runs, and Hono routes.

Key concepts

The architecture rests on a few clear ideas, kept consistent with the glossary verbs Deliver (push an issued artifact over a channel) and Issue (produce the artifact itself).
  • Templated notifications. Database-backed templates render with provider-agnostic data into transport fields (subject, text, html). Feature code chooses a template and a channel, not a vendor.
  • Multi-channel delivery. A notification names a channel (email, sms, and future channels such as push). Provider resolution happens in the infrastructure layer, by channel, not in feature code.
  • Providers. A provider is a transport implementation. It formats the vendor request, sends it, and returns a result in the shared shape. It does not own product business rules.
  • Channels. The delivery surface (email, SMS) that selects which registered provider sends a message.
  • Delivery. What was sent, through which channel, by which provider, with what result. Notifications guarantees only delivery intent and the transport result it actually knows, never a stronger promise than the provider can offer.
Notification delivery is transport, not CRM history. This module owns templates, delivery attempts, provider message ids, and reminder-oriented sends. Customer-relationship timelines belong to a higher-level product surface, not the transport layer.

What it owns

Providers, channels, and delivery

Providers implement the NotificationProvider contract. Which one sends is decided by the deployment’s email and sms provider roles, and the bring-your-own path is first class: implement NotificationProvider against Resend, Twilio, SES, or anything else and select it as the role’s provider. When you register a set of providers, later providers override earlier ones on a channel conflict, and sendWith(name, payload) dispatches to a named provider directly.

Templates and delivery logs

Templates and delivery logs are database-backed (exposed through the ./schema and ./validation subpaths and managed through routes). Delivery logs record the transport result, channel, provider, and provider message id, which is the honest record of what the runtime actually did.

Reminders

Reminder rules drive scheduled operational sends and can currently target a booking_payment_schedule or an invoice. A scheduled sweep is run with sendDueNotificationReminders(...). Stage cadence and maxSendsInStage are evaluated against reminder run attempts: a queued, sent, skipped, or failed run consumes the stage slot, and a failed run is terminal until an operator or recovery flow requeues it.

Finance-aware and document sends

Specialized routes compose the core service for collection and document flows: sending a payment session or an invoice, listing a booking’s document bundle, and sending booking documents. These resolve recipients from the payment session, invoice, and linked booking travelers, then render the selected template with finance context such as payment links, invoice balances, and booking references. Booking document sends bundle the latest customer-facing contract attachment and the ready invoice or proforma rendition for a booking. Sensitive attachments resolve access at send time from durable storage metadata rather than relying on stale persisted signed URLs. Public, editorial assets may use stable public URLs, but private documents use signed or authenticated access. Override resolution with documentAttachmentResolver (or resolveDocumentAttachmentResolver) when mounting the routes so attachment URLs reflect the current runtime and storage context.

Working with it

Create the service with a provider set and send a notification through the shared surface:
You do not assemble the module by hand. @voyant-travel/notifications is part of the standard product graph and contributes its runtime ports through the runtime entry in its voyant.package.v1 manifest. Which transport actually sends is a provider selection, made in the deployment profile rather than in code:
Provider bindings resolve at boot inside the fixed graph, so swapping a transport never changes module or route code. See Configuration and Choose or swap a provider. Lifecycle sends are driven by declared subscribers, not by a lifecycle option object. The package declares each subscriber in its voyant.package.v1 manifest and the graph activates it exactly once:
The older documentBundleLifecycle option is gone, and its return is blocked by an architecture check (check-notifications-subscriber-authority). The module must not hide event-bus subscriptions or re-introduce document lifecycle orchestration behind a config object. Subscribers are declared, activated once, and visible in the graph.
Run a reminder sweep on a schedule (from a workflow or cron):
Workflows, routes, and subscribers are all fine trigger points, but delivery should always converge on the shared notification service. Do not open-code provider-specific delivery in a workflow or feature module.
  • Finance. Payment-session and invoice sends, collection reminders, and the fully-paid document bundle all read finance context (payment links, invoice balances) and compose finance generators. See Finance.
  • Legal. Booking document sends bundle the latest customer-facing contract attachment; the confirmation policy generates a contract through ensureLegalDocuments. See Legal.
  • Bookings. Recipients resolve from linked booking travelers, and the document bundle lifecycle subscribes to booking confirmation and fully-paid signals.
  • Inventory. Product brochures stay an extension point via resolveBrochureDocuments, so apps that install @voyant-travel/inventory can add brochure artifacts without making notifications depend on products at runtime.

React package

@voyant-travel/notifications-react provides hooks, a client, query keys, reusable UI, and admin surfaces for template management, delivery listing, and reminder management.
It exposes the standard family of subpaths (./hooks, ./client, ./query-keys, ./ui, ./admin, and ./components/*).

Next steps

Finance

The payment sessions, invoices, and schedules behind finance-aware sends.

Legal

The contracts bundled into booking document sends.

Jobs

Run reminders and delivery through package-owned background work.

Glossary

The Issue and Deliver verbs and the channel vocabulary.