---
title: "validate() and encoding"
description: "sms.validate(input), the segment and encoding helpers (estimateSegments, isGsm7, SEGMENT_LIMITS), and the phone number and redaction utilities."
---

## `sms.validate(input)`

Checks a message locally and estimates its segments. It is synchronous and free, and it never contacts a provider or claims that an account or sender is approved.

```ts
import { sms } from "./sms";

const check = sms.validate({ to: "+14155550123", body: "Your package is ready 📦" });

if (!check.supported) {
  for (const issue of check.issues) console.warn(issue.code, issue.field, issue.message);
}
console.log(check.encoding, check.segments, check.adapterCandidates);
```

Guide: [Message length and cost](/sending/length-and-cost).

### Parameters

- `input: SmsSendInput`, the same input `send()` takes. See [send()](/reference/send#smssendinput).

### Returns: `SmsValidationResult`

`SegmentPreview` plus four fields:

| Field | Type | Meaning |
| --- | --- | --- |
| `supported` | `boolean` | `true` when the primary adapter can send the message and the message has no issues. |
| `issues` | `readonly ValidationIssue[]` | Every problem found, across the message and every adapter. |
| `adapterCandidates` | `readonly string[]` | Adapters that could send it, in configured order. Empty when the message itself is invalid. |
| `providerOptionsFor` | `readonly string[]` | The candidates in `adapterCandidates` that have an entry in `providerOptions`, in configured order. Each of these sends its own entry if it ends up sending. Empty when the message itself is invalid or no candidate has an entry. |
| `encoding` | `"gsm7" \| "ucs2"` | |
| `segments` | `number` | Estimated segments, `0` for an empty body. |
| `units` | `number` | Septets (GSM-7) or UTF-16 code units (UCS-2). |
| `unitsPerSegment` | `number` | 160 or 153 (GSM-7), 70 or 67 (UCS-2). |
| `remainingInSegment` | `number` | Units left before another segment starts. |
| `containsUnicode` | `boolean` | At least one character forces UCS-2. |
| `nonGsmCharacters` | `readonly string[]` | Distinct characters that forced UCS-2. |

`supported` matches what `send()` does before any request. When the primary adapter makes `supported` false, `send()` throws even with fallback on.

Message-level issues come first: recipient, body, field formats, and `providerOptions` checked against every configured adapter. When there are any, adapters are not checked, and `adapterCandidates` and `providerOptionsFor` are empty.

## `ValidationIssue`

```ts ignore="type listing"
type ValidationIssue = {
  readonly code: ValidationIssueCode;
  readonly field: ValidationField;
  readonly message: string;
  readonly provider?: string; // set when the issue applies to one adapter
};
```

| `code` | `send()` throws | Meaning |
| --- | --- | --- |
| `invalid_recipient` | `InvalidRecipientError` | `to` is not E.164. |
| `invalid_sender` | `InvalidSenderError` | The sender is malformed, such as a sender ID over 11 characters, a short code that is not 3 to 8 digits, a Twilio Messaging Service SID that is not `MG` plus 32 hex, or a Telnyx alphanumeric sender without `messagingProfileId`. |
| `missing_sender` | `InvalidSenderError` | No `from` and no adapter default. |
| `empty_body` | `InvalidMessageError` | Empty body without media, or any empty body on Vonage. |
| `unsupported_field` | `UnsupportedFieldError` | The adapter cannot send this field or sender type, Twilio `sendAt` without a Messaging Service sender, or a `providerOptions` key the adapter does not accept or that names a parameter the SDK sets. |
| `invalid_field` | `InvalidMessageError` | A malformed field, a value outside the adapter's range, or a `providerOptions` value of the wrong type. |

`ValidationField` is `"to" | "from" | "body" | "mediaUrls" | "sendAt" | "validityPeriodSec" | "webhookUrl" | "idempotencyKey" | "providerOptions"`.

## What it does not check

- Whether the sender is provisioned or registered in your account.
- Whether the destination accepts your sender type or MMS.
- Recipient opt-outs held by the provider, account balance, or permissions.

The provider reports these as rejections at send time.

## Encoding and utility helpers

Synchronous helpers with no network access. The encoding functions live in `@opencoredev/sms-sdk/encoding`; `estimateSegments` and `isGsm7` are also exported from the root.

### `estimateSegments(body)`

Estimates the encoding and segment count for a message body.

```ts
import { estimateSegments } from "@opencoredev/sms-sdk/encoding";

const preview = estimateSegments("Café opens at 9 € 5");
console.log(preview.encoding, preview.units, preview.segments); // "gsm7" 20 1
```

`é` is in the GSM-7 basic table and `€` is in the extension table, so it counts 2 septets.

#### Returns: `SegmentPreview`

| Field | Type | Meaning |
| --- | --- | --- |
| `encoding` | `"gsm7" \| "ucs2"` | GSM-7 when every character is in the basic or extension table. |
| `segments` | `number` | Estimated segments. `0` for an empty body. |
| `units` | `number` | Septets (extension characters count 2) or UTF-16 code units. |
| `unitsPerSegment` | `number` | 160 or 153 for GSM-7, 70 or 67 for UCS-2. |
| `remainingInSegment` | `number` | Units left before another segment is needed. |
| `containsUnicode` | `boolean` | At least one character forced UCS-2. |
| `nonGsmCharacters` | `readonly string[]` | Distinct characters that forced UCS-2, in order of first appearance. |

Extension characters and surrogate pairs never split across segments, and nothing is transliterated. The result is an estimate, not a price. See [Message length and cost](/sending/length-and-cost).

### `isGsm7(text)`

Returns `true` when every character is in the GSM-7 basic or extension table.

### `GSM7_BASIC_CHARACTERS` and `GSM7_EXTENSION_CHARACTERS`

Strings holding the GSM 03.38 basic alphabet, one septet each, and the extension table, two septets each. The extension table is form feed, `^`, `{`, `}`, `\`, `[`, `~`, `]`, `|`, and `€`.

### `SEGMENT_LIMITS`

```ts ignore="value listing"
{ gsm7: { single: 160, concatenated: 153 }, ucs2: { single: 70, concatenated: 67 } }
```

Exported from `/encoding` only.

### `isE164(value)`

```ts
import { isE164 } from "@opencoredev/sms-sdk";

console.log(isE164("+14155550123"), isE164("+1 415 555 0123"), isE164("0044207946000")); // true false false
```

A type guard (`value is E164`) for `^\+[1-9]\d{1,14}$`. It checks format only, not whether the number exists or receives SMS. It accepts `unknown`, so you can pass `process.env` values directly.

### `maskPhoneNumber(value)`

Masks a number for logs, keeping the `+`, the first digit, and the last two digits: `+14155550123` becomes `+1********23`. Short strings are fully masked.

### `redactText(text)`

Makes provider text safe to log. It masks digit runs that look like phone numbers, removes control characters, and truncates to 200 characters. Use it in [custom adapters](/advanced/writing-an-adapter) for `details.message`.

```ts
import { maskPhoneNumber, redactText } from "@opencoredev/sms-sdk";

console.log(maskPhoneNumber("+14155550123")); // "+1********23"
console.log(redactText("The 'To' number +14155550123 is not valid")); // "The 'To' number +1********23 is not valid"
```

### `resolveSender(from)` and `supportsSender(capabilities, sender)`

`resolveSender` validates an `SmsFrom` and returns `{ kind: "ok", sender: SmsSender }`, `{ kind: "missing" }`, or `{ kind: "invalid", message }`. `supportsSender` returns whether an adapter's `capabilities.senderTypes` allow that sender. The client uses both. They are exported for adapter authors.

### `isSmsError(value)` and `isFallbackEligible(category)`

`isSmsError` is a type guard for any SDK error. `isFallbackEligible` returns `true` for `auth`, `rate_limited`, `sender`, and `account`. See [Errors](/reference/errors).

## Types

`SmsValidationResult`, `ValidationIssue`, `ValidationIssueCode`, `ValidationField`, and `SegmentPreview` are exported as types from `@opencoredev/sms-sdk`.

`E164`, `SegmentPreview`, `SmsEncoding`, and `SenderResolution` are exported as types from `@opencoredev/sms-sdk`.
