Skip to main content

Skala Twilio SMS i Node.js: DLT och webhooks

Lär dig skala Twilio SMS i Node.js. Hantera TRAI:s DLT-registrering, ta emot webhooks i stora volymer med BullMQ och konfigurera egna HTTP-agenter för…

Digvijay Singh Shekhawat
Digvijay Singh Shekhawat
July 26, 2026
9 min read
Krämvit växelbord i keramik med sladdar i mossgrönt, persika och pudrigt rosa på en ljus yta

Ett enda SMS via Twilios Node.js SDK är fem rader kod. Ett aviseringssystem som sväljer 10 000 samtidiga webhooks utan att stanna av event-loopen är något helt annat, och det händer inte av en slump. Så här bygger vi en tålig, DLT-kompatibel SMS-pipeline i Node.js: connection pooling, köbaserad bearbetning och validering av payloads, i just den prioritetsordningen.

Verkligheten bakom Twilio SMS i stor skala med Node.js

Varje handledning säger samma sak: skapa en twilio-klient, invänta messages.create(), klart. Det rådet faller sönder över 100 anrop per sekund. Om Twilios Node.js SDK startar en ny HTTP-agent per anrop i stället för att dela på en enda tar socketarna slut snabbt och event-loopen kvävs under last.

Twilios Messaging API ligger på vanliga HTTPS-endpoints. HTTP/2-multiplexering finns, men riktiga integrationer faller ofta tillbaka på connection pooling över HTTP/1.1. Utan en varm pool bränner processen CPU på en ny TLS-handskakning för varje utgående SMS. Rent slöseri i varje twilio sms javascript-uppsättning som går för fullt.

Peka npm-paketet twilio mot en egen HTTP-agent så håller sig socketarna varma. Uppkopplingstiden sjunker från cirka 150 ms till under 15 ms. Vid höga transaktionsvolymer är det gapet skillnaden mellan att hänga med och att hamna efter.

Routningslatensen i Indien svänger kraftigt mellan globala aggregatorer och lokala Tier 1-operatörer. Twilios globala routning är stabil, men lokala aktörer som Route Mobile och Gupshup når indiska telefoner på färre hopp. Draget är att behålla ett enda enhetligt API-lager genom Twilio och samtidigt routa över lokala operatörsnät, vilket bara fungerar när dina payloads uppfyller den lokala regleringen.

Steg för steg: Express, Node.js och Twilio

En härdad gateway börjar med en Express.js-server och Joi som validerar allt på väg in. Felformaterade telefonnummer och tomma meddelandetexter når aldrig Twilios API, vilket sparar både nätverksanrop och API-kostnader innan de uppstår.

Vi instansierar Twilio-klienten mot en egen https.Agent. Det är där maxSockets bor och där TCP-anslutningar återanvänds mellan anrop.

// client.js
const twilio = require('twilio');
const https = require('https');

// Keep sockets warm to avoid TLS handshake overhead on every SMS
const keepAliveAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 100,
  keepAliveMsecs: 3000,
  freeSocketTimeout: 15000
});

const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;

const twilioClient = twilio(accountSid, authToken, {
  httpClient: new twilio.RequestClient({
    agent: keepAliveAgent
  })
});

module.exports = twilioClient;

Express-routen sköter det utgående utskicket. Valideringen körs först; Twilios SDK ser bara rena payloads.

// server.js
const express = require('express');
const Joi = require('joi');
const twilioClient = require('./client');

const app = express();
app.use(express.json());

const smsSchema = Joi.object({
  to: Joi.string().pattern(/^\+[1-9]\d{1,14}$/).required(), // E.164 format
  body: Joi.string().min(1).max(1600).required(),
  peId: Joi.string().optional(), // Required for India DLT
  templateId: Joi.string().optional() // Required for India DLT
});

app.post('/api/v1/sms/send', async (req, res) => {
  const { error, value } = smsSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ error: error.details[0].message });
  }

  try {
    const payload = {
      to: value.to,
      from: process.env.TWILIO_PHONE_NUMBER,
      body: value.body
    };

    // Append DLT parameters if routing to India
    if (value.peId && value.templateId) {
      payload.edge = 'mumbai'; // Route via local Twilio edge
    }

    const message = await twilioClient.messages.create(payload);
    return res.status(202).json({ sid: message.sid, status: message.status });
  } catch (err) {
    return res.status(500).json({ error: 'Failed to dispatch SMS via Twilio', details: err.message });
  }
});

app.listen(3000, () => console.log('SMS Service listening on port 3000'));

Leveranskvittona (DLR) landar tillbaka på din server, och du kan inte ta dem för givna. Du måste bevisa att anropet verkligen kom från Twilio och inte från någon främling. npm-paketet twilio innehåller en middleware som gör signaturkontrollen åt dig.

// webhook.js
const express = require('express');
const twilio = require('twilio');

const app = express();

// Twilio signature verification requires the raw body to validate correctly
app.post('/webhooks/twilio/status', 
  twilio.webhook({ validate: true }), 
  (req, res) => {
    const status = req.body.MessageStatus;
    const messageSid = req.body.MessageSid;
    
    // Process status updates asynchronously to avoid blocking webhook delivery
    res.status(200).send('OK');
  }
);

Sätt alltid validate: true i produktionsmiljöer. Om du testar lokalt behöver du ett verktyg som ngrok för att exponera din lokala server och ge Twilio en giltig HTTPS-endpoint som matchar din publika signatur.

Så navigerar du Indiens DLT-regler och operatörskrav

TRAI, Indiens telekomtillsyn, kräver att varje kommersiellt SMS passerar ett Distributed Ledger Technology-system (DLT). Syftet är att stoppa spam. Bieffekten är en uppsättning hårda tekniska krav i samma sekund som du skickar till ett +91-nummer.

Två saker måste finnas på plats först. Ditt företag registrerar sig som Principal Entity (PE) och får ett PE ID. Sedan registreras och godkänns varje mall du tänker skicka på DLT-plattformen, som returnerar ett Content Template ID (CTID). Utelämna någotdera i din payload så släcker operatörernas brandväggar meddelandet direkt, och du betalar ändå för försöket.

Twilio bär DLT-data genom att mappa den till egna parametrar i Messaging API:ts payload. Du skickar med dem i options-objektet vid utskick.

// dlt-send.js
const twilioClient = require('./client');

async function sendIndianTransactionalSMS(toPhoneNumber, messageText, peId, templateId) {
  try {
    const message = await twilioClient.messages.create({
      to: toPhoneNumber,
      from: process.env.TWILIO_MESSAGING_SERVICE_SID, // Recommended for multi-sender setups
      body: messageText,
      // Twilio routes these custom parameters to Indian carriers for DLT validation
      riskCheck: 'disable', // Optional: bypass Twilio's internal fraud check if pre-validated
      sendAsMms: false,
      // Provide DLT metadata via custom headers or parameters supported by the regional gateway
      // Note: Ensure your Twilio account manager has enabled DLT parameter mapping for your SID
    });
    
    console.log(`Message queued successfully. SID: ${message.sid}`);
  } catch (error) {
    console.error(`DLT Dispatch failed: ${error.message}`);
  }
}

När ett DLT-filter hos operatören avvisar ett meddelande returnerar Twilio en specifik kod. Fel 30007 eller 30008 betyder nästan alltid att innehållet inte matchade din registrerade DLT-mall, eller att en variabel var formaterad annorlunda än den godkända versionen i registret.

Hantera webhook-samtidighet och rate limits

Att bearbeta leveranskvitton inline, direkt i Express-routen, är receptet på minnesläckor och trådsvält. Låt skrivlatensen mot databasen krypa uppåt under last så börjar webhookarna hopa sig i minnet. Event-loopen blockeras. Containern dör.

Koppla loss inmatningen från bearbetningen. Skjut in varje inkommande webhook direkt i en snabb kö i minnet, Redis via BullMQ, så svarar din Express-app 200 OK på under 5 millisekunder. Det mönstret är ryggraden i varje twilio sms javascript-tjänst som körs i kampanjskala.

// queue-ingest.js
const express = require('express');
const { Queue } = require('bullmq');
const IORedis = require('ioredis');

const connection = new IORedis(process.env.REDIS_URL);
const smsStatusQueue = new Queue('sms-status', { connection });

const app = express();
app.use(express.urlencoded({ extended: true })); // Twilio webhooks are Form-URLEncoded

app.post('/webhooks/twilio/status', async (req, res) => {
  try {
    // Immediately push payload to Redis queue
    await smsStatusQueue.add('status-update', {
      messageSid: req.body.MessageSid,
      status: req.body.MessageStatus,
      errorCode: req.body.ErrorCode,
      timestamp: new Date().toISOString()
    }, {
      attempts: 3,
      backoff: {
        type: 'exponential',
        delay: 1000
      }
    });

    // Acknowledge receipt to Twilio instantly
    res.status(200).send('<Response></Response>');
  } catch (err) {
    res.status(500).send('Internal Queue Failure');
  }
});

En dedikerad worker-pool tömmer kön vid sidan om. Din primära databas tar inte längre hela skrivtoppen under en stor aviseringskampanj.

// worker.js
const { Worker } = require('bullmq');
const IORedis = require('ioredis');

const connection = new IORedis(process.env.REDIS_URL);

const worker = new Worker('sms-status', async job => {
  const { messageSid, status, errorCode } = job.data;
  
  // Perform database updates or trigger retries here
  if (status === 'failed' || status === 'undelivered') {
    console.warn(`SMS `{messageSid} failed with code`{errorCode}. Executing retry logic.`);
    // Implement backoff retry or fallback carrier routing
  }
}, { connection });

worker.on('completed', job => {
  console.log(`Job ${job.id} processed successfully`);
});

Optimera resursanvändningen: minne och repostorlek

Node.js-processer lever kontinuerligt i minnet, inget som liknar PHP-FPM:s tillståndslösa nedmontering. Utmärkt för att hålla en beständig socketpool, farligt när du parsar stora payloads slarvigt. Mata in en gigantisk JSON-array från ett Twilio batch-API i ett rått JSON.parse och V8 svarar med toppar i skräpinsamlingen som drar med sig alla andra samtidiga uppgifter.

På serverless-runtimes som AWS Lambda eller Google Cloud Functions ska du inte importera hela Twilios SDK för ett jobb som bara skickar SMS. Hela paketet drar in modulerna för Voice, Video och Chat, och var och en av dem lägger på kallstartslatens.

Ett uppsvällt repo med gamla loggar, överdimensionerade lockfiles eller en databasdump som råkat checkas in bromsar CI/CD och gör containerbyggena tunga. Städa historiken med git-filter-repo innan du deployar.

# Remove a large legacy log file from the entire Git history
git filter-repo --path logs/production-sms.log --invert-paths

Vill du ha ett ännu lättare node_modules? Hoppa över Twilios SDK helt och anropa API:et direkt med en minimal klient som undici eller axios bakom en varm agent.

Övervakning, observability och felsökning

I stor skala behöver du strukturerade loggar för att följa ett meddelande mellan systemen. Släng console.log i produktion. Använd Winston eller Pino, skriv ut JSON och låt Datadog eller ELK indexera dina Message SID på egen hand.

const pino = require('pino');
const logger = pino({ level: 'info' });

logger.info({
  event: 'sms_dispatched',
  messageSid: 'SMxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  recipient: '+919999999999',
  provider: 'twilio'
});

Exponera Prometheus-mätvärden för observability i realtid. Tre är värda att följa:

  • sms_delivery_latency_seconds: tiden från utskick via API till mottagen webhook-status delivered.
  • sms_failure_total: räknare för misslyckanden segmenterade per felkod (till exempel 30007, 30008).
  • active_socket_connections: mätare för tillståndet i din HTTPS keep-alive-pool.

Rikta aldrig dina automatiserade tester mot Twilios skarpa API. Det bränner budget och förgiftar dina mätvärden. Twilios testuppgifter och magiska nummer låter dig simulera lyckade leveranser, ogiltiga nummer och operatörsavbrott utan att ett enda riktigt SMS skickas.

SMS blir bara mer reglerat, i USA och EU såväl som i de indiska korridorerna. Det svåra slutade vara API-anropet för länge sedan. Nu handlar det om regelefterlevnad och effektivitet i drift. En twilio sms javascript-pipeline byggd på connection pooling, köbaserade webhooks och explicit DLT-metadata levererar på millisekunder och lämnar din kärnapplikation orörd.

Vanliga frågor

Vad går sönder först när man skalar Twilio SMS i Node.js?
Webhook-hanteringen. Leveranskvitton kommer asynkront och i stora volymer, och en handler som gör riktigt arbete inline i stället för att köa hamnar efter under last.

Vad är DLT och när gäller det?
Indiens register byggt på Distributed Ledger Technology, som kräver att avsändare, headers och meddelandemallar registreras innan kommersiella SMS kan levereras. Det gäller indiska mottagarnummer oavsett var du skickar ifrån.

Varför levereras meddelanden utan att tas emot?
Oftast på grund av mallavvikelser under DLT eller filtrering på operatörsnivå. API:et rapporterar att gatewayen tagit emot meddelandet, inte att det kommit fram till telefonen.

Digvijay Singh Shekhawat
Digvijay Singh Shekhawat

Grundare, Finn AI

Digvijay bygger Finn – lagret för röstorkestrering för företag som resonerar sig genom samtal, extraherar data och uppdaterar dina system i realtid. Skriver om röst-AI, go-to-market och vad som krävs för att leverera autonoma agenter i stor skala.

Skala Twilio SMS i Node.js: DLT och webhooks — Finn