Files
suaveplan/openspec/changes/enterprise-notification-channel/proposal.md
2026-05-27 23:29:53 +01:00

6.2 KiB

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

type ChannelType = 'email' | 'sms' | 'push' | 'in-app' | 'slack' | 'discord' | 'telegram'

interface Notification {
  id: string
  recipient: NotificationRecipient
  templateName: string
  channels: ChannelType[]
  context: Record<string, unknown>
  priority: 'low' | 'normal' | 'high' | 'critical'
  metadata: Record<string, unknown>
}

interface NotificationRecipient {
  id: string
  email?: string
  phone?: string
  pushToken?: string
  slackUserId?: string
  discordUserId?: string
  telegramChatId?: string | number
  preferences?: ChannelPreferences
}

type ChannelPreferences = Partial<Record<ChannelType, boolean>>

interface NotificationResult {
  notificationId: string
  channel: ChannelType
  status: 'sent' | 'failed' | 'suppressed' | 'skipped'
  error?: string
}

NotificationChannel interface

interface NotificationChannel {
  readonly type: ChannelType
  send(recipient: NotificationRecipient, subject: string, body: string, options?: ChannelSendOptions): Promise<void>
  isAvailableFor(recipient: NotificationRecipient): boolean
}

NotificationService

class NotificationService {
  constructor(options: NotificationServiceOptions)
  register(channel: NotificationChannel): void
  send(notification: Notification, options?: SendOptions): Promise<NotificationResult[]>
  sendBatch(notifications: Notification[], options?: BatchOptions): AsyncGenerator<NotificationResult[]>
}

interface NotificationServiceOptions {
  channels?: NotificationChannel[]
  fallbackChain?: ChannelType[]    // try channels in order until one succeeds
  logger?: LoggerLike
  bus?: EventBusLike
}

Error codes

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

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.