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 loginPayment fraud alertCourier arrivingCritical service outage

Poor SMS use cases:

Weekly newsletterLow-value promotionEvery in-app activityBulk reminders with no user urgency

Provider Choices

ProviderStrength
TwilioGlobal developer experience
MSG91India-focused SMS workflows
AWS SNSCloud-native integrations
Local aggregatorCost 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

Process flow6 steps
  1. 01
    User requests login
  2. 02
    Generate OTP
  3. 03
    Hash OTP and store expiry
  4. 04
    Send SMS job
  5. 05
    User submits OTP
  6. 06
    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.

English
Your OTP is 123456 -> 1 segment
Unicode
आपका OTP 123456 है -> may use Unicode encoding

Track segment count before sending, especially for multilingual products.

Delivery Reports

SMS providers may send status webhooks.

queuedsentdeliveredfailedundelivered

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