Use Cases (अपडेट: 2/6/2026)

Claude Code से Twilio SMS: notifications, Verify और Webhooks production-ready

Claude Code से Twilio SMS बनाएं: E.164, consent, idempotency, retries, Verify और status callbacks।

Claude Code से Twilio SMS: notifications, Verify और Webhooks production-ready

SMS अभी भी ऐसा channel है जो user के app न खोलने पर भी पहुंच सकता है। Order shipment, appointment reminder, incident alert, login verification और support acknowledgement जैसे cases में email या push notification काफी नहीं होते।

Twilio SMS integration छोटी लगती है, लेकिन production में सिर्फ client.messages.create काफी नहीं है। आपको phone number validation, user consent, duplicate send रोकना, retry strategy, status callbacks, Webhook signature validation और privacy-safe logs चाहिए।

यह guide दिखाती है कि Claude Code से Express + TypeScript में practical Twilio SMS integration कैसे बनवाएं। इसमें outbound SMS, Twilio Verify, status callbacks, idempotency, retries, logging, security और common failure cases शामिल हैं। Related topics के लिए authentication implementation, Webhook implementation और secrets management पढ़ें।

Twilio SMS को सरल भाषा में समझें

Twilio communication API देता है। आपका backend Twilio से कहता है: इस sender से, इस phone number पर, यह message भेजो। Twilio message को carrier network तक पहुंचाता है और Message SID लौटाता है। यही SID support, troubleshooting और delivery tracking में काम आती है।

Phone numbers को E.164 format में भेजना चाहिए: plus sign, country code और number, जैसे +15558675310 या +819012345678। यह user-friendly local format नहीं, बल्कि API-safe international format है। Official reference के लिए Twilio की international number formatting guidance देखें।

API response आने के बाद भी delivery flow खत्म नहीं होता। SMS status queued, sent, delivered, undelivered या failed में बदल सकता है। Twilio इन बदलावों को आपके status callback endpoint पर भेज सकता है। Implementation के समय Programmable Messaging, Node.js SMS tutorial, Messaging Webhooks और outbound status callbacks को primary source मानें।

Realistic use cases

Generic “send SMS” helper से शुरू न करें। पहले business event और failure rules तय करें।

Use caseSMS क्यों उपयोगी हैक्या ध्यान रखें
Order और shipping updateCustomer email miss कर सकता है, पर status जरूरी हैगलत tracking URL, duplicate send, opt-out
Appointment reminderNo-show और last-minute confusion घटता हैTime zone, quiet hours, consent record
Incident/admin alertOn-call person Slack/email न देखे तब भी alert पहुंचेAlert storm, rate limit, escalation
Login check और 2FAAccount protection में मददHomegrown OTP के बजाय Twilio Verify देखें
Support acknowledgementUser को तुरंत पता चलता है कि request मिलीBody में sensitive details न डालें

Pricing, supported countries, sender registration, A2P जैसी rules और compliance requirements बदलते रहते हैं। इस article में इन्हें hard-code नहीं किया गया है। Launch से पहले Twilio Console, latest official docs और legal/compliance review जरूर करें।

Claude Code prompt

Claude Code से केवल API call न मांगें। Production behavior साफ लिखें।

Implement Twilio SMS notifications in Express + TypeScript.

Requirements:
- Read Twilio credentials, sender number, and Verify Service SID from env vars
- Validate phone numbers in E.164 format with Zod
- Add POST /api/order-shipped-sms for order shipment SMS
- Use eventId as the idempotency key so duplicate events do not send twice
- Retry only 429 and 5xx-style transient failures
- Never log full phone numbers, full message bodies, Auth Tokens, or OTP codes
- Receive status callbacks at POST /twilio/status-callback
- Require Twilio signature validation in production
- Add Twilio Verify start/check endpoints
- Include .env.example, package.json, run commands, and curl examples

Idempotency का अर्थ है कि same business event दोबारा आए तो भी external effect duplicate न हो। SMS में यह बहुत जरूरी है, क्योंकि queue retry, Webhook redelivery, batch replay या support action same message को फिर trigger कर सकते हैं।

flowchart LR
  A["Order update"] --> B["Idempotency check"]
  B --> C["Twilio Messaging API"]
  C --> D["SMS delivery"]
  C --> E["Store Message SID"]
  D --> F["Status Callback"]
  F --> G["Signature check"]
  G --> H["Delivery log update"]
  I["Login check"] --> J["Twilio Verify"]

Minimal project बनाएं

यह project copy-paste करके चलाया जा सकता है। Real Twilio credentials के बिना SMS delivery सफल नहीं होगी, लेकिन environment parsing, request validation, duplicate handling और local callback parsing test हो सकते हैं।

mkdir twilio-sms-demo
cd twilio-sms-demo
npm init -y
npm install express twilio dotenv zod
npm install -D typescript tsx @types/express
{
  "type": "module",
  "scripts": {
    "dev": "tsx src/app.ts"
  },
  "dependencies": {
    "dotenv": "latest",
    "express": "latest",
    "twilio": "latest",
    "zod": "latest"
  },
  "devDependencies": {
    "@types/express": "latest",
    "tsx": "latest",
    "typescript": "latest"
  }
}
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}
# .env.example
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=replace-with-your-auth-token
TWILIO_FROM_NUMBER=+15551234567
TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PUBLIC_BASE_URL=https://example.ngrok-free.app
REQUIRE_TWILIO_SIGNATURE=true
PORT=3000

PUBLIC_BASE_URL ऐसी HTTPS URL होनी चाहिए जिसे Twilio access कर सके। Local development में ngrok या Cloudflare Tunnel इस्तेमाल करें। Twilio signature validation exact URL पर depend करता है, इसलिए protocol, proxy, query string और trailing slash ध्यान से मिलाएं।

SMS, idempotency और callbacks implement करें

src/app.ts बनाएं और यह code paste करें। Demo in-memory Map इस्तेमाल करता है; production में PostgreSQL, Redis, DynamoDB या durable store के साथ idempotency key पर unique constraint लगाएं।

import "dotenv/config";
import express from "express";
import twilio from "twilio";
import { z } from "zod";

const e164Schema = z.string().regex(/^\+[1-9]\d{1,14}$/, {
  message: "Use E.164 format, for example +819012345678.",
});

const envSchema = z.object({
  TWILIO_ACCOUNT_SID: z.string().regex(/^AC[a-fA-F0-9]{32}$/),
  TWILIO_AUTH_TOKEN: z.string().min(20),
  TWILIO_FROM_NUMBER: e164Schema,
  TWILIO_VERIFY_SERVICE_SID: z.string().regex(/^VA[a-fA-F0-9]{32}$/).optional(),
  PUBLIC_BASE_URL: z.string().url(),
  REQUIRE_TWILIO_SIGNATURE: z.enum(["true", "false"]).default("true"),
  PORT: z.coerce.number().int().positive().default(3000),
});

const env = envSchema.parse(process.env);
const client = twilio(env.TWILIO_ACCOUNT_SID, env.TWILIO_AUTH_TOKEN);
const app = express();

type Delivery = {
  status: "pending" | "sent" | "failed";
  attempts: number;
  updatedAt: string;
  sid?: string;
  error?: string;
};

const deliveries = new Map<string, Delivery>();

const orderSmsSchema = z.object({
  eventId: z.string().min(6).max(120),
  phone: e164Schema,
  orderId: z.string().min(1).max(80),
  trackingUrl: z.string().url().optional(),
  consentAt: z.string().datetime(),
});

const statusCallbackSchema = z.object({
  MessageSid: z.string().min(2),
  MessageStatus: z.string().min(2),
  To: z.string().optional(),
  ErrorCode: z.string().optional(),
}).passthrough();

function maskPhone(phone: string) {
  return phone.replace(/\d(?=\d{4})/g, "*");
}

function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function getErrorStatus(error: unknown) {
  if (typeof error === "object" && error && "status" in error) {
    return Number((error as { status?: number }).status ?? 0);
  }
  return 0;
}

function getErrorMessage(error: unknown) {
  return error instanceof Error ? error.message : String(error);
}

function shouldRetry(error: unknown) {
  const status = getErrorStatus(error);
  return status === 429 || status >= 500;
}

async function sendSmsWithRetry(params: {
  to: string;
  body: string;
  statusCallback: string;
  maxAttempts?: number;
}) {
  const maxAttempts = params.maxAttempts ?? 3;

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    try {
      const message = await client.messages.create({
        body: params.body,
        from: env.TWILIO_FROM_NUMBER,
        statusCallback: params.statusCallback,
        to: params.to,
      });

      return { sid: message.sid, attempts: attempt };
    } catch (error) {
      if (attempt === maxAttempts || !shouldRetry(error)) {
        throw error;
      }
      await delay(500 * attempt);
    }
  }

  throw new Error("SMS retry loop ended unexpectedly.");
}

function verifyTwilioSignature(req: express.Request) {
  const signature = req.header("x-twilio-signature") ?? "";
  const callbackUrl = new URL(req.originalUrl, env.PUBLIC_BASE_URL).toString();
  return twilio.validateRequest(env.TWILIO_AUTH_TOKEN, signature, callbackUrl, req.body);
}

app.use(express.json());

app.post("/api/order-shipped-sms", async (req, res) => {
  const parsed = orderSmsSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json({
      error: "invalid_request",
      details: parsed.error.flatten(),
    });
  }

  const input = parsed.data;
  const idempotencyKey = `order-shipped:${input.eventId}`;
  const existing = deliveries.get(idempotencyKey);

  if (existing?.status === "sent") {
    return res.status(200).json({
      duplicate: true,
      sid: existing.sid,
      status: existing.status,
    });
  }

  if (existing?.status === "pending") {
    return res.status(202).json({
      duplicate: true,
      status: existing.status,
    });
  }

  deliveries.set(idempotencyKey, {
    attempts: 0,
    status: "pending",
    updatedAt: new Date().toISOString(),
  });

  const trackingText = input.trackingUrl ? ` Tracking: ${input.trackingUrl}` : "";
  const body = `Your order ${input.orderId} has shipped.${trackingText}`;
  const statusCallback = new URL("/twilio/status-callback", env.PUBLIC_BASE_URL).toString();

  try {
    const result = await sendSmsWithRetry({
      body,
      statusCallback,
      to: input.phone,
    });

    deliveries.set(idempotencyKey, {
      attempts: result.attempts,
      sid: result.sid,
      status: "sent",
      updatedAt: new Date().toISOString(),
    });

    console.log("sms_sent", {
      idempotencyKey,
      sid: result.sid,
      to: maskPhone(input.phone),
    });

    return res.status(202).json({ accepted: true, sid: result.sid });
  } catch (error) {
    deliveries.set(idempotencyKey, {
      attempts: 3,
      error: getErrorMessage(error),
      status: "failed",
      updatedAt: new Date().toISOString(),
    });

    console.error("sms_failed", {
      idempotencyKey,
      message: getErrorMessage(error),
      status: getErrorStatus(error),
      to: maskPhone(input.phone),
    });

    return res.status(502).json({ error: "sms_delivery_failed" });
  }
});

app.post("/twilio/status-callback", express.urlencoded({ extended: false }), (req, res) => {
  if (env.REQUIRE_TWILIO_SIGNATURE === "true" && !verifyTwilioSignature(req)) {
    return res.status(403).send("invalid signature");
  }

  const parsed = statusCallbackSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).send("invalid callback");
  }

  console.log("twilio_status", {
    errorCode: parsed.data.ErrorCode,
    sid: parsed.data.MessageSid,
    status: parsed.data.MessageStatus,
    to: parsed.data.To ? maskPhone(parsed.data.To) : undefined,
  });

  return res.status(204).send();
});

app.listen(env.PORT, () => {
  console.log(`Twilio SMS demo listening on http://localhost:${env.PORT}`);
});

Server start करें और request भेजें। Real delivery के लिए valid Twilio credentials, sender, public callback URL और account में allowed destination number चाहिए।

npm run dev
curl -X POST http://localhost:3000/api/order-shipped-sms \
  -H "Content-Type: application/json" \
  -d '{
    "eventId": "order_1001_shipped_v1",
    "phone": "+15558675310",
    "orderId": "1001",
    "trackingUrl": "https://example.com/track/1001",
    "consentAt": "2026-06-02T09:00:00.000Z"
  }'

Same eventId दोबारा भेजने पर API दूसरा SMS नहीं भेजती, बल्कि existing state लौटाती है। Production में इस state को durable database में रखें।

Local callback shape test करने के लिए temporarily REQUIRE_TWILIO_SIGNATURE=false कर सकते हैं। Production में इसे true रखें।

curl -X POST http://localhost:3000/twilio/status-callback \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "MessageSid=SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  --data-urlencode "MessageStatus=delivered" \
  --data-urlencode "To=+15558675310"

OTP के लिए Twilio Verify इस्तेमाल करें

Login check और 2FA के लिए खुद छह digit code generate करना आसान लगता है, पर OTP में expiry, resend limit, brute-force protection, channel behavior और audit log चाहिए। Twilio Verify और Verification API इसी काम के लिए बने हैं।

नीचे का code src/app.ts में app.listen से पहले जोड़ें।

const verifyStartSchema = z.object({
  phone: e164Schema,
});

const verifyCheckSchema = z.object({
  code: z.string().min(4).max(10),
  phone: e164Schema,
});

function requireVerifyServiceSid() {
  if (!env.TWILIO_VERIFY_SERVICE_SID) {
    throw new Error("TWILIO_VERIFY_SERVICE_SID is required for Verify.");
  }
  return env.TWILIO_VERIFY_SERVICE_SID;
}

app.post("/api/verify/start", async (req, res) => {
  const parsed = verifyStartSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json({ error: "invalid_request" });
  }

  const verification = await client.verify.v2
    .services(requireVerifyServiceSid())
    .verifications.create({
      channel: "sms",
      to: parsed.data.phone,
    });

  return res.status(202).json({ sid: verification.sid, status: verification.status });
});

app.post("/api/verify/check", async (req, res) => {
  const parsed = verifyCheckSchema.safeParse(req.body);

  if (!parsed.success) {
    return res.status(400).json({ error: "invalid_request" });
  }

  const check = await client.verify.v2
    .services(requireVerifyServiceSid())
    .verificationChecks.create({
      code: parsed.data.code,
      to: parsed.data.phone,
    });

  return res.json({ approved: check.status === "approved", status: check.status });
});

Verify approved होने के बाद अपनी user table में phoneVerifiedAt या mfaEnabledAt update करें। Full auth boundary के लिए authentication guide और Zod validation guide देखें।

SMS personal phone number पर सीधे जाता है, इसलिए consent record जरूरी है। User ने किस purpose के लिए SMS स्वीकार किया, कहां किया और opt-out कैसे handle होगा, यह लिखें। Country, sender type और content के हिसाब से requirements बदलती हैं, इसलिए latest Twilio docs और legal review को आधार बनाएं।

Real Account SID, Auth Token, OTP, full phone number या full message body को code, prompts, screenshots या logs में न डालें। .env Git में न जाए; production में hosting platform या secret manager से secrets inject करें।

Logs में आमतौर पर Message SID, event ID, message type, masked phone, Twilio error code, attempt count और timestamp काफी होते हैं। Full body retain करनी हो तो retention, access और deletion rules पहले तय करें।

Common pitfalls

Common failures हैं: local phone format को API में भेजना, queue retry से duplicate SMS भेजना, public callback पर signature validation न रखना, OTP खुद बनाना, और logs में Message SID या error code न रखना। Async design के लिए queue systems guide और security review के लिए security best practices देखें।

Claude Code review prompt

Review this Twilio SMS implementation before production.

Check:
- E.164 validation always runs before sending
- Consent timestamp and message purpose are tracked
- eventId idempotency holds under parallel requests
- Only 429 and 5xx transient failures are retried
- Twilio status callback signature validation is required in production
- Auth Tokens, OTP codes, full phone numbers, and full bodies never reach logs
- Pricing, countries, or regulatory rules are not hard-coded in comments
- Support can trace a failure by Message SID

यह topic monetizable है क्योंकि reader को सिर्फ function नहीं चाहिए; auth, queues, Webhooks, logs, CLAUDE.md और review gates भी चाहिए। ClaudeCodeLab Claude Code training and consultation के जरिए इसे real repository workflow में बदल सकता है।

Summary

Twilio SMS एक छोटे API call से शुरू होता है, लेकिन production quality E.164, consent, idempotency, retries, callback signature और privacy-safe logs पर निर्भर करती है। Claude Code को पहले prompt में ये requirements दें और result को operational integration की तरह review करें।

इस article की hands-on check में local E.164 validation, duplicate eventId handling, Status Callback parsing और masked logs की shape verify हुई। Real SMS delivery फिर भी Twilio credentials, sender setup, destination country और current Twilio rules पर निर्भर करती है; launch से पहले controlled test number पर Message SID और callback status trace करें।

#Claude Code #Twilio #SMS #notifications #API integration
मुफ़्त

मुफ़्त PDF: Claude Code cheatsheet

Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.

हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.

Masa

लेखक के बारे में

Masa

Claude Code workflow और team adoption पर काम करने वाला engineer.