How SMS length is counted
A message uses GSM-7 when every character is in the GSM 03.38 alphabet or its extension table. If any character is outside it, the whole message switches to UCS-2, which fits fewer characters in each segment.
| Encoding | Counts | One SMS holds | Each part of a longer message holds |
|---|---|---|---|
| GSM-7 | Septets | 160 | 153 |
| UCS-2 | UTF-16 code units | 70 | 67 |
A longer message loses a few units in each part to the header that tells the phone how to join the parts. That is why 161 GSM-7 characters cost 2 segments, and 307 cost 3.
Characters that count double
The GSM-7 extension table holds characters that cost 2 septets each, because each is sent as an escape plus the character: ^{}\[~]|€ and the form feed. Eighty euro signs fill one 160-septet SMS. The two septets never split across segments.
Why one emoji makes a message cost more
One character outside GSM-7 switches the whole message to UCS-2, so the limit drops from 160 to 70. An 89-character message that fit in one segment needs two once you add an emoji. Most emoji count 2 UTF-16 code units, and they are never split across segments.
Emoji aren't the only cause. Curly quotes (’ “ ”), long dashes, and many accented letters do the same, and they often arrive by copying text from a document. The calculator above marks each one.
Count segments in your code
The calculator runs estimateSegments() from @opencoredev/sms-sdk, a TypeScript library for sending SMS through your own Twilio,Telnyx, Plivo,Vonage, Bandwidth, orSinch account. You can run the same check on your server before you send:
import { estimateSegments } from "@opencoredev/sms-sdk/encoding";
const body = "Hi Sam, your table for 4 at Rosa's is confirmed for Friday at 7:30 PM. Reply C to cancel.";
const plain = estimateSegments(body);
console.log(plain.encoding, plain.segments, plain.units); // gsm7 1 89
const withEmoji = estimateSegments(`${body} 🍝`);
console.log(withEmoji.encoding, withEmoji.segments, withEmoji.units); // ucs2 2 92
console.log(withEmoji.nonGsmCharacters); // ["🍝"]Install it with npm install @opencoredev/sms-sdk. sms.validate() returns the same estimate along with checks for the recipient and your provider, and every send result reports encoding andsegments. See Message length and cost to block messages that grow past a limit.
The count is an estimate, not a price
The count is how a standard handset splits the message. Your provider's count can differ, for example when it replaces curly quotes before sending or the route has its own length limit. For money, use your provider's pricing page and invoices. Message length and cost covers provider limits and how to refuse messages that grow too long.