Skip to content
SMS SDK
Esc
↑↓navigate↵open⌘Jpreview
On this page

send()

sms.send(input, options): SmsSendInput and SmsSendResult fields, SendAttempt, the order of checks, and the errors it throws.

sms.send(input, options?)

Sends one logical message. It resolves only when a provider accepts the message (handoff: "accepted"). Every rejection and unknown outcome throws.

import { sms } from "./sms";

const result = await sms.send(
  {
    to: "+14155550123",
    body: "Your order has shipped.",
    idempotencyKey: "order:123:shipped:v1",
  },
  { signal: AbortSignal.timeout(30_000) },
);

console.log(result.providerId, result.delivery, result.segments);

Guide with error handling: Send your first SMS.

Parameters

  • input: SmsSendInput, the message. Fields below.
  • options?: { signal?: AbortSignal }. Aborting before any request, or during a rate-limit backoff, throws SendAbortedError. Aborting while a request is in flight throws HandoffUnknownError with reason: "aborted".

SmsSendInput

to

from

  • Type: SmsFrom (E164 | { senderId } | { shortCode } | { messagingService })
  • Optional
  • Defaults to the adapter’s configured from. With fallback, each adapter uses its own default when from is omitted.

body

  • Type: string
  • Required
  • Message text. May be empty only when mediaUrls is non-empty, and never for Vonage. Twilio limits it to 1600 characters.

idempotencyKey

  • Type: string, 1 to 255 characters
  • Optional
  • Names the logical message, such as order:123:shipped:v1. Deduplicates only when the client has idempotency.store. Without a store, only concurrent calls with the same key on the same client share one send. See Idempotency.

mediaUrls

  • Type: readonly string[], absolute http or https URLs
  • Optional
  • MMS media. Needs capabilities.mms.

sendAt

  • Type: Date
  • Optional
  • Provider-side scheduled send. Needs capabilities.scheduling. Twilio also needs a Messaging Service sender.

validityPeriodSec

  • Type: positive integer
  • Optional
  • How long the provider may keep trying. Needs capabilities.validityPeriod, and must be within its range.

webhookUrl

  • Type: absolute http or https URL
  • Optional
  • Per-message status callback. Needs capabilities.webhookUrlOverride.

providerOptions

  • Type: SmsProviderOptions, an object keyed by adapter name
  • Optional
  • Provider-specific parameters the portable fields do not cover, such as Twilio’s ShortenUrls or Telnyx’s auto_detect.
import { createSmsClient } from "@opencoredev/sms-sdk";
import { telnyx } from "@opencoredev/sms-sdk/telnyx";
import { twilio } from "@opencoredev/sms-sdk/twilio";

const sms = createSmsClient({
  adapters: [
    twilio({
      accountSid: process.env.TWILIO_ACCOUNT_SID ?? "",
      authToken: process.env.TWILIO_AUTH_TOKEN ?? "",
      from: { messagingService: process.env.TWILIO_MESSAGING_SERVICE_SID ?? "" },
    }),
    telnyx({ apiKey: process.env.TELNYX_API_KEY ?? "", from: "+15550100001" }),
  ],
  fallback: "on-known-rejection",
});

await sms.send({
  to: "+14155550123",
  body: "Track your order: https://example.com/orders/123",
  providerOptions: {
    twilio: { shortenUrls: true, smartEncoded: false },
    telnyx: { autoDetect: true },
  },
});

Each entry has typed options in camelCase, which the adapter sends under the provider’s own parameter names. An optional extra object holds parameters the typed options do not cover yet.

interface SmsProviderOptions {
  twilio?: TwilioSendOptions;   // added by "@opencoredev/sms-sdk/twilio"
  telnyx?: TelnyxSendOptions;   // added by "@opencoredev/sms-sdk/telnyx"
  plivo?: PlivoSendOptions;     // added by "@opencoredev/sms-sdk/plivo"
  vonage?: VonageSendOptions;   // added by "@opencoredev/sms-sdk/vonage"
}

type TwilioSendOptions = {
  shortenUrls?: boolean;
  smartEncoded?: boolean;
  // ...one optional field per typed option
  extra?: Record<string, string | number | boolean>;
};

Each provider page lists its options and wire names: Twilio, Telnyx, Plivo, and Vonage.

Rules:

  • Typed by import. Each adapter subpath adds its key to SmsProviderOptions, so you can only write keys for adapters you import. An unknown option key is a type error.
  • One adapter each. An entry applies only when that adapter sends. With fallback, each adapter sends its own entry and never another’s. Entries for adapters the client does not have are ignored. sms.validate() lists the candidates that have an entry in providerOptionsFor.
  • Checked before sending. For every configured adapter, before any request: an unknown option key throws UnsupportedFieldError, a wrong type or value throws InvalidMessageError, and an extra key that names a parameter SMS SDK sets (such as Twilio’s To or StatusCallback, in any letter case) or a typed option’s parameter throws UnsupportedFieldError. Each error has field: "providerOptions".
  • extra is not validated. Its values must be strings, finite numbers, or booleans, and are sent as they are. Use it for parameters released after this SDK version, and check the provider’s reference first.
  • Part of the idempotency key. Reusing an idempotencyKey with different options throws IdempotencyConflictError. Key order does not matter, and empty entries or entries for adapters the client does not have are ignored.
  • Values stay out of logs. Error messages name option keys, never their values. Hook events and SendAttempt carry no options. beforeSend receives the whole input, options included, as it does body.

An adapter you write can accept options too. Set providerOptions on the adapter to a ProviderOptionsSpec and add its key to SmsProviderOptions with declare module "@opencoredev/sms-sdk". See Adapter contract.

Returns: SmsSendResult

Field Type Meaning
id string sms_ plus 32 hex characters. The SDK’s ID for this logical send. Stable across idempotent replays.
provider string Name of the adapter that accepted.
providerId string The provider’s message ID: Twilio SM…/MM…, Telnyx data.id, Plivo message_uuid[0], Vonage message_uuid.
handoff "accepted" Always "accepted".
delivery Delivery Status from the response, usually "queued". Not handset delivery.
encoding "gsm7" | "ucs2" Encoding of the estimate.
segments number Estimated segments. Not a billing figure.
attemptedProviders readonly string[] Adapters tried, in order, without duplicates.
attempts readonly SendAttempt[] Every provider request, in order.
replayed boolean true when returned from the idempotency store or a concurrent in-flight call with the same key.

Delivery is "unknown" | "queued" | "sent" | "delivered" | "undelivered" | "filtered". Handoff is "accepted" | "rejected" | "unknown".

SendAttempt

Safe to log. It has no body and no phone numbers.

type SendAttempt =
  | { provider: string; attempt: number; outcome: "accepted"; providerId: string; delivery: Delivery; durationMs: number }
  | { provider: string; attempt: number; outcome: "rejected"; category: RejectionCategory; httpStatus?: number; providerCode?: string; retryAfterMs?: number; durationMs: number }
  | { provider: string; attempt: number; outcome: "unknown"; reason: UnknownReason; httpStatus?: number; durationMs: number };

attempt counts from 1 per adapter within one send.

Order of checks

  1. Local validation: recipient, body, field formats, providerOptions for every configured adapter, then the primary adapter’s sender and capabilities. Failures throw before any request.
  2. Abort check: an already-aborted signal throws SendAbortedError.
  3. Idempotency: replay, conflict, in-progress, or unknown, when a key and store are set.
  4. beforeSend.
  5. Provider requests, with rate-limit retries and, if enabled, fallback.

Errors

Error code retrySafe When
InvalidRecipientError invalid_recipient true to is not E.164
InvalidSenderError invalid_sender true No sender, or a malformed one
InvalidMessageError invalid_message true Empty body without media, bad URL, date, or number field, a provider limit, a providerOptions value of the wrong type
UnsupportedFieldError unsupported_field true The primary adapter cannot send a field or sender type, or a providerOptions key is unknown or names a parameter SMS SDK sets
PolicyRejectedError policy_rejected true beforeSend rejected
ProviderRejectedError provider_rejected true The provider proved non-acceptance
ProviderAuthError provider_auth true Category auth
ProviderRateLimitedError provider_rate_limited true Rate limited after retries
AllProvidersRejectedError all_providers_rejected true Fallback ran and every attempted adapter rejected
HandoffUnknownError handoff_unknown false Acceptance cannot be determined
SendAbortedError aborted true Aborted before any request could have been processed
IdempotencyConflictError idempotency_conflict false Key reused with a different payload or sender
IdempotencyInProgressError idempotency_in_progress true Another process holds a fresh reservation

Errors has details and the action for each. An error thrown by your beforeSend propagates unchanged.

Types

SmsSendInput, SmsSendOptions, SmsSendResult, SmsProviderOptions, ProviderOptionsSpec, ProviderOptionsOf, ProviderExtra, SmsFrom, Delivery, Handoff, SendAttempt, RejectionCategory, and UnknownReason are exported as types from @opencoredev/sms-sdk. E164 is exported from the same path.