# `sms-channel` — SMS delivery channel ## Purpose `sms-channel.ts` ships the `SmsChannel` `DeliveryChannel` implementation. Like the email channel, it is provider-neutral: a caller-supplied `sendFn(message)` performs the actual API call into Twilio, MessageBird, Vonage, Sinch, or any in-house SMSC. The channel keeps the responsibility surface tiny — recipient validation (E.164 `+` prefix), payload shaping, and error swallowing — so the router can stay decoupled from any specific carrier. ## Features - Provider-neutral via injectable `sendFn`. - `canSend` rejects recipients that do not start with `+` (E.164 prefix); avoids hitting upstream providers with formats they will reject. - Propagates `metadata` to the provider message for correlation IDs and segmentation. - Injectable `Clock` for deterministic `timestamp` fields in tests. - Returns `DeliveryResult` even on provider failure — never throws to the router. ## Usage: Basic ```ts import { SmsChannel } from "@suaveplan/notification-channel"; const channel = new SmsChannel({ from: "+15559999999", sendFn: async (msg) => ({ messageId: await twilio.messages.create({ from: "+15559999999", to: msg.to, body: msg.body, }).sid, }), }); ``` ## Usage: Advanced ```ts import { SmsChannel } from "@suaveplan/notification-channel"; const channel = new SmsChannel({ from: "+15559999999", sendFn: async (msg) => { const { sid } = await provider.send({ from: "+15559999999", to: msg.to, body: msg.body, ...(msg.metadata?.["statusCallback"] !== undefined ? { statusCallback: msg.metadata["statusCallback"] } : {}), }); return { messageId: sid }; }, clock: () => testNow, }); ``` ## API Reference - `SmsChannel(config: SmsChannelConfig)` — constructor. - `SmsChannelConfig` — `{ from, sendFn, clock? }`. - `SmsMessage` — `{ to, body, metadata? }` passed to `sendFn`. - `canSend(n): boolean`, `send(n): Promise` — `DeliveryChannel` impl. ## Implementation Notes The `+` prefix check is intentionally conservative — it does not validate the full E.164 country/area format because providers do that anyway and over-validating client-side breaks edge cases like short-code dispatch. Tests cover both `Error` and non-`Error` throws inside `sendFn` to lock in the `String(err)` coercion. The injected `clock` only affects the `DeliveryResult.timestamp` field; provider-recorded send time remains authoritative.