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
NotificationErrorextendsSuaveplanErrorwith seven stable codes.- Zod schemas for
Notification,NotificationRecipient,ChannelPreferences,NotificationResult,NotificationServiceOptions. - Preference-aware routing — before dispatch, checks
recipient.preferences[channel];false→skippedstatus. - Suppression integration — email and SMS channels call
@suaveplan/suppressionbefore dispatch; suppressed →suppressedstatus. - Fallback chain — if primary channel fails, try next channel in
fallbackChainuntil one succeeds or all fail. - OTel instrumentation — counter
notification.send_totalwith attributesnotification.channel,notification.status; histogramnotification.send_duration_ms. - 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.