2.5 KiB
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. canSendrejects recipients that do not start with+(E.164 prefix); avoids hitting upstream providers with formats they will reject.- Propagates
metadatato the provider message for correlation IDs and segmentation. - Injectable
Clockfor deterministictimestampfields in tests. - Returns
DeliveryResulteven on provider failure — never throws to the router.
Usage: Basic
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
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 tosendFn.canSend(n): boolean,send(n): Promise<DeliveryResult>—DeliveryChannelimpl.
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.