3.9 KiB
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
class SmsService {
constructor(options: SmsServiceOptions)
send(message: SmsMessage): Promise<SmsSendResult>
sendBatch(messages: SmsMessage[]): Promise<SmsSendResult[]>
getStatus(messageId: string): Promise<SmsDeliveryStatus | undefined>
handleInbound(payload: unknown): InboundSms
handleStatusCallback(payload: unknown): SmsDeliveryStatus
}
function createSmsService(options: SmsServiceOptions): SmsService
interface SmsProvider {
name: string
send(message: SmsMessage): Promise<SmsSendResult>
getStatus(messageId: string): Promise<SmsDeliveryStatus | undefined>
}
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
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.