# OpenSpec Change: enterprise-notification-channel - **Change ID**: enterprise-notification-channel - **Package**: `@suaveplan/notification-channel` - **Category**: universal - **Target path**: `universal/messaging/notification-channel` - **Status**: IN PROGRESS - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/notification-channel.md` --- ## Summary Deliver `@suaveplan/notification-channel` on the genesis branch from scratch at enterprise quality. The package provides multi-channel notification routing: a `NotificationChannel` interface, concrete channel adapters (email, SMS, push/in-app, Slack webhook, Discord webhook, Telegram), a `NotificationRouter` that dispatches a notification through registered channels based on priority rules, template rendering per channel via `@suaveplan/email-templates` or `@suaveplan/templates`, preference-aware routing (per-recipient opt-in/opt-out per channel), delivery tracking integration, and a `NotificationService` orchestrator. The main-branch version sat at 95.61% coverage. Genesis adds structured errors, Zod schemas, OTel instrumentation, retry, and property-based tests. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `NotificationError`; `NOTIF.*` error codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock`, `EventBusLike` | | `@suaveplan/events` (`workspace:^`) | Notification lifecycle events | | `@suaveplan/templates` (`workspace:^`) | Channel-specific template rendering | | `@suaveplan/email-templates` (`workspace:^`) | Email-specific template rendering | | `@suaveplan/retry` (`workspace:^`) | Retry on channel send failures | | `@suaveplan/suppression` (`workspace:^`) | Email/SMS suppression list check | | `zod` (`catalog:forms`) | Schema validation | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`). --- ## Public API surface ### Core types ```ts type ChannelType = 'email' | 'sms' | 'push' | 'in-app' | 'slack' | 'discord' | 'telegram' interface Notification { id: string recipient: NotificationRecipient templateName: string channels: ChannelType[] context: Record priority: 'low' | 'normal' | 'high' | 'critical' metadata: Record } interface NotificationRecipient { id: string email?: string phone?: string pushToken?: string slackUserId?: string discordUserId?: string telegramChatId?: string | number preferences?: ChannelPreferences } type ChannelPreferences = Partial> interface NotificationResult { notificationId: string channel: ChannelType status: 'sent' | 'failed' | 'suppressed' | 'skipped' error?: string } ``` ### `NotificationChannel` interface ```ts interface NotificationChannel { readonly type: ChannelType send(recipient: NotificationRecipient, subject: string, body: string, options?: ChannelSendOptions): Promise isAvailableFor(recipient: NotificationRecipient): boolean } ``` ### `NotificationService` ```ts class NotificationService { constructor(options: NotificationServiceOptions) register(channel: NotificationChannel): void send(notification: Notification, options?: SendOptions): Promise sendBatch(notifications: Notification[], options?: BatchOptions): AsyncGenerator } interface NotificationServiceOptions { channels?: NotificationChannel[] fallbackChain?: ChannelType[] // try channels in order until one succeeds logger?: LoggerLike bus?: EventBusLike } ``` ### Error codes ```ts type NotificationErrorCode = | 'NOTIF_NO_CHANNEL' | 'NOTIF_ALL_FAILED' | 'NOTIF_TEMPLATE_NOT_FOUND' | 'NOTIF_RECIPIENT_INVALID' | 'NOTIF_SUPPRESSED' | 'NOTIF_SEND_FAILED' | 'NOTIF_ABORTED' ``` --- ## Genesis additions 1. **`NotificationError`** extends `SuaveplanError` with seven stable codes. 2. **Zod schemas** for `Notification`, `NotificationRecipient`, `ChannelPreferences`, `NotificationResult`, `NotificationServiceOptions`. 3. **Preference-aware routing** — before dispatch, checks `recipient.preferences[channel]`; `false` → `skipped` status. 4. **Suppression integration** — email and SMS channels call `@suaveplan/suppression` before dispatch; suppressed → `suppressed` status. 5. **Fallback chain** — if primary channel fails, try next channel in `fallbackChain` until one succeeds or all fail. 6. **OTel instrumentation** — counter `notification.send_total` with attributes `notification.channel`, `notification.status`; histogram `notification.send_duration_ms`. 7. **Property-based tests** — channel preference filter correctly skips opted-out channels for arbitrary preference maps. --- ## File layout ```text universal/messaging/notification-channel/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/ index.ts types/ common.ts common.md core/ notification-service/ notification-service.ts notification-service.md notification-service.types.ts notification-service.test.ts index.ts notification-router/ notification-router.ts notification-router.md notification-router.test.ts index.ts channels/ email-channel/ email-channel.ts email-channel.md email-channel.test.ts index.ts sms-channel/ sms-channel.ts sms-channel.md sms-channel.test.ts index.ts in-app-channel/ in-app-channel.ts in-app-channel.md in-app-channel.test.ts index.ts webhook-channel/ webhook-channel.ts webhook-channel.md webhook-channel.test.ts index.ts __benchmarks__/ notification-channel.bench.ts tests/ integration.test.ts property.test.ts ``` --- ## Impact **Downstream consumers**: any server-side notification dispatch service. **Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/templates`, `@suaveplan/email-templates`, `@suaveplan/retry`, `@suaveplan/suppression`.