SMS notifications demand a unique approach. They're expensive, heavily regulated, and limited to 160 characters, yet they achieve crazy open rates and reach users instantly. Getting them right means balancing brevity with clarity, compliance with engagement, and cost control with reliability.

Writing effective SMS content

The 160-character limit is a forcing function for clarity. Every word must earn its place, and the message must be instantly comprehensible to someone glancing at their phone while walking, driving (at a red light), or in a meeting.

Contextual writing considerations

When writing an SMS notification, lead with identity and urgency. Unlike email, SMS doesn't automatically show who the sender is unless you're using an alphanumeric sender ID. Start with your brand name or service identifier, then immediately state why you're texting.

Instead of:

Payment processed successfully

Write:

Acme Bank: Your payment of $1,250 to Vendor Corp processed successfully

The most effective SMS notifications follow a consistent structure that users learn to recognize. For transactional messages, use this pattern: [Brand]: [What happened] [Specific detail] [Action if needed]. This front-loads the critical information while maintaining scannability.

Character encoding considerations

Character encoding can triple your costs overnight. Standard GSM-7 encoding gives you 160 characters per segment, but add a single emoji, smart quote, or accented character, and you switch to Unicode (UCS-2) encoding, and you're suddenly down to 70 characters per segment. A 150-character message with one emoji becomes a two-segment message, doubling your cost.

Common Unicode traps to avoid:

  • Smart quotes ("curly quotes" instead of "straight quotes").
  • Em dashes (— instead of --).
  • Ellipses (… as a single character instead of three periods ...).
  • Any emoji (even basic ones like ✓ or ★).
  • Accented characters (café, résumé, naïve).

Build character counting into your template system and warn content creators when they're approaching limits or using expensive characters. Many teams have saved thousands of dollars monthly just by replacing "—" with "--" in their templates (so don’t have AI write your SMS messages).

Personalization increases engagement, but watch the character cost. Including the recipient's name can improve response rates, but "Hi Jennifer Richardson" just consumed 22 characters, 14% of your message. For critical transactional messages, skip the greeting entirely. For marketing messages where engagement matters more, the personalization might be worth the character investment.

Writing guidelines by notification type

TypeExampleKey Elements
Authentication"Knock: Your login code is 445-892. Valid for 10 minutes."Brand, clear purpose, code formatting, expiration
Transaction

"Chase: Payment of $125.00 received from ABC Corp for Invoice #4412"

Brand, amount, parties, reference number
Appointment

"Dr. Smith appointment tomorrow (Jan 5) 2:30 PM. Reply C to confirm, R to reschedule"

Who, when, clear actions
Marketing

"Flash sale: 30% off all items today only. Shop: acme.co/sale Reply STOP to opt out"

Value prop, urgency, link, opt-out

Location-specific writing considerations

Beyond regulatory compliance, successful SMS requires cultural and linguistic adaptation. What works in San Francisco might fail in Frankfurt or confuse users in Tokyo.

Time zone intelligence prevents 3 AM wake-ups. Store user time zones and implement sending windows that respect local norms. But don't just rely on geographic assumptions. A user's phone number might have a New York area code while they live in Los Angeles.

def get_optimal_send_time(user, message_type):
    user_tz = user.timezone or infer_timezone(user.phone_number)
    local_time = convert_to_local(datetime.now(), user_tz)
 
    if message_type == 'critical':
        return 'immediate'  # Security alerts can't wait
 
    if message_type == 'transactional':
        # Send immediately during business hours, queue otherwise
        if 8 <= local_time.hour <= 20:
            return 'immediate'
        else:
            return local_time.replace(hour=8, minute=0)  # Next morning
 
    if message_type == 'marketing':
        # Optimal engagement windows by region
        optimal_hours = {
            'US': [10, 14, 19],  # 10 AM, 2 PM, 7 PM
            'EU': [9, 13, 18],   # 9 AM, 1 PM, 6 PM
            'APAC': [11, 15, 20] # 11 AM, 3 PM, 8 PM
        }
        return find_next_optimal_hour(local_time, optimal_hours[user.region])

Localization goes beyond translation. Each market has different expectations for formality, information density, and interaction patterns:

  • United States: Casual tone acceptable, include "Reply HELP for help."
  • Germany: Formal tone preferred, include data privacy notice.
  • Japan: Ultra-polite language, avoid direct commands.
  • Middle East: Consider right-to-left text display, Friday/Saturday weekends.

Date and number formatting matters too. "Your appointment on 05/03/25" means May 3rd to Americans but March 5th to Europeans. Use unambiguous formats: "May 3, 2025" or "3 May 2025".

Opt-out commands must work in local languages. While "STOP" is universally recognized, support local variants:

  • French: ARRET, STOP
  • Spanish: BAJA, PARAR, STOP
  • German: STOPP, ABMELDEN
  • Italian: FERMA, CANCELLA

Templating and content management

Templates are the foundation of consistent, compliant SMS at scale. But unlike email templates that can be freely edited, SMS templates in many countries must be pre-registered and approved.

Knock's template editor provides a unified approach to SMS template management using Liquid syntax, the same templating language used across all channels. This consistency means your team learns one syntax for all notification types.

You can reference user properties, trigger data, and even data from other Knock resources directly in your templates:

{% if recipient.plan == "premium" %} {{ recipient.name }}, your premium order
#{{ data.order_id }} shipped! Track: {{ data.tracking_url }} {% else %} Order
#{{ data.order_id }} shipped! Track: {{ data.tracking_url }} {% endif %} Reply
STOP to opt out

Partials allow you to build reusable content blocks that work across all channels. Create a standard SMS footer with opt-out language once, then include it in every marketing template with {% partial "sms_footer" %}. When regulations change, update the partial once and every template using it automatically updates.

For markets requiring template pre-registration, like India's DLT system, Knock's template versioning becomes invaluable. You can maintain different template versions for different regions, test changes in development environments, and track exactly what content was approved and when. The Management API and CLI let you work with templates programmatically, enabling automated deployments and integration with your existing CI/CD pipeline.

If you roll your own templating system, make sure you version control your templates like code. Track changes, require review for modifications, and maintain rollback capability. A single character change can break deliverability or compliance and result in extra costs.

Test templates with edge cases:

  • Maximum length names and values.
  • Missing optional variables.
  • Special characters in user data.
  • Different character encodings.
  • Multiple language variants.

Links in SMS present unique challenges. They consume precious characters, look suspicious to security-conscious users, and can trigger carrier spam filters if not appropriately handled.

Invest in a branded short domain instead of using generic shorteners. acme.co/deal builds trust and saves characters compared to bit.ly/x7k9m2p. The setup cost (as low as $100-500 for domain and SSL) pays for itself in improved click rates and deliverability. Implement intelligent link generation that balances tracking needs with user experience:

class SMSLinkGenerator {
  generateLink(destination, campaign, recipient) {
    // Keep slugs short but meaningful
    const slug = this.generateSlug(campaign, 5);
 
    // Add UTM parameters for analytics
    const params = new URLSearchParams({
      utm_source: "sms",
      utm_campaign: campaign.id,
      utm_content: this.hashRecipient(recipient), // Privacy-safe tracking
    });
 
    // Generate short link
    const shortUrl = `${this.domain}/${slug}`;
 
    // Store mapping for redirect
    this.store.set(shortUrl, {
      destination: `${destination}?${params}`,
      recipient: recipient,
      created: Date.now(),
      campaign: campaign.id,
    });
 
    return shortUrl;
  }
}

Track engagement but respect privacy. Log clicks at the aggregate level (campaign performance) while being careful about individual tracking. Some jurisdictions require disclosure if you're tracking individual link clicks. Structure your messages to build context before presenting the CTA.

Instead of:

Track: acme.co/track your order has shipped

Write:

Your order shipped! Track delivery: acme.co/track

Knock link tracking streamlines this process. When you enable link tracking for SMS channels, Knock automatically generates short links that are consistently 31 characters long, perfect for character-constrained SMS messages. These links use Knock's tiered domain system (like c1.knock.app or e2.knock.app) to prevent domain poisoning, though Enterprise customers can configure custom tracking domains like links.yourcompany.com for better brand consistency and deliverability.

Knock captures message.link_clicked events that you can use to trigger workflow conditions (like sending a follow-up only if a link wasn't clicked) or integrate with your analytics via webhooks. Since Knock handles the link wrapping and event capture automatically, you don't need to build your own shortening infrastructure. Just include regular URLs in your templates, and Knock handles the rest.

Managing costs and scaling efficiently

Now that you've written the perfect SMS message, you’ll want to consider strategies for cost-effective sending.

SMS costs add up fast. At $0.01 per message, sending daily notifications to 10,000 users costs $3,000 per month. Optimization can cut costs without sacrificing user experience.

Intelligent routing

Implement intelligent channel routing that considers both cost and effectiveness. Not every notification needs SMS's 98% open rate. Build a decision tree that evaluates each message and sends to less costly channels where appropriate:

function selectNotificationChannel(notification, user) {
  // Critical alerts always use SMS
  if (notification.priority === "critical") {
    return "sms";
  }
 
  // Use push for users with the app installed and active
  if (user.lastAppActivity < 7 && user.pushEnabled) {
    return "push";
  }
 
  // Marketing goes to email unless user explicitly prefers SMS
  if (notification.type === "marketing") {
    return user.marketingPreference || "email";
  }
 
  // Transactional: Try push first, fall back to SMS
  if (user.pushEnabled) {
    return ["push", "sms"]; // Multi-channel with fallback
  }
 
  return "sms";
}

Batching SMS notifications

Message batching and digests can dramatically reduce volume. Instead of sending five separate SMS notifications throughout the day, combine them into a single daily digest. This works particularly well for non-urgent updates:

  • Individual approach: 5 messages × $0.01 × 10,000 users = $500/day
  • Digest approach: 1 message × $0.01 × 10,000 users = $100/day
  • Savings: $12,000/month

Nellis Auction, an online marketplace for auctioning off returned and overstock items, uses message batching through Knock for all their SMS messaging, causing them to send fewer notifications and reduce their Twilio bill by 60%.

Check out this 4-minute video to learn how Knock's batching functionality addresses notification fatigue and rising provider costs by intelligently grouping related notifications together.

But be intelligent about batching. Authentication codes and payment confirmations need immediate delivery. Activity summaries and marketing updates can wait for the daily batch.