> ## Documentation Index
> Fetch the complete documentation index at: https://voyant.travel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Notifications

> The notifications module is Voyant's notification transport layer: templated, multi-channel delivery through a provider abstraction, with delivery logs, reminders, and finance-aware sends.

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.

```bash theme={null}
pnpm add @voyant-travel/notifications
```

## Key concepts

The architecture rests on a few clear ideas, kept consistent with the [glossary](/docs/concepts/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.

<Note>
  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.
</Note>

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

```ts theme={null}
import { createNotificationService } from "@voyant-travel/notifications"

// Providers are resolved from the deployment's selected `email` and `sms`
// provider roles; the service never names a vendor.
const notifications = createNotificationService(resolvedProviders)

await notifications.send({
  to: "user@example.com",
  channel: "email",
  template: "welcome",
  subject: "Hello",
  html: "<p>Welcome</p>",
})
```

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:

```ts theme={null}
import { defineConfig } from "@voyant-travel/framework/project"

export default defineConfig({
  deployment: {
    target: "node",
    mode: "self-hosted",
    providers: {
      email: "…",
      sms: "…",
    },
  },
})
```

Provider bindings resolve at boot inside the fixed graph, so swapping a transport never changes module or route code. See [Configuration](/docs/platform/fundamentals/configuration) and [Choose or swap a provider](/docs/guides/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:

```text theme={null}
notificationsReminderSubscriber
notificationsBookingConfirmedReminderSubscriber
notificationsBookingCancelledReminderSubscriber
notificationsCheckoutFinalizedReminderSubscriber
notificationsPaymentCompletedReminderSubscriber
notificationsContractDocumentReminderSubscriber
notificationsInvoiceRenderedReminderSubscriber
notificationsProductContentReminderSubscriber
```

<Warning>
  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.
</Warning>

Run a reminder sweep on a schedule (from a workflow or cron):

```ts theme={null}
import { sendDueNotificationReminders } from "@voyant-travel/notifications/tasks"

await sendDueNotificationReminders(db, process.env, {
  now: "2026-04-08T09:00:00.000Z",
})
```

<Note>
  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.
</Note>

## Links to other modules

* **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](/docs/platform/modules/finance).
* **Legal.** Booking document sends bundle the latest customer-facing contract attachment; the confirmation policy generates a contract through `ensureLegalDocuments`. See [Legal](/docs/platform/modules/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.

```tsx theme={null}
import { NotificationsProvider } from "@voyant-travel/notifications-react/provider"
```

It exposes the standard family of subpaths (`./hooks`, `./client`, `./query-keys`, `./ui`, `./admin`, and `./components/*`).

## Next steps

<CardGroup cols={2}>
  <Card title="Finance" icon="receipt" href="/docs/platform/modules/finance">
    The payment sessions, invoices, and schedules behind finance-aware sends.
  </Card>

  <Card title="Legal" icon="file-signature" href="/docs/platform/modules/legal">
    The contracts bundled into booking document sends.
  </Card>

  <Card title="Jobs" icon="clock" href="/docs/platform/jobs">
    Run reminders and delivery through package-owned background work.
  </Card>

  <Card title="Glossary" icon="book-open" href="/docs/concepts/glossary">
    The Issue and Deliver verbs and the channel vocabulary.
  </Card>
</CardGroup>
