SMS Channel

Norq compiles SMS templates from Markdown to plain text with automatic segment counting and encoding detection -- GSM-7 (standard ASCII, 160 chars/segment) vs UCS-2 (Unicode, 70 chars/segment).

Pipeline: Markdown -> AST (parse tree) -> plain text + segment count + encoding detection

Template file

sms.md -- always Markdown mode. Native JSON is not supported for SMS.

Frontmatter

---
enabled: true
---
Field Required Description
enabled No true (default) or false. Disables the channel.

SMS has no subject or other channel-specific frontmatter fields.

Directive compilation

Directive SMS output
:::header Ignored
:::footer Appended after a blank line
:::action "Label: url" plain text
:::callout Rendered as plain text (no styling)
:::hero Ignored
:::fields Plain text lines ("Key: Value")
:::media URL as plain text; ignored for non-URL content
:::columns Stacked sequentially as text
:::list Numbered lines
:::highlight Star prefix (⭐ text)
:::centered Plain text (no alignment)
:::raw Ignored

Example

Hey {{user.first_name}}, order #{{order.id}} shipped! Track: {{tracking_url}}
 
::: footer
Reply STOP to unsubscribe.
:::

Highlight

Rendered with a star prefix to draw attention in a plain-text medium.

::: highlight
Flash sale: 30% off today only!
:::

Compiles to: ⭐ Flash sale: 30% off today only!

Compiled output

Norq produces three fields:

  • body: The plain text message
  • segments: Number of SMS segments (1 segment = 160 GSM-7 chars or 70 UCS-2 chars)
  • encoding: "gsm7" or "ucs2"

Example output:

{
  "body": "Hey Gaurav, order #ORD-123 shipped! Track: https://track.example.com/123\n\nReply STOP to unsubscribe.",
  "segments": 1,
  "encoding": "gsm7"
}

Segment counting

SMS messages are split into segments based on encoding:

Encoding Single segment Multi-segment (per segment)
GSM-7 160 characters 153 characters
UCS-2 70 characters 67 characters

GSM-7 covers ASCII letters, digits, and common punctuation. If the message contains any character outside GSM-7 (e.g., emoji, accented characters, CJK), the entire message switches to UCS-2.

Testing segment count

Use tests.yaml to enforce segment limits:

tests:
  - name: "SMS fits 1 segment"
    channel: sms
    sample: "New user"
    assert:
      sms_segments: { lte: 1 }
 
  - name: "SMS under 2 segments"
    channel: sms
    sample: "Shipped with discount"
    assert:
      sms_segments: { lte: 2 }

Best practices

  • Keep messages under 160 characters for a single segment (cheapest delivery)
  • Avoid emoji if you want GSM-7 encoding (fewer characters per segment with UCS-2)
  • Use {{url | truncate 30}} if URLs are long and you need to save characters
  • Put opt-out text in :::footer for consistent formatting
  • Use norq preview <name> --channel sms --terminal to see the message and segment count