# Ship @suaveplan/notification-channel **Package**: `@suaveplan/notification-channel` **Status**: PROPOSED **Type**: new-package (migration) - **Design**: ./design.md (inlined below — single-file change) - **Spec delta**: ./specs/notification-channel/spec.md ## Why Genesis needs a unified multi-channel notification router so downstream services (api-plugin-notifications, marketing-ops, alerting) have a single, deterministic surface for delivering messages over email, SMS, push, in-app, and webhooks. The upstream `@suaveplan/notification-channel@1.2.6` ships in `../suaveplan/packages/universal/notification-channel/` with 34 source files and ~95% coverage; this change lands a feature-complete port at 100/100/100/100 coverage, rebuilt against the genesis foundation (`defineCodes` registry, `EventBus`, Zod boundaries, OTel meters, injectable `FetchLike`/`Clock`). ## What changes This is a brand-new package — every export is ADDED. - ADDED: `NotificationRouter` — primary entrypoint; routes notifications across channels with template rendering, sliding-window rate limiting, status lifecycle, EventBus emission, optional delivery-channel dispatch. - ADDED: `EmailChannel`, `SmsChannel`, `PushChannel`, `InAppChannel`, `WebhookChannel`, `DigestChannel` — six `DeliveryChannel` implementations. - ADDED: `TemplateEngine` — channel-specific template variants with `{{var}}` substitution. - ADDED: `ChannelRateLimiter` — sliding-window per-channel + per-user rate limits. - ADDED: `DeliveryRetry` — exponential-backoff retry wrapper with dead-letter queue. - ADDED: `NotificationScheduler` — schedule-for-future + interval processing. - ADDED: `PreferenceManager` — per-user opt-in/out + quiet-hours, storage-adapter pluggable. - ADDED: `NOTIFICATION_CHANNEL_CODES` — `defineCodes("NOTIFICATION_CHANNEL", […])` registry with 11 stable reason codes. - ADDED: `notificationHooks` / `onNotificationRouting` — pre-routing hook pipeline. - ADDED: `FakeFetch` test double for `FetchLike`. - ADDED: OTel meters: `notification_channel.routed`, `notification_channel.delivery_attempts`, `notification_channel.dead_letters`, `notification_channel.latency_ms`. - ADDED: Zod schemas at every public option boundary. ## Source mapping This change derives from `../suaveplan/enterprise/notification-channel.md` and the upstream package source. | Requirement | Source lines | Notes | |---|---|---| | Multi-channel router with EventBus + lifecycle | `../suaveplan/packages/universal/notification-channel/src/router/notification-router.ts` L1-L313 | Re-shaped onto genesis `EventBus`; injectable `Clock`. | | Email / SMS / Push / InApp / Webhook channels | `src/channels/*.ts` (upstream) | Webhook uses injectable `FetchLike`. Push uses injectable `FetchLike` for VAPID POST. | | DigestChannel | `src/digest/digest-channel.ts` | Injectable `Clock` for interval gating. | | TemplateEngine with channel variants | `src/templates/template-engine.ts` | Verbatim. | | ChannelRateLimiter sliding-window | `src/rate-limiter/channel-rate-limiter.ts` | Injectable `Clock`. | | DeliveryRetry + dead-letters | `src/retry/delivery-retry.ts` | Injectable `Clock`/`TimersLike` sleeper. | | NotificationScheduler | `src/scheduler/notification-scheduler.ts` | Injectable `Clock`. | | PreferenceManager + quiet hours | `src/preferences/preference-manager.ts` | Injectable `Clock`. | | Error codes registry | `enterprise/notification-channel.md` L51-L75 + upstream `error-codes.ts` + `errors/notification-errors.ts` | Reunified onto `defineCodes("NOTIFICATION_CHANNEL", [...])`; the upstream split between `error-codes.ts` (process-wide `ErrorCodes`) and `errors/notification-errors.ts` (`NotificationError` factory) is collapsed into one stable code registry per genesis convention. | | Hooks pipeline | `src/hooks.ts` | Reimplemented as local `Hooks` — `@suaveplan/events` does not yet export `Hooks` on genesis. | ### Requirements with no source line | Requirement | Justification | |---|---| | OTel meters + `refreshNotificationMeters` | Enterprise audit §3 requires OTel; upstream has none — added per genesis convention. | | Zod boundary schemas | Enterprise audit §3 requires Zod at every public type — upstream has none — added per genesis convention. | | Injectable `FetchLike` in webhook/push | Genesis universal-tier deterministic-fetch convention — upstream uses raw `globalThis.fetch`. | | `FakeFetch` test double | Replaces `vi.mock`/runtime-monkey-patching tests (no mocks rule). | ## Verification approach Every requirement in the spec delta carries a `**Verified by:**` line naming concrete test titles. - `src/error-codes.test.ts` — registry stability, code shape, side-effect import. - `src/channels/.test.ts` — one per channel. - `src/digest/digest-channel.test.ts` — digest queueing/flushing. - `src/router/notification-router.test.ts` + `src/router/notification-router-routing.test.ts` — send/lifecycle/routing/rate-limit. - `src/rate-limiter/channel-rate-limiter.test.ts`, `src/retry/delivery-retry.test.ts`, `src/scheduler/notification-scheduler.test.ts`, `src/templates/template-engine.test.ts`, `src/preferences/preference-manager.test.ts`, `src/hooks/hooks.test.ts`, `src/types/notification.types.test.ts`. - `src/otel/metrics.test.ts` — instruments fire against real in-memory `MeterProvider`. - `src/fakes/fake-fetch.test.ts` — fake-fetch double. ## Out of scope - Real provider integrations (Twilio, SendGrid, Amazon SES, Firebase Cloud Messaging) — gated by `EmailChannel.sendFn`/`SmsChannel.sendFn`/`PushChannel.sendFn` callbacks. Provider adapters land in sibling packages (`@suaveplan/emailer`, `@suaveplan/sms-providers`) per the genesis enterprise plan. - Persistence of notification history — the in-memory `Map` is intentional; downstream consumers wire their own storage adapter through `EventBus.on("notification.sent", …)`. ### Excluded from this migration None — the migration is at full functional parity with upstream `1.2.6` plus genesis enterprise enhancements (OTel, Zod boundaries, injectable fetch). ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/notification-channel` to enumerate. This is a brand-new package on genesis — no genesis consumers yet. - `@suaveplan/api-plugin-notifications` — [x] no action needed (consumer will land in a follow-up change; no genesis code depends on this package today). ## Migration impact None — additive only.