Validation and encoding
Check a message locally with sms.validate() before you pay for a send: E.164, sender, capabilities, encoding, and segments.
sms.validate() tells you whether a message would be refused and how many segments it uses, before you send. It is synchronous, free, and never contacts a provider.
Validate a message
import { sms } from "./sms";
const check = sms.validate({
to: "+14155550123",
body: "Your package is ready 📦",
});
console.log(check.supported); // true
console.log(check.encoding, check.segments); // "ucs2" 1
console.log(check.nonGsmCharacters); // ["📦"]
console.log(check.issues); // []
supported is true when the primary adapter can send the message and nothing is wrong with it. The emoji forces UCS-2, as encoding and nonGsmCharacters show.
Read the issues
Each problem is a ValidationIssue with a stable code, the field it concerns, a readable message, and provider when it applies to one adapter.
import { sms } from "./sms";
const check = sms.validate({
to: "+14155550123",
body: "Your appointment is tomorrow.",
sendAt: new Date("2026-12-01T09:00:00Z"),
});
for (const issue of check.issues) {
console.log(`${issue.provider ?? "message"}: ${issue.field} ${issue.code}: ${issue.message}`);
}
With an adapter that cannot schedule, this prints one unsupported_field issue for sendAt, and supported is false.
code |
Meaning |
|---|---|
invalid_recipient |
to is not E.164. |
invalid_sender |
from (or the adapter default) is malformed, such as a 12-character sender ID. |
missing_sender |
No from on the message and no default on the adapter. |
empty_body |
The body is empty and there is no media. |
unsupported_field |
The adapter cannot send this field or sender type. |
invalid_field |
A field is malformed or outside the adapter’s range: a relative URL, an invalid Date, a validityPeriodSec outside the provider’s range, a Twilio body over 1600 characters, more than 10 Twilio media URLs, or an idempotencyKey outside 1 to 255 characters. |
Check every adapter
With several adapters, validate() checks each one. adapterCandidates lists the adapters that could send the message, in order. issues collects the problems from the rest.
import { createSmsClient } from "@opencoredev/sms-sdk";
import { telnyx } from "@opencoredev/sms-sdk/telnyx";
import { vonage } from "@opencoredev/sms-sdk/vonage";
const sms = createSmsClient({
adapters: [
vonage({ apiKey: process.env.VONAGE_API_KEY ?? "", apiSecret: process.env.VONAGE_API_SECRET ?? "", from: "+15550100001" }),
telnyx({ apiKey: process.env.TELNYX_API_KEY ?? "", from: "+15550100002" }),
],
fallback: "on-known-rejection",
});
const check = sms.validate({
to: "+14155550123",
body: "See the attached receipt.",
mediaUrls: ["https://example.com/receipt.png"],
});
console.log(check.supported); // false: Vonage cannot send MMS
console.log(check.adapterCandidates); // ["telnyx"]
supported reflects only the primary adapter, because send() throws UnsupportedFieldError when the primary cannot send the message, even with fallback on.
What validate() cannot know
Validation checks format and declared capabilities. It cannot see whether your sender is provisioned or registered for 10DLC or toll-free, whether the destination country accepts your sender type, whether the recipient opted out with the provider, or your balance, trial status, and geographic permissions. The provider reports those as rejections when you send.
Encoding
Every result includes the estimateSegments() fields:
| Field | Meaning |
|---|---|
encoding |
"gsm7" when every character is in the GSM-7 alphabet or its extension table, otherwise "ucs2". |
units |
GSM-7 septets (extension characters such as €, [, { count 2) or UTF-16 code units. |
segments |
Estimated segments. 0 for an empty body. |
unitsPerSegment |
160 or 153 for GSM-7, 70 or 67 for UCS-2. |
remainingInSegment |
Units left before another segment starts. |
containsUnicode |
true when at least one character forces UCS-2. |
nonGsmCharacters |
The distinct characters that forced UCS-2, in order. |
The validate() reference has the full result type.
SMS SDK never rewrites your text. One curly quote or emoji puts the whole message in UCS-2. For GSM-7, replace those characters yourself. Segments and billing covers the counting rules.