Agent Wranglers

Text messages Recommended

Keep each text to one segment

Keep each text within one segment, and test every template for its encoding and length.

How to do it

A text in the GSM-7 alphabet fits 160 characters, or 153 per part when split. One character outside it, such as an emoji or a curly quote, switches the whole text to UCS-2. Then it fits 70 characters, or 67 per part, and an emoji counts as two. GSM-7 extension characters, such as € and the square brackets, count as two. Each part is billed as one message. Replace curly quotes and long dashes with plain ones, and put long details behind a link.

Example

const GSM7 = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./0-9:;<=>?¡A-ZÄÖÑܧ¿a-zäöñüà^{}\\[~\]|€]*$/;
const GSM7_EXTENSION = /[\^{}\\[~\]|€]/g;

export function segments(text) {
  if (!GSM7.test(text)) return text.length <= 70 ? 1 : Math.ceil(text.length / 67);
  const units = text.length + (text.match(GSM7_EXTENSION)?.length ?? 0);
  return units <= 160 ? 1 : Math.ceil(units / 153);
}
import math
import re

GSM7 = re.compile(r"^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\"#¤%&'()*+,\-./0-9:;<=>?¡A-ZÄÖÑܧ¿a-zäöñüà^{}\\\[~\]|€]*$")

GSM7_EXTENSION = re.compile(r"[\^{}\\\[~\]|€]")

def segments(text: str) -> int:
    if not GSM7.fullmatch(text):
        utf16_units = len(text.encode("utf-16-le")) // 2
        return 1 if utf16_units <= 70 else math.ceil(utf16_units / 67)
    units = len(text) + len(GSM7_EXTENSION.findall(text))
    return 1 if units <= 160 else math.ceil(units / 153)

How to check

A unit test renders every template with the longest sample data and counts segments. Each one is 1, or 2 where the catalogue allows it.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers notifications playbook: https://agentwranglers.se/playbook/notifications/text-messages/sms-segments.