# OpenSpec Change: enterprise-server-sms - **Change ID**: enterprise-server-sms - **Package**: `@suaveplan/sms` - **Category**: server/services - **Target path**: `server/services/sms` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/sms.md` --- ## Summary Deliver `@suaveplan/sms` on the genesis branch from scratch at enterprise quality. The package provides SMS send/receive and delivery status tracking with a pluggable provider interface. It ships a `SmsService` facade, a `SmsProvider` interface, an `InMemoryProvider` for testing, and webhook handler utilities for inbound SMS and delivery status callbacks. Genesis ships with full Zod schemas, `@suaveplan/error` codes, `@suaveplan/retry` on provider calls, `@suaveplan/suppression` integration (skip suppressed numbers), OTel spans, and idempotency via `@suaveplan/idempotency`. --- ## Current-state audit (genesis) Does not exist on genesis — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | Typed `SMS.*` error codes | | `@suaveplan/types` (`workspace:^`) | `LoggerLike` | | `@suaveplan/retry` (`workspace:^`) | Retry provider calls | | `@suaveplan/suppression` (`workspace:^`) | Skip suppressed phone numbers | | `@suaveplan/idempotency` (`workspace:^`) | Dedup retried sends | | `@suaveplan/templates` (`workspace:^`) | SMS body templating | | `@suaveplan/logger` (`workspace:^`) | Structured logs | | `zod` (`catalog:forms`) | Options + message schemas | | `@opentelemetry/api` (`catalog:observability`) | Spans + counters | | `tslib` (`catalog:build`) | Runtime helpers | --- ## Public API surface ```ts class SmsService { constructor(options: SmsServiceOptions) send(message: SmsMessage): Promise sendBatch(messages: SmsMessage[]): Promise getStatus(messageId: string): Promise handleInbound(payload: unknown): InboundSms handleStatusCallback(payload: unknown): SmsDeliveryStatus } function createSmsService(options: SmsServiceOptions): SmsService interface SmsProvider { name: string send(message: SmsMessage): Promise getStatus(messageId: string): Promise } interface SmsMessage { to: string // E.164 format from?: string // sender ID or number body: string idempotencyKey?: string } interface SmsSendResult { messageId: string status: 'queued' | 'sent' | 'failed' provider: string error?: string } interface SmsDeliveryStatus { messageId: string status: 'queued' | 'sent' | 'delivered' | 'failed' | 'undelivered' timestamp: number errorCode?: string } interface InboundSms { from: string to: string body: string timestamp: number messageId: string } interface SmsServiceOptions { provider: SmsProvider suppressionStore?: SuppressionStore maxRetries?: number logger?: LoggerLike } type SmsErrorCode = | 'SMS_SEND_FAILED' | 'SMS_INVALID_NUMBER' | 'SMS_SUPPRESSED' | 'SMS_PROVIDER_UNAVAILABLE' | 'SMS_INBOUND_PARSE_FAILED' function registerSmsErrorCodes(): void ``` --- ## File layout ```text server/services/sms/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/ sms.types.ts sms.types.md service/ sms-service/ sms-service.ts sms-service.md sms-service.types.ts sms-service.test.ts index.ts providers/ in-memory-provider/ in-memory-provider.ts in-memory-provider.md in-memory-provider.test.ts index.ts tests/ integration.test.ts ``` --- ## Impact Downstream: auth OTP, notification service, order confirmations. **Prerequisites on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/retry`, `@suaveplan/suppression`, `@suaveplan/idempotency`, `@suaveplan/templates`, `@suaveplan/logger`.