Telnyx
Send and receive SMS and MMS through the Telnyx Messaging API v2 with the SMS SDK telnyx() adapter: setup, senders, webhooks, errors, and limits.
The telnyx() adapter sends SMS and MMS through the Telnyx Messaging API v2 and verifies Telnyx’s Ed25519-signed messaging webhooks. To compare providers, see Choose a provider.
Support status
Supported. Requests, responses, the error catalog, and webhook signing follow Telnyx’s official documentation and its official Node SDK, and the adapter passes the shared contract tests. Limits to know:
- No validity period. Telnyx’s send request has no expiry field, so
validityPeriodSecthrowsUnsupportedFieldError. - Alphanumeric senders need
messagingProfileIdon the adapter. - Webhooks are verified with a public key, not the API key. The URL is not signed.
- Webhook timestamps must be within 300 seconds of now by default.
Setup
npm install @opencoredev/sms-sdkpnpm add @opencoredev/sms-sdkyarn add @opencoredev/sms-sdkbun add @opencoredev/sms-sdknub add @opencoredev/sms-sdkaube add @opencoredev/sms-sdk| Variable | Required | Used for |
|---|---|---|
TELNYX_API_KEY |
Yes | API v2 key, sent as a Bearer token. Create it in Mission Control under API Keys. |
TELNYX_FROM |
Unless you send from a profile | Default sender: a Telnyx number, short code, or alphanumeric sender ID. |
TELNYX_MESSAGING_PROFILE_ID |
For alphanumeric senders | Messaging profile ID sent with every message. |
TELNYX_PUBLIC_KEY |
For webhooks | Base64 Ed25519 public key from Mission Control, Keys & Credentials, Public Key. |
The adapter reads no environment variables itself; the examples and the doctor CLI use these names. Keep the API key on the server.
import { createSmsClient } from "@opencoredev/sms-sdk";
import { telnyx } from "@opencoredev/sms-sdk/telnyx";
const sms = createSmsClient({
adapters: [
telnyx({
apiKey: process.env.TELNYX_API_KEY ?? "",
from: "+15550100001",
// Needed only for alphanumeric senders:
// messagingProfileId: process.env.TELNYX_MESSAGING_PROFILE_ID,
}),
],
});
const result = await sms.send({ to: "+14155550123", body: "Your order has shipped." });
console.log(result.providerId); // the Telnyx message ID (data.id)
telnyx() throws ConfigurationError at startup when apiKey is empty. Two more options exist for tests and proxies: baseUrl (default https://api.telnyx.com) and fetch. To test without a Telnyx account, see Test without a provider.
The adapter calls POST /v2/messages with a JSON body. Delivery comes from data.to[0].status, usually queued. A 2xx without data.id is an unknown outcome.
Senders
| Sender | Write | Sent as |
|---|---|---|
| Long code or toll-free number | "+15550100001" |
from |
| Short code | { shortCode: "12345" } |
from |
| Alphanumeric sender ID | { senderId: "Acme" } |
from, plus messaging_profile_id from the adapter |
| Messaging profile number pool | { messagingService: "<profile id>" } |
messaging_profile_id, with no from |
An alphanumeric sender without messagingProfileId on the adapter fails local validation before any request. In Telnyx, assign each number to a messaging profile before it sends.
US A2P traffic to mobile numbers needs 10DLC registration, toll-free numbers need verification, and a short code works in one country only. Alphanumeric senders can’t reach the US, Canada, or Puerto Rico, and recipients can’t reply to them. Some countries, such as the UK and France, require sender ID registration in advance. Telnyx’s sender type guide covers the rules. SMS SDK does not check them before sending; see Registration and consent.
Features
| Feature | Telnyx |
|---|---|
| MMS | Yes, sent with type: "MMS" |
Scheduling (sendAt) |
Yes, from any sender |
| Validity period | No |
Per-message webhookUrl |
Yes, sent as webhook_url |
Provider options
Pass other POST /v2/messages fields in providerOptions.telnyx:
import { sms } from "./sms";
await sms.send({
to: "+14155550123",
body: "Your order has shipped.",
webhookUrl: "https://example.com/webhooks/sms/telnyx",
providerOptions: {
telnyx: { encoding: "gsm7", webhookFailoverUrl: "https://backup.example.com/webhooks/sms/telnyx" },
},
});
| Option | Telnyx field | Type |
|---|---|---|
subject |
subject |
string, the subject of an MMS |
webhookFailoverUrl |
webhook_failover_url |
http or https URL |
useProfileWebhooks |
use_profile_webhooks |
boolean, Telnyx default true |
autoDetect |
auto_detect |
boolean, Telnyx default false |
encoding |
encoding |
"auto" | "gsm7" | "ucs2". With "gsm7", Telnyx answers 400 if a character has no GSM-7 form. |
extra |
any other field | Record<string, string | number | boolean>, sent as is |
Unknown keys and wrong types throw before any request, and the options go out only when the telnyx adapter sends. extra cannot set the fields SMS SDK owns: to, from, messaging_profile_id, text, media_urls, type, webhook_url, and send_at. See providerOptions for how options interact with fallback and idempotency.
Webhooks
import { parseSmsWebhook, WebhookSignatureError } from "@opencoredev/sms-sdk/webhooks";
import { db } from "./db";
export async function POST(request: Request): Promise<Response> {
try {
const event = await parseSmsWebhook({
provider: "telnyx",
request,
credentials: { publicKey: process.env.TELNYX_PUBLIC_KEY ?? "" },
});
if (event.type === "message.delivered" || event.type === "message.undelivered") {
await db.messages.updateStatus(event.providerId, event.type, event.errorCode);
} else if (event.type === "recipient.opted_out") {
await db.suppressions.add(event.from);
}
return new Response(null, { status: 200 });
} catch (error) {
if (error instanceof WebhookSignatureError) return new Response("Invalid signature", { status: 401 });
throw error;
}
}
- Credential: your account’s Ed25519 public key. A key that is not 32 bytes of base64 fails with
invalid_credentials. - Point Telnyx at it: for status, pass
webhookUrlper message or set the webhook URL on the messaging profile. For inbound, set the inbound webhook URL on the profile the number belongs to. - Signing:
telnyx-signature-ed25519covers`${telnyx-timestamp}|${rawBody}`. The URL is not signed, so proxies don’t affect verification and there is nopublicUrloption. - Replay window: 300 seconds either side of now by default. Older or future timestamps fail with
stale_timestamp. Change it withtoleranceSec.
SMS SDK reads message.received, message.sent, and message.finalized events. Status comes from data.payload.to[0].status:
| Telnyx status | Event |
|---|---|
queued, sending |
message.queued |
sent, delivery_unconfirmed |
message.sent |
delivered |
message.delivered |
sending_failed, delivery_failed, expired |
message.undelivered, or message.filtered with error 40002, 40003, or 40322 |
Other events and statuses, such as read |
unrecognized |
message.received |
message.received, or a recipient.* event from STOP and HELP keywords (source: "keyword") |
Telnyx sends no opt-out flag, so opt-outs come from keyword detection. dedupeKey is telnyx:{data.id}, the webhook event ID. providerId is the message ID, data.payload.id, which matches result.providerId.
Limitations
- No
validityPeriodSec. For an expiry, use Twilio, Plivo, Vonage, or Sinch. - Error 40008 is not mapped to
message.filtered, because Telnyx’s pages disagree on its meaning. It staysmessage.undelivered.
Error codes
The code comes from errors[0].code in the response.
| Telnyx response | Category | Falls back |
|---|---|---|
| 10009, 10010, 20001, 20002, 20003, 20006, 20008; 401 or 403 without a listed code | auth |
Yes |
| 10011, 40318 (queue full) | rate_limited |
Yes, after retries |
| 40305, 40306, 40308, 40315, 40320, 40321, 40329, 40330 | sender |
Yes |
| 20013, 20100, 40309, 40312, 40314, 40331, 40333 (spend limit) | account |
Yes |
| 40301, 40310, 40319 | recipient |
No |
| 40300 (blocked due to STOP), 40322 (blocked content) | compliance |
Never |
| Any other 4xx | request |
No |
| 429 without code 10011 or 40318 | unknown | Never |
5xx, network error, timeout, 2xx without data.id |
unknown | Never |
Telnyx does not document a Retry-After header, but SMS SDK honors one on a rate limit. error.provider.requestId holds the x-request-id header. For adapter authors, the subpath also exports buildTelnyxRequest, interpretTelnyxResponse, telnyxRejectionCategory, and telnyxDelivery.
Official documentation
Checked on 2026-10-08. The full evidence list is in PROVIDERS.md.
- Send a message
- Receiving messaging webhooks
- Webhook delivery and signing
- API errors
- Alphanumeric sender ID
- International SMS compliance
- Official Node SDK webhook verification