NetSendo
Guides & Tips

SMS character limit: why one text is billed as 2 or 3

NetSendo Team13 min read
SMS character limit: why one text is billed as 2 or 3

An SMS holds 160 characters only while every character in it belongs to the GSM-7 alphabet. One character outside that alphabet, such as a Polish ł, a Spanish á, an emoji or a curly apostrophe, switches the whole message to UCS-2 encoding, which fits just 70 characters, and long messages then split every 67 characters instead of every 153. Providers bill per segment, so that single character can double or triple the cost of a send.

TL;DR: A single SMS segment carries 140 bytes. GSM-7 packs 160 seven-bit characters into it; UCS-2 needs 16 bits per character and fits 70. Concatenated messages lose space to a header, leaving 153 (GSM-7) or 67 (UCS-2) characters per segment, and you pay for every segment. Check your text against the GSM 03.38 table, watch for characters that count double (€ [ ] { } \ ~ ^ |), and test the real segment count with your provider before a large send.

Why is an SMS 160 characters, or only 70?

Because one SMS segment has a fixed payload of 140 bytes (1,120 bits), and the encoding decides how many characters fit into it. GSM-7 uses 7 bits per character, so 1,120 ÷ 7 = 160 characters; UCS-2 uses 16 bits per character, so 1,120 ÷ 16 = 70 characters.

The AWS End User Messaging SMS documentation puts it plainly: a message that uses only characters from the GSM 03.38 set can contain up to 160 characters, and a message with any character outside that set can have up to 70. The switch is all-or-nothing. The encoding is chosen per message, not per character, so 159 plain letters and one emoji are encoded entirely in UCS-2.

GSM-7 (GSM 03.38)

The default 7-bit SMS alphabet defined by the GSM standard. It covers the Latin letters A–Z and a–z, digits, common punctuation, a handful of accented Western European letters (such as é, ä, ö, ü, ñ, à) and some Greek capitals.

UCS-2

A fixed 16-bit encoding of Unicode used when a message contains any character outside GSM-7. It can represent almost any script, at the price of fitting fewer than half as many characters into a segment.

Why do long messages split at 153 and 67 characters?

When a message is longer than one segment, each segment carries a User Data Header (UDH) that tells the phone how to reassemble the parts, and that header takes space from the text. In GSM-7 it costs 7 characters, which leaves 153 per segment; in UCS-2 it costs 3 characters, which leaves 67.

Twilio's SMS character limit page gives the same figures: 160 for a single GSM-7 segment, 153 per segment in a multi-part message, 70 for a single UCS-2 segment and 67 per segment after that. It also notes an exception for US and Canadian toll-free numbers, where the limits are 152 and 66.

The practical consequence is that a 161-character message is not "one SMS plus one character". It becomes two segments of up to 153 characters, and the capacity of two segments is 306 characters, not 320. The table shows where each extra segment starts.

Segments billedMax length, GSM-7 (characters)Max length, UCS-2 (characters)
116070
2306134
3459201
4612268
5765335
101,530670

Calculated from 160/153 and 70/67 characters per segment. As of November 2026.

Providers also cap the total length. SMSAPI accepts up to 1,530 characters (670 with special characters) in at most 10 parts, AWS accepts 1,530 GSM characters or 630 non-GSM characters, and Twilio lists 1,600 and 700, while recommending that you keep SMS under 320 characters. Longer messages are rejected or, depending on the provider, sent another way, so check your provider's limit before you build long templates.

Which characters force UCS-2 encoding?

Any character that is not in the GSM 03.38 table. For most senders the culprits fall into three groups: letters with diacritics that GSM-7 does not include, typographic punctuation inserted by word processors, and emoji.

The AWS page reproduces the full GSM 03.38 table. Comparing it with the alphabets of a few European languages shows why the same campaign can cost one segment in German and two in Polish:

LanguageLetters that stay in GSM-7Letters that force UCS-2
Polishnone of the Polish-specific letters✗ ą ć ę ł ń ó ś ź ż and their capitals
German✓ ä ö ü ß Ä Ö Ünone of the standard German letters
Spanish~ ñ Ñ é É ü Ü ¿ ¡✗ á í ó ú Á Í Ó Ú
English✓ all letterscurly quotes, en/em dashes, ellipsis character

Based on the GSM 03.38 character table published by AWS. As of November 2026.

Characters that slip in without anyone typing them deliberately cause most of the surprises:

  • Curly quotes and apostrophes (’ ‘ “ ”): Word, Google Docs and macOS replace straight quotes automatically. The AWS documentation shows a 130-character message that needs two parts only because of one closing single quote in "Dr. Salazar’s".
  • Dashes and ellipsis (– — …): the hyphen-minus (-) is in GSM-7, the en dash and the single-character ellipsis are not.
  • Invisible characters: a non-breaking space or a zero-width space copied from a web page or a CMS looks like a normal space but forces UCS-2.
  • Emoji: no emoji is in GSM-7. According to Twilio's emoji support article, an emoji uses 2 of the 70 characters of a UCS-2 segment, and emoji with modifiers take even more.
⚠️ Watch out: Personalisation changes the encoding per recipient. "Hi [[fname]]" stays in GSM-7 for Anna and switches to UCS-2 for Łukasz or Ramón, so the same template can cost one segment for one contact and two for another.

Which GSM-7 characters count as two?

Nine printable characters live in the GSM-7 extension table and each is sent as an escape character plus the character itself, so it takes two of the 160 (or 153) places: ^ { } \ [ ] ~ | €.

These characters do not switch the message to UCS-2, but they do eat into the limit. "Price: 20€ [today only]" is 23 characters on screen and 26 in GSM-7. The effect is small in plain text and large in messages that contain JSON, code snippets or URLs with brackets. If a message hovers near a boundary, write "EUR" instead of "€" or replace square brackets with round ones.

If a counter and the table ever disagree, trust the segment count your provider returns after sending, because that is what you are billed for.

How much does the encoding add to the bill?

Every segment is billed as a message. Twilio states that it "bills SMS per message segment sent", AWS charges for the number of message parts, and SMSAPI sends long messages as up to 10 linked parts billed according to its price list. A message that grows from one to two segments doubles the cost of the send.

The table multiplies official per-segment prices by 1,000 recipients. The prices are list prices for outbound SMS on the providers' public pages; volume discounts, carrier fees and VAT are not included.

Provider and destinationPrice per segment1,000 × 1 segment1,000 × 2 segments1,000 × 3 segments
Twilio → PolandUSD 0.0457USD 45.70USD 91.40USD 137.10
Twilio → GermanyUSD 0.112USD 112.00USD 224.00USD 336.00
Twilio → SpainUSD 0.0875USD 87.50USD 175.00USD 262.50
SMSAPI → Poland (prepaid, lowest tier)PLN 0.17 netPLN 170PLN 340PLN 510

Twilio prices "current as of October 2026" on Twilio's pages; SMSAPI price list checked November 2026. As of November 2026.

The example below shows how little it takes. Both texts say the same thing in Polish; the first is 94 characters with diacritics and goes out as two UCS-2 segments, the second is the same 94 characters without them and fits into one GSM-7 segment:

Cześć Anna, Twoje zamówienie 1042 zostało wysłane. Śledź: https://ex.co/t/1042 STOP=rezygnacja
→ UCS-2, 94 characters, 2 segments

Czesc Anna, Twoje zamowienie 1042 zostalo wyslane. Sledz: https://ex.co/t/1042 STOP=rezygnacja
→ GSM-7, 94 characters, 1 segment

The German equivalent, "Hallo Jürgen, deine Bestellung 1042 ist unterwegs…", stays in GSM-7 at 109 characters, because ü is in the table. The Spanish one with "está" and "Síguelo" switches to UCS-2 at 106 characters and needs two segments.

Should you strip diacritics from SMS text?

It depends on the language and on the reader. Stripping diacritics (transliteration) keeps a message in GSM-7 and can halve the cost, but in some languages it lowers readability and can look careless, so it is a trade-off, not a rule.

✅ Advantages

  • Up to 160 characters in one segment instead of 70; up to 153 per segment instead of 67.
  • Predictable cost: names with diacritics no longer push individual recipients into an extra segment.
  • Polish SMS without diacritics are common in transactional messages, so many readers are used to them.

❌ Considerations

  • Some words become ambiguous or change meaning when the accent disappears (Polish "sąd", a court, becomes "sad", an orchard; Spanish "papá" becomes "papa"; "año" is safe because ñ is in GSM-7).
  • A brand that writes carefully everywhere else may look sloppy in a text without accents.
  • Names of people and places are spelled wrong, which matters in personal messages.

You do not need to strip diacritics by hand. SMSAPI offers a normalize parameter that replaces special characters with their plain equivalents (ą→a, ć→c, ę→e), and its documentation says the default encoding is windows-1250 unless you pass another one such as utf-8. Twilio offers Smart Encoding, which replaces some non-GSM characters with GSM equivalents and turns curly quotes into straight ones.

A pragmatic middle ground many teams use: keep diacritics in short messages that fit into 70 characters anyway, and transliterate only when the text would otherwise need an extra segment. Whatever you choose, decide it once per language and apply it consistently.

Links and opt-out text count toward the length because they are part of the message body; the alphanumeric sender ID does not, because it travels in the address field of the SMS, not in the text.

Links

A full URL with UTM parameters easily takes 80–120 characters, more than half a GSM-7 segment. Use a short path on your own domain (for example https://ex.co/t/1042) rather than a long tracking URL. A short link on your own domain also keeps your brand visible and does not depend on a third-party shortener.

Opt-out text

Marketing SMS usually carry an opt-out instruction; the exact rules depend on the country. "Reply STOP to opt out" is 21 characters; budget for it in every template rather than adding it at the end and discovering a second segment. Keep it in GSM-7 characters so it never triggers UCS-2 by itself. For the consent rules that apply in your country, see the guide to email and SMS automation on your own server. This is not legal advice.

Placeholders

Placeholders such as a first name, an order number or a coupon code make the final length variable. Test templates with your longest realistic values, not with "Anna", and remember that a placeholder value with a diacritic changes the encoding for that recipient.

How do you check the segment count before sending?

Run the final text, with placeholders filled in, through a counter that knows the GSM 03.38 table, then confirm with your provider on a test send. The provider's figure is the one you pay for.

  1. Find non-GSM characters

    Paste the text into a counter that lists the characters that force UCS-2, not only the total. The Python function below does that offline.

  2. Test with the longest real values

    Fill placeholders with the longest names and codes from your data, and with names that contain diacritics.

  3. Send a test SMS to your own phone

    Check how it renders, whether the parts arrive joined and whether the link works.

  4. Read the segment count from the provider

    Twilio returns num_segments in the message resource; SMSAPI returns parts and the cost in points. Compare it with your estimate before sending to the whole list.

This function follows the GSM 03.38 table from the AWS documentation. It counts extension characters as two and emoji as two UCS-2 units. It is an estimate: real gateways may not split an escaped character or an emoji across two segments, which can occasionally add a segment near a boundary.

import math

GSM7_BASIC = set(
    "@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>?"
    "¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà"
)
GSM7_EXTENDED = set("\f^{}\\[]~|€")  # each costs 2 septets (ESC + char)


def sms_segments(text: str) -> dict:
    non_gsm = sorted({c for c in text if c not in GSM7_BASIC | GSM7_EXTENDED})
    if not non_gsm:
        units = sum(2 if c in GSM7_EXTENDED else 1 for c in text)
        single, per_part, encoding = 160, 153, "GSM-7"
    else:
        units = len(text.encode("utf-16-le")) // 2  # emoji = 2 units
        single, per_part, encoding = 70, 67, "UCS-2"
    parts = 1 if units <= single else math.ceil(units / per_part)
    return {"encoding": encoding, "units": units, "parts": parts,
            "non_gsm": non_gsm}


if __name__ == "__main__":
    import sys
    print(sms_segments(sys.stdin.read().rstrip("\n")))

Save it as sms_segments.py and pipe a message into it:

echo "Don’t miss it" | python3 sms_segments.py
# {'encoding': 'UCS-2', 'units': 13, 'parts': 1, 'non_gsm': ['’']}

The non_gsm list tells you exactly which character to replace. In the example above, swapping the curly apostrophe for a straight one brings the message back to GSM-7.

📋 Checklist

  • No curly quotes, en dashes, ellipsis characters or non-breaking spaces left from a text editor
  • Diacritics policy decided for this language (keep or transliterate)
  • Extension characters (€ [ ] { } \ ~ ^ |) counted as two
  • Opt-out text and short link included in the count
  • Template tested with the longest real placeholder values
  • Segment count confirmed on a test send with the provider

Segment counts when you send SMS from NetSendo

NetSendo sends SMS through Twilio, Vonage, MessageBird, Plivo or SMSAPI, and the segment rules above are the provider's, not NetSendo's. Two details matter in practice.

First, the counter in the SMS editor is a quick estimate. It labels a message as Unicode as soon as it contains any non-ASCII character and divides the length by 160 or 70. That means a German message with ü is shown as Unicode, although GSM-7 includes ü, and messages just above a segment boundary can be counted one segment short because the 153/67 rule is not applied. Use it as a warning light and check borderline texts with the function above.

Second, the provider's real figure is available after sending. The sms.sent webhook includes parts and credits for each message, taken from the provider's response (SMSAPI's parts and points, Twilio's num_segments). You can collect it in n8n with the NetSendo node or any endpoint described in the webhooks documentation, verify the HMAC-SHA256 signature and alert when a campaign averages more segments than planned. NetSendo sends to SMSAPI with encoding=utf-8, so diacritics are delivered as written; if you want them stripped, do it in the text. Messages can also be sent through the REST API.

ℹ️ Note: SMS sending in NetSendo was broken in older releases; version 2.2.0 fixed the SMS job and 2.7.2 fixed campaigns sent from the panel and test SMS. Update to the current release before you rely on SMS.

🎯 Expert tips

1
Write SMS in a plain-text editor

Draft templates in a code editor or the SMS editor itself, never in Word or Google Docs, which insert curly quotes and dashes automatically.

2
Budget 140 characters, not 160

Leave room for the opt-out text and for longer placeholder values, so a long name does not push a message into a second segment.

3
Decide on diacritics per language

German and most English texts fit GSM-7 anyway. Polish and Spanish need an explicit choice: accept UCS-2 and write for 70 characters, or transliterate.

4
Monitor parts after every campaign

Compare the average segments per message with your plan. A jump usually means one template picked up a non-GSM character.

Frequently asked questions

Why is my SMS limited to 70 characters?

Your text contains at least one character outside the GSM-7 alphabet, such as an emoji, a curly quote or a letter like ł or á. The whole message is then encoded in UCS-2, which uses 16 bits per character, so only 70 characters fit into the 140-byte segment.

Why are long SMS split into 153-character parts instead of 160?

Each part of a concatenated SMS carries a User Data Header that tells the phone how to reassemble the message. It takes 7 GSM-7 characters, leaving 153 per part, or 3 UCS-2 characters, leaving 67.

Does an emoji count as one or two characters in an SMS?

An emoji forces UCS-2 encoding for the whole message and, according to Twilio, uses 2 of the 70 characters available in a UCS-2 segment. Emoji with modifiers can take more.

Are German umlauts and Spanish ñ in the GSM-7 alphabet?

Yes. ä, ö, ü, ß, Ä, Ö, Ü, ñ, Ñ, é and É are part of GSM-7, so they do not reduce the limit. Spanish á, í, ó, ú and all Polish diacritics are not, and they switch the message to UCS-2.

Which characters count as two in a GSM-7 SMS?

The extension characters ^ { } \ [ ] ~ | and € are sent as an escape character plus the character, so each takes two of the 160 or 153 places.

Am I charged per SMS or per segment?

Per segment. Twilio and AWS bill each part, and SMSAPI sends long texts as linked parts billed by its price list of a concatenated message, so a text that grows from one to two segments doubles the cost of the send.

Sources

Send email and SMS from your own server

NetSendo runs on your infrastructure and connects to the SMS provider you already use.

#sms#sms-marketing#encoding#unicode#twilio
Share