SMS Best Practices
Getting an SMS delivered is more than calling the API. This guide covers the practical decisions — sender IDs, message length, encoding, timing, and compliance — that separate 60% delivery from 95%+.
🚀 Quick Start
curl -X POST https://api.afriroute.ai/api/v1/sms/send \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+254712345678",
"from": "MyBrand",
"message": "Your order shipped! Reply STOP to opt out."
}'
📡 Deliverability Fundamentals
Deliverability is driven by three things: a trusted sender ID, a clean message body, and good timing.
| Factor | Impact | Recommendation |
|---|---|---|
| Registered sender ID | High | Register per-country before launch |
| Message content | Medium | Avoid spam trigger words, shortened URLs |
| Send timing | Medium | Local business hours, 8am–8pm |
| Number validation | Medium | Validate E.164 before sending |
| Throughput | Low | Smooth bursts to avoid carrier filtering |
✍️ Message Length & Encoding
SMS is billed per segment. The segment size depends on the character set used.
| Encoding | Single segment | Per segment (multipart) | Trigger |
|---|---|---|---|
| GSM-7 | 160 chars | 153 chars | Standard Latin text |
| UCS-2 (Unicode) | 70 chars | 67 chars | Emoji, Amharic, Arabic |
A single emoji or an accented character switches the entire message to UCS-2, cutting your limit to 70 characters. Keep transactional messages in plain GSM-7 where possible.
def estimate_segments(message: str) -> int:
is_unicode = any(ord(c) > 127 for c in message)
if is_unicode:
return 1 if len(message) <= 70 else -(-len(message) // 67)
return 1 if len(message) <= 160 else -(-len(message) // 153)
🏷️ Sender IDs
Use a registered alphanumeric sender ID (3–11 chars) for brand recognition and higher trust. Numeric shortcodes are required for two-way flows in some countries. See the dedicated Sender IDs guide.
const response = await fetch('https://api.afriroute.ai/api/v1/sms/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '+234803000000',
from: 'MyBank',
message: 'OTP 482913 expires in 5 min. Do not share.'
})
});
⏰ Timing & Throttling
- Send during local business hours (8am–8pm) for marketing.
- Transactional (OTP, alerts) can send any time.
- Spread large campaigns over minutes, not seconds, to avoid carrier rate filters.
- Use
scheduled_atto queue sends in the recipient's timezone.
🚫 Opt-Out & Compliance
Marketing SMS must offer opt-out in most African markets.
- Append
Reply STOP to opt outto promotional messages. - Maintain a suppression list and never re-message opted-out numbers.
- Keep consent records for audit.
if recipient in suppression_list:
skip() # never send to opted-out numbers
💡 Best Practices
- Register sender IDs per country before going live — unregistered IDs get filtered.
- Keep messages under 160 GSM-7 characters to avoid multipart charges.
- Avoid public URL shorteners — carriers flag them as spam.
- Validate numbers with Lookup to cut wasted spend.
- Track delivery webhooks instead of assuming success.
- Send time-sensitive OTPs immediately, batch the rest.
📚 Related Resources
Last Updated: May 2026