Lesson 10 of 15SMS Notification System

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

  1. Using SMS for low-urgency messages.
  2. Not rate-limiting OTP requests.
  3. Storing raw OTPs.
  4. Ignoring Unicode segment costs.
  5. Retrying SMS without duplicate control.

Interview Questions

  1. Why is SMS expensive to operate?
  2. How would you prevent OTP abuse?
  3. Why should OTPs be hashed?
  4. 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
SMS Notification System - Production Notification System Design | Niraj Kumar