SMS Notification System
SMS is fast, universal, and expensive. That combination makes it powerful and dangerous. Use it when the value of immediacy is higher than the cost.
Good SMS use cases:
OTP login
Payment fraud alert
Courier arriving
Critical service outage
Poor SMS use cases:
Weekly newsletter
Low-value promotion
Every in-app activity
Bulk reminders with no user urgency
Provider Choices
Provider Strength
Twilio global developer experience
MSG91 India-focused SMS workflows
AWS SNS cloud-native integrations
Local aggregator cost and regional routing
Large systems often use more than one provider. If provider A fails or becomes expensive for a country, route to provider B.
OTP Flow
User requests login
|
v
Generate OTP
|
v
Hash OTP and store expiry
|
v
Send SMS job
|
v
User submits OTP
|
v
Compare hash and mark used
Never store raw OTPs.
const otp = generateSixDigitOtp();
const otpHash = await hashOtp(otp);
await db.otp.create({
data: {
phone,
otpHash,
expiresAt: addMinutes(new Date(), 5),
consumedAt: null
}
});
Unicode and Message Length
SMS length changes with encoding. English text often allows 160 characters per segment. Unicode characters can reduce segment size. More segments means higher cost.
"Your OTP is 123456" -> 1 segment
"आपका OTP 123456 है" -> may use Unicode encoding
Track segment count before sending, especially for multilingual products.
Delivery Reports
SMS providers may send status webhooks.
queued
sent
delivered
failed
undelivered
Store provider status separately from internal attempt status. Provider "sent" does not always mean the handset received it.
Cost Controls
SMS fraud can burn money quickly. Apply limits:
phone number: 3 OTPs / 10 minutes
IP address: 20 OTPs / hour
user account: 5 OTPs / hour
country: allowlist for launch regions
Use CAPTCHA or risk scoring after repeated requests.
SMS is a payment surface. Treat every send request like it can cost real money.
Failover
async function sendSmsWithFailover(message: SmsMessage) {
try {
return await primarySmsProvider.send(message);
} catch (error) {
if (!isProviderOutage(error)) throw error;
return backupSmsProvider.send(message);
}
}
Failover should avoid duplicate sends. Use provider outage detection and idempotency records.
Common Mistakes
- Using SMS for low-urgency messages.
- Not rate-limiting OTP requests.
- Storing raw OTPs.
- Ignoring Unicode segment costs.
- Retrying SMS without duplicate control.
Interview Questions
- Why is SMS expensive to operate?
- How would you prevent OTP abuse?
- Why should OTPs be hashed?
- What is a delivery report?
Exercise
Design an OTP SMS flow for login. Include rate limits, expiry, retry policy, and fraud controls.
What you will learn
When SMS is worth its cost.
How OTP and transactional SMS flows differ.
How delivery reports update status.
How fraud and cost abuse are controlled.
Production checklist
- SMS is reserved for high-value cases
- OTP expiry is enforced
- Rate limits are applied
- Delivery reports are stored
- Unicode length is considered
- Fraud controls exist