SMS SDK is an open-source TypeScript library for sending and receiving transactional SMS through Twilio, Telnyx, Plivo, Vonage, Bandwidth, or Sinch with one typed API. Switch providers by changing one import, add a second provider as a fallback, and verify every provider's webhooks the same way.
It never texts anyone twice: it falls back only when a provider proves it refused the message, and stops when the outcome is unknown.
Start with the quick start · Read the docs · Install from npm
- Adapters for Twilio, Telnyx, Plivo, Vonage, Bandwidth, and Sinch, each behind its own subpath import
- Opt-in fallback to the next provider after a known rejection, with rate-limit retries that honor
Retry-After - Typed errors that say whether sending again is safe, and idempotency keys that stop duplicate sends
- Verified, normalized webhooks for delivery status, replies, and STOP/HELP opt-outs
- A
memory()adapter and signed webhook builders for tests that never touch the network - Zero runtime dependencies, ESM, Node.js 20+ and Bun
SMS SDK runs in server-side code. Keep provider credentials and webhook secrets out of browser bundles.
npm install @opencoredev/sms-sdk
# or
bun add @opencoredev/sms-sdkCreate a client with one or more adapters and call send():
import { createSmsClient } from "@opencoredev/sms-sdk";
import { twilio } from "@opencoredev/sms-sdk/twilio";
import { telnyx } from "@opencoredev/sms-sdk/telnyx";
const sms = createSmsClient({
adapters: [
twilio({ accountSid: process.env.TWILIO_ACCOUNT_SID!, authToken: process.env.TWILIO_AUTH_TOKEN!, from: "+15550100001" }),
telnyx({ apiKey: process.env.TELNYX_API_KEY!, from: "+15550100002" }),
],
fallback: "on-known-rejection",
});
const result = await sms.send({ to: "+14155550123", body: "Your order has shipped." });
console.log(result.provider);
// "telnyx" when Twilio refused the sendersend() resolves only when a provider accepted the message. A refusal throws ProviderRejectedError with retrySafe: true. A timeout or unreadable response throws HandoffUnknownError with retrySafe: false, and the SDK neither retries nor falls back, because the text may already be on its way. How sending works covers every outcome and default.
No provider account yet? Swap in memory() from @opencoredev/sms-sdk/testing. It records every send and calls nothing.
| Provider | Status | MMS | Scheduling | Webhook verification |
|---|---|---|---|---|
| Twilio | supported | yes | yes (Messaging Service) | HMAC-SHA1 |
| Telnyx | supported | yes | yes | Ed25519 |
| Plivo | partial | yes | no | HMAC-SHA256 V2 |
| Vonage | partial | no | no | HS256 JWT |
| Bandwidth | partial | no | no | Basic credentials |
| Sinch | partial | no | yes | Basic or HMAC |
Partial means part of the provider's behavior is undocumented, so fewer rejections can be recognized for fallback. PROVIDERS.md lists every provider fact the SDK relies on, its source, and what could not be confirmed. Choose a provider has the full capability matrix.
One parser verifies the request before reading it and returns the same event shape for every provider:
import { parseSmsWebhook } from "@opencoredev/sms-sdk/webhooks";
const event = await parseSmsWebhook({
provider: "telnyx",
request,
credentials: { publicKey: process.env.TELNYX_PUBLIC_KEY! },
});
// event.type: "message.delivered", "message.received", "recipient.opted_out", ...See Receive webhooks for Next.js, Hono, and Bun handlers.
Full documentation lives at www.sms-sdk.com. Coding agents can read it as Markdown, through llms.txt, or with the MCP server:
claude mcp add --transport http sms-sdk https://www.sms-sdk.com/mcpGood places to start:
bun install
bun run test # SDK tests
bun run typecheck # SDK types
bun run dev # docs siteEvery change to the package ships with a changeset (bun run changeset). See Contributing and support for the release flow, adding a provider, and reporting security issues.
Want your logo here? Become a sponsor →
MIT License · Built by @leodev
