The rule everyone knows, the mechanism few do
Most developers learn the rule from our character-limit guide: 160 characters in GSM-7, 70 in Unicode, and one stray special character flips the whole message to the lower limit. SMS Unicode encoding comes down to how many bits each character costs. Understanding the bit-level mechanism explains every otherwise-baffling behavior: why the limit is exactly 160, why it drops to exactly 70, why some 'GSM' characters cost double, and why your options for non-Latin text are what they are.
This is the reference for when 'just keep it under 160' isn't enough: when you're building the encoding logic, supporting many languages, or debugging why a message costs more segments than its character count suggests. For A2P (application-to-person) messaging, understanding encoding is critical because carrier filtering and concatenation handling depend on correct segment counting. When submitting via SMPP, the data_coding field must be set correctly (0x00 for GSM-7, 0x08 for UCS-2) to avoid encoding mismatches.
The bit math behind the limits
An SMS payload is a fixed size: 140 bytes, or 1,120 bits. That single constant, divided by the bits-per-character of each encoding, produces every limit you've memorized. This is defined in the 3GPP TS 23.038 specification, which standardizes the GSM 7-bit default alphabet and UCS-2 encoding for SMS. When concatenation is used (via UDH), the payload shrinks to 153 bytes per segment, reducing the GSM-7 limit to 153 characters.
| Encoding | Bits per character | Chars in 1,120 bits | Covers |
|---|---|---|---|
| GSM-7 | 7 bits | 160 | Basic Latin, digits, common punctuation |
| UCS-2 (Unicode) | 16 bits | 70 | Any Unicode character: all scripts, emoji |
| GSM-7 extension char | 14 bits (2 slots) | counts as 2 | { } [ ] ( ) ~ | \ ^ € |
There it is: 1,120 ÷ 7 = 160, and 1,120 ÷ 16 = 70. The limits aren't arbitrary conventions; they're arithmetic on a fixed payload. UCS-2 costs 16 bits per character because it encodes the full Unicode space, so it fits less th
Why one character flips the whole message
The behavior that surprises everyone: a single non-GSM-7 character switches the *entire* message to UCS-2, not just that character. This follows directly from the encoding model. A message uses one encoding for all its characters: it can't mix 7-bit and 16-bit characters in the same payload. So the moment any character falls outside the GSM-7 set, the whole message must use UCS-2, and every character in it now costs 16 bits, dropping the limit to 70.
- Curly quotes and smart punctuation (’ “ ” – —) are outside GSM-7: the classic accidental trigger from copy-pasted text.
- Accented Latin characters (é, ñ, ü) beyond the small set GSM-7 includes force UCS-2.
- Emoji are always UCS-2: a single emoji halves your character budget and can multiply your segment count.
- Non-Latin scripts (Arabic, Chinese, Cyrillic, Hindi, Thai) are entirely outside GSM-7, so those languages are always UCS-2: 70 characters is simply the working limit there.
The transliteration trade-off
When cost matters and content allows, you can *transliterate* — replace non-GSM characters with GSM-7 approximations (é→e, curly quote→straight quote, — →-) to keep a message in the cheaper 160-character encoding. It's a real lever, and a real trade-off.
- Sanitize incidental non-GSM charactersCurly quotes, en/em-dashes, and stray Unicode punctuation from word processors add no meaning — transliterate them to GSM equivalents and keep the message in GSM-7. Pure savings, no loss.
- Don't mangle meaningful charactersTransliterating a person's name (José→Jose) or an accented word can change or degrade meaning; for names and languages where accents matter, accept UCS-2 rather than corrupt the content.
- Never transliterate non-Latin scriptsYou can't render Arabic or Chinese in GSM-7 meaningfully — those markets are UCS-2, and forcing Latin would make the message unreadable. Plan around the 70-character limit for them.
- Detect encoding before sendingScan the final body for non-GSM characters and compute the real segment count and cost — the same check your batch and cost logic needs so a stray character doesn't silently multiply spend. Use a REST API to validate encoding before submission, and ensure phone numbers are in international format (E.164) to avoid routing errors.
The judgment call: transliterate incidental punctuation to save cost, but never transliterate meaning. A straight quote for a curly quote is free; stripping the accent from someone's name to save a segment is a bad trade that users notice.
Applying it
SMSRoute is a no-KYC SMS API with crypto billing (BTC, ETH, USDT, XMR, LTC, and SOL) that bills by segment like every SMS API: so the encoding math is your cost math. The practical takeaways from the bytes: 160 and 70 are 1,120 bits divided by 7 and 16; one non-GSM character flips the whole message because a payload uses one encoding; extension characters cost double; and non-Latin markets are always UCS-2. Sanitize incidental Unicode to stay in GSM-7 where meaning allows, accept UCS-2 where it doesn't, and always compute the real segment count before sending. Understand the encoding at this level and you stop being surprised by your segment bills: you write copy that costs what you expect. SMSRoute's published route pages list delivery from $0.004/message (premium direct-carrier corridors up to $0.035) with sub-100ms median submission and ~98.6% delivered success (smsroute.cc route pages, 2026). For high-throughput A2P traffic, consider using long codes or short codes depending on your use case and regulatory requirements (e.g., 10DLC in the US). When sending OTPs or 2FA codes, ensure your sender ID is registered and compliant with local regulations like TRAI's DLT guidelines in India.
Related reading
FAQ
Why is the SMS limit 160 characters for GSM-7 but 70 for Unicode?
Why does one special character change my whole SMS encoding?
What is transliteration in SMS, and should I use it?
Are non-Latin language SMS always Unicode?
Send your first SMS in 5 minutes
No KYC. Pay with BTC, ETH, USDT, XMR, LTC, and SOL. Live routes to 149 countries.
Get an API key →