Skip to content

Encoding & pricing

SMS is billed per page (or “segment”), per recipient. How many pages your message costs depends entirely on which characters are in it.

EncodingChars per pageUsed when
GSM-7160Every character is in the GSM alphabet
UCS-270Any character isn’t

That second row is where the money goes.

Safe: A–Z, a–z, 0–9, space, and @ £ $ ¥ è é ù ì ò Ç Ø ø Å å Δ _ Φ Γ Λ Ω Π Ψ Σ Θ Ξ Æ æ ß É ! " # ¤ % & ' ( ) * + , - . / : ; < = > ? ¡ Ä Ö Ñ Ü § ¿ ä ö ñ ü à

These count as two characters each: ^ { } \ [ ] ~ | €

Everything else — emoji, “smart quotes”, en-dashes, ellipses, most non-Latin scripts — forces UCS-2.

The characters that catch people out aren’t emoji, which are at least visible. They’re the ones a word processor or a designer’s copy-deck silently inserts:

Looks likeActually isSafe?
'' curly apostrophe (U+2019)❌ UCS-2
'' straight apostrophe✅ GSM-7
"…""…" curly quotes❌ UCS-2
- en-dash (U+2013)❌ UCS-2
... ellipsis (U+2026)❌ UCS-2

Copy a message out of Word, Google Docs, or Notion and it will almost certainly contain curly quotes. Your 158-character message just became three pages, and nothing about it looks different.

Normalise before you send:

const toGSM = (s) =>
s.replace(/[‘’]/g, "'") // curly single → straight
.replace(/[“”]/g, '"') // curly double → straight
.replace(/[–—]/g, '-') // en/em dash → hyphen
.replace(//g, '...'); // ellipsis → three dots
toGSM('We'll see you at 5–6pm'); // → "We'll see you at 5-6pm..."

That one function can cut a campaign’s cost by two thirds.

Longer messages are split and reassembled on the handset — the recipient sees one message. But each page carries a header, so the usable space per page shrinks:

EncodingSingle pagePer page when concatenated
GSM-7160153
UCS-27067

So a 320-character GSM-7 message is three pages (153 + 153 + 14), not two.

MessageEncodingCharsPages
Your code is 481920.GSM-7201
160 chars, plain ASCIIGSM-71601
161 chars, plain ASCIIGSM-71612
Your code is 481920. 👋UCS-2221
71 chars with one emojiUCS-2712
150 chars with one curly quoteUCS-21503

Every accepted batch returns a cost_estimate. pages is the page count for one message — multiply by recipients for the batch:

"cost_estimate": {
"total_cost": 19.2,
"currency": "NGN",
"pages": 3,
"accepted_recipients": 2,
"sufficient": true
}

pages: 3 on a message you thought was short is your signal that something re-encoded it to UCS-2.

const batch = await sendSMS({ to, content });
if (batch.cost_estimate.pages > expectedPages) {
logger.warn(
{ reference_id: batch.reference_id, pages: batch.cost_estimate.pages },
'message segmented more than expected — check for non-GSM characters',
);
}
  • Keep OTPs under 160 GSM-7 characters. They’re your highest-volume, most cost-sensitive traffic. No emoji, ever.
  • Run marketing copy through a normaliser. Marketing copy comes from documents; documents contain curly quotes.
  • Emoji are a deliberate choice. They lift engagement — sometimes worth 3× the cost. Just make it a decision, not an accident.
  • Watch pages in your logs. It’s the cheapest early warning you have.