Important defaults
What SMS SDK does when you pass no options: fallback, retries, timeouts, idempotency, webhooks, and the option that changes each.
SMS SDK defaults to the safe option, even when that means an error instead of an automatic retry. Read this list before you go to production.
Sending
send()resolves only on acceptance. Every rejection and unclear outcome throws. There is no{ ok: false }result. See Send your first SMS.- Fallback is off. With several adapters, only the first one is used. Set
fallback: "on-known-rejection"to try the next adapter when a provider definitely did not accept the message. See Retries and fallback. - Only rate limits are retried. Each adapter gets at most 2 requests per send (
retry.maxAttempts: 2), and the second follows only a documented rate-limit rejection. Backoff starts at 500 ms (retry.baseDelayMs) and caps at 10 s (retry.maxDelayMs). ARetry-Afterlonger thanmaxDelayMsstops retrying. A 429 without the provider’s documented rate-limit error counts as an unknown outcome. - Unknown outcomes are never retried or failed over. A timeout, network error, 5xx, or malformed success response throws
HandoffUnknownErrorwithretrySafe: false. No option changes this. - Each request times out after 10 seconds (
timeoutMs: 10000). A timeout after the request starts is an unknown outcome. - Idempotency keys deduplicate only with a store. Without
idempotency.store, anidempotencyKeyonly joins concurrent same-key calls in one process. See Idempotency. - Stored idempotency records last 24 hours (
idempotency.ttlSec: 86400). Unknown outcomes are kept until you delete them. A reservation older than 5 minutes (idempotency.staleReservationSec: 300) is treated as an unknown outcome, never as permission to resend. - Unsupported fields throw before any request. If the primary adapter cannot handle
mediaUrls,sendAt,validityPeriodSec,webhookUrl, or your sender type,send()throwsUnsupportedFieldError. Nothing is dropped silently. - The sender comes from the adapter. A message without
fromuses the adapter’s configuredfrom. With neither,send()throwsInvalidSenderError. - Segment counts are estimates.
result.segmentsandvalidate()apply GSM-7 and UCS-2 rules. The provider decides what it bills. See Segments and billing. - Hooks are redacted and cannot break a send. Phone numbers in hook events are masked, bodies are never included, and a hook that throws is ignored. See Policy checks and hooks.
Receiving
- Webhooks are verified before they are read.
parseSmsWebhook()throwsWebhookSignatureErrorfor an unsigned or altered request.unsafeSkipVerification: trueturns this off for tests only. - Forwarded headers are ignored. Twilio and Plivo sign the public URL. Behind a proxy, pass
publicUrl, or settrustProxy: trueonly if a proxy you control setsX-Forwarded-ProtoandX-Forwarded-Host. See Webhook security. - Signed timestamps must be within 5 minutes for Telnyx and Vonage (
toleranceSec: 300). Twilio and Plivo sign no timestamp, so they have no replay window; deduplicate withdedupeKey. - STOP and HELP keywords are detected. An inbound message whose whole text is
STOP,UNSUBSCRIBE,HELP, or a similar keyword becomes arecipient.opted_outorrecipient.helpevent. SetdetectKeywords: falseto getmessage.receivedinstead. SMS SDK never replies itself. See STOP, HELP, and opt-outs. - New provider statuses do not throw. A verified event that SMS SDK does not map becomes
{ type: "unrecognized" }, with the provider payload inraw.
Storage
memoryIdempotencyStore()protects one process. It is lost on restart and not shared across instances or serverless invocations. See Deploy with multiple instances.