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.
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.
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.
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 billed | Max length, GSM-7 (characters) | Max length, UCS-2 (characters) |
|---|---|---|
| 1 | 160 | 70 |
| 2 | 306 | 134 |
| 3 | 459 | 201 |
| 4 | 612 | 268 |
| 5 | 765 | 335 |
| 10 | 1,530 | 670 |
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:
| Language | Letters that stay in GSM-7 | Letters that force UCS-2 |
|---|---|---|
| Polish | none of the Polish-specific letters | ✗ ą ć ę ł ń ó ś ź ż and their capitals |
| German | ✓ ä ö ü ß Ä Ö Ü | none of the standard German letters |
| Spanish | ~ ñ Ñ é É ü Ü ¿ ¡ | ✗ á í ó ú Á Í Ó Ú |
| English | ✓ all letters | curly 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 destination | Price per segment | 1,000 × 1 segment | 1,000 × 2 segments | 1,000 × 3 segments |
|---|---|---|---|---|
| Twilio → Poland | USD 0.0457 | USD 45.70 | USD 91.40 | USD 137.10 |
| Twilio → Germany | USD 0.112 | USD 112.00 | USD 224.00 | USD 336.00 |
| Twilio → Spain | USD 0.0875 | USD 87.50 | USD 175.00 | USD 262.50 |
| SMSAPI → Poland (prepaid, lowest tier) | PLN 0.17 net | PLN 170 | PLN 340 | PLN 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.
Do links, opt-out text and the sender ID count?
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.
- 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.
- Test with the longest real values
Fill placeholders with the longest names and codes from your data, and with names that contain diacritics.
- Send a test SMS to your own phone
Check how it renders, whether the parts arrive joined and whether the link works.
- Read the segment count from the provider
Twilio returns
num_segmentsin the message resource; SMSAPI returnspartsand 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
Draft templates in a code editor or the SMS editor itself, never in Word or Google Docs, which insert curly quotes and dashes automatically.
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.
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.
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
- Twilio: What is the SMS character limit?
- Twilio Support: Can I send and receive emoji with Twilio?
- AWS End User Messaging SMS: SMS character limits and GSM 03.38 character set
- SMSAPI documentation (message, encoding, normalize, max_parts)
- SMSAPI price list
- Twilio SMS pricing: Poland
- Twilio SMS pricing: Germany
- Twilio SMS pricing: Spain
Send email and SMS from your own server
NetSendo runs on your infrastructure and connects to the SMS provider you already use.




