SMS

Bulk SMS API Integration in India (2026): Build Fast, Stay DLT-Compliant

10 min read

There's a moment every developer building on SMS in India hits — usually around the time they're trying to debug a failed OTP delivery at 11pm before a production launch. The message left the system. The DLR says delivered. But the user never got it.

bulk-sms-api

Understanding why that happens — and building a system that prevents it — is what this guide is actually about. Not just how to make an API call, but how to build an SMS integration that stays reliable at production scale, handles India's DLT compliance correctly, and gives you the delivery visibility to fix problems before users notice them.

What a Bulk SMS API Actually Does

Think of the SMS API as the layer between your application logic and the telecom operator's network. When a user clicks "Send OTP," your backend doesn't talk directly to Jio or Airtel. It talks to your SMS provider's API, which handles route selection, DLT template validation, operator handoff, and delivery receipt collection — all before returning a response to your system.

From a code perspective, you're making an HTTP POST request with a JSON payload. What the API does with it is where the complexity lives:

Route selection — based on messageType, the provider routes your message to the correct operator path. OTP (messageType 3) gets a dedicated high-priority route. Mixing OTP and transactional traffic on the same route is a common mistake that affects OTP delivery latency during congestion.

DLT validation — before the message reaches any operator, its content is checked against your registered DLT template. A mismatch here causes silent rejection. The API returns success — the message left your system — but nothing reaches the device. This is the most common cause of "delivered but not received" complaints in Indian SMS systems.

Operator handoff — validated messages are passed to the telecom operator's SMSC via SMPP protocol. What happens from here is outside your provider's direct control, which is why route quality and direct operator connectivity matters — Tier-1 providers have better visibility and recovery when operator-side issues occur.

DLR collection — the operator sends a delivery confirmation back to your provider, which stores it against the MessageId and makes it available via webhook or polling.

The DLT Requirement: What Developers Actually Need to Pass

Every commercial SMS sent in India requires three pre-registered DLT identifiers passed in your API request. This isn't optional — missing these fields causes operator-level rejection.

dltEntityId — your registered business entity ID on the DLT portal. This is a one-time registration. Every message you send must carry this ID to identify your business.

dltEntityTemplateId — the ID of the specific pre-approved template being used. Every unique message format needs its own registered template. The actual message content must match the registered template exactly — variable fields (OTP codes, order numbers, names) are the only permitted deviation.

sourceAddress — your registered sender ID (header). Must match the sender ID registered against the template on the DLT portal.

Understanding how DLT registration works in India — entity registration, sender ID approval, template submission — is essential before you write your first line of integration code. The API call is simple. The compliance setup before it is where most teams get stuck.

Sending an SMS: Real API Reference

Here is MessageBot's actual API for sending an SMS in India.

POST Request

POST http://papi.messagebot.in/SendSmsV2
Content-Type: application/json
{

  "apiToken": "YOUR_TOKEN",

  "messageType": "3",

  "messageEncoding": "1",

  "destinationAddress": "917006822667",

  "sourceAddress": "MBOTIN",

  "messageText": "Your OTP is 849201. Valid for 10 minutes. Do not share with anyone.",

  "dltEntityId": "YOUR_DLT_ENTITY_ID",

  "dltEntityTemplateId": "YOUR_DLT_TEMPLATE_ID",

  "userReferenceId": "SESSION_REF_001"

}

Key parameters:

  • messageType: 3 = OTP route (dedicated high-priority). Use 2 for transactional, 1 for promotional

  • messageEncoding: 1 = standard ASCII (160 chars/part). Use 2 for Unicode/regional languages (70 chars/part)

  • userReferenceId = your internal session or transaction ID — store this alongside the MessageId for reconciliation

  • dltEntityId and dltEntityTemplateId = mandatory for India

Successful response:

{
  "MessageId": 3195701,

  "OperationCode": 0,

  "Status": "Success",

  "DlrStatus": null,

  "UserReferenceId": "SESSION_REF_001",

  "DestinationAddress": "917006822667",

  "Remarks": "Message Submitted"
}

Store MessageId immediately — you need it to poll delivery status. DlrStatus is null at submission time; it populates after operator confirmation.

GET Request (Alternative)

GET http://papi.messagebot.in/SendSmsV2?apiToken=YOUR_TOKEN&messageType=3&messageEncoding=1&destinationAddress=917006822667&sourceAddress=MBOTIN&messageText=Your+OTP+is+849201&dltEntityId=YOUR_DLT_ENTITY_ID&dltEntityTemplateId=YOUR_DLT_TEMPLATE_ID&userReferenceId=SESSION_REF_001

POST is preferred for production — GET requests with sensitive parameters in the URL create logging exposure risks.

Code Examples: PHP, Python, Node.js

PHP

<?php

$payload = [

    'apiToken'              => 'YOUR_TOKEN',

    'messageType'           => '3',

    'messageEncoding'       => '1',

    'destinationAddress'    => '917006822667',

    'sourceAddress'         => 'MBOTIN',

    'messageText'           => 'Your OTP is 849201. Valid for 10 minutes.',

    'dltEntityId'           => 'YOUR_DLT_ENTITY_ID',

    'dltEntityTemplateId'   => 'YOUR_DLT_TEMPLATE_ID',

    'userReferenceId'       => 'SESSION_REF_001'
];

$ch = curl_init('http://papi.messagebot.in/SendSmsV2');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_POST, true);

curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));

curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

$response = curl_exec($ch);

$data = json_decode($response, true);

// Store MessageId for DLR lookup
$messageId = $data['MessageId'];
?>

Python

import requests

payload = {

    'apiToken': 'YOUR_TOKEN',

    'messageType': '3',

    'messageEncoding': '1',

    'destinationAddress': '917006822667',

    'sourceAddress': 'MBOTIN',

    'messageText': 'Your OTP is 849201. Valid for 10 minutes.',

    'dltEntityId': 'YOUR_DLT_ENTITY_ID',

    'dltEntityTemplateId': 'YOUR_DLT_TEMPLATE_ID',

    'userReferenceId': 'SESSION_REF_001'

}

response = requests.post(

    'http://papi.messagebot.in/SendSmsV2',

    json=payload,

    headers={'Content-Type': 'application/json'}

)

data = response.json()

message_id = data['MessageId']  # Store this for DLR lookup

Node.js

const axios = require('axios');

const payload = {

  apiToken: 'YOUR_TOKEN',

  messageType: '3',

  messageEncoding: '1',

  destinationAddress: '917006822667',

  sourceAddress: 'MBOTIN',

  messageText: 'Your OTP is 849201. Valid for 10 minutes.',

  dltEntityId: 'YOUR_DLT_ENTITY_ID',

  dltEntityTemplateId: 'YOUR_DLT_TEMPLATE_ID',

  userReferenceId: 'SESSION_REF_001'

};

axios.post('http://papi.messagebot.in/SendSmsV2', payload)

  .then(res => {

    const messageId = res.data.MessageId; // Store for DLR lookup

    console.log('Sent:', messageId);

  })

  .catch(err => console.error('Send failed:', err.message));

Checking Delivery Status (DLR)

After sending, poll for actual delivery confirmation using the MessageId:

POST http://papi.messagebot.in/Dlr/GetDetails

Content-Type: application/json

{

  "apiToken": "YOUR_TOKEN",

  "messageId": 3195701

}

DLR response:

{

  "IsSuccess": true,

  "ErrorCode": 0,

  "ErrorDescription": "OK",

  "ReturnData": [{

    "MessageId": 3195701,

    "DestinationAddress": "917006822667",

    "MessageType": "OTP",

    "DlrStatus": "Delivered",

    "ErrorCode": 1,

    "ErrorDescription": "No Error",

    "SentDateTime": "2026-06-29T10:22:04",

    "CustomerCost": 0.001

  }]

}

DlrStatus: "Delivered" is operator-confirmed delivery — not just that the message left your provider's system. This distinction matters significantly for OTP retry logic: triggering a resend before this status is confirmed can result in users receiving duplicate OTPs.

Polling vs webhooks: For low-volume sends, polling the DLR endpoint after 5–10 seconds works fine. For high-volume OTP flows where you need to trigger fallback quickly on failures, webhook-based callbacks are more reliable — configure your provider to POST delivery updates to your endpoint rather than polling.

Building Reliable OTP Flows

OTP delivery has different reliability requirements from bulk campaigns. A promotional message arriving 2 minutes late is acceptable. An OTP arriving 30 seconds late during a payment confirmation is a failed transaction.

Generate OTPs server-side only. Never generate or expose OTP values in client-side code. Use a cryptographically random function — not sequential or predictable.

Store in cache with TTL, not in your database. Redis or Memcached with a TTL matching your OTP expiry is the correct pattern. Storing OTPs in a relational database introduces unnecessary latency and persistence risk.

Set expiry that balances security and delivery time. 60 seconds is too short given that SMS delivery on Indian telecom networks can vary during peak periods. 3–10 minutes is the standard range — shorter for high-security transaction confirmation, longer for account verification flows.

Implement retry limits. Maximum 3 OTP requests per number per 10 minutes, with exponential backoff after failed verification attempts. Without rate limiting, your OTP endpoint becomes a cost vector for bots.

Build multi-channel fallback. If SMS delivery isn't confirmed within 30 seconds, automatically trigger WhatsApp OTP as the next attempt, then Voice OTP as the final layer. A single-channel OTP system will always have a failure rate that a multi-channel system can eliminate.

Never log OTP values. Treat OTPs like passwords — hash if stored, never log in plain text, purge immediately after verification or expiry.

Understanding SMS DLR Status Codes

Not all delivery failures are the same. The DLR status tells you why a message failed, which determines the right response:

DlrStatus

Meaning

Action

Delivered

Confirmed at device

No action needed

Undelivered

Reached operator, not device

Retry on alternate route or switch channel

Expired

TTL exceeded before delivery

Trigger resend — likely operator congestion

Rejected

Operator rejected — often DLT mismatch

Check template registration, don't retry same message

Unknown

Status not yet available

Poll again in 30 seconds

Understanding how SMS DLR works in India — and what each status code actually means at the operator level — helps you build smarter retry logic rather than blanket resend-on-failure.

Sending to Multiple Recipients

For bulk sends — campaigns, notifications, alerts — use the same endpoint but structure your backend to send in batches rather than sequential single sends.

{

  "apiToken": "YOUR_TOKEN",

  "messageType": "2",

  "messageEncoding": "1",

  "destinationAddress": "917006822667,919876543210,918765432109",

  "sourceAddress": "MBOTIN",

  "messageText": "Your order has been shipped. Track at: example.com/track",

  "dltEntityId": "YOUR_DLT_ENTITY_ID",

  "dltEntityTemplateId": "YOUR_DLT_TEMPLATE_ID"

}

For campaign-level reporting across large sends, use the campaign report endpoint:

POST http://papi.messagebot.in/Dlr/GetCampaignDetail

{

  "apiToken": "YOUR_TOKEN",

  "campaignId": 16139,

  "dlrStatus": "NULL",

  "startDate": "2026-06-29T09:00:00"

}

This returns per-message delivery status across the entire campaign — useful for post-send analysis and identifying operator-specific delivery issues.

Checking Approved Sender IDs

Before sending, verify your registered sender IDs programmatically:

POST http://papi.messagebot.in/sender/List

{

  "apiToken": "YOUR_TOKEN"

}

Response includes approval status, message type, and country for each registered sender. Integrating this check into your deployment pipeline prevents the common mistake of sending on a sender ID that's still pending approval.

Production Checklist Before Go-Live

Before your SMS integration goes live, confirm:

  • API token stored in environment variables — never in source code or version control

  • DLT entity, sender ID, and all templates registered and approved

  • messageType correctly set per message category — 3 for OTP, 2 for transactional, 1 for promotional

  • dltEntityId and dltEntityTemplateId passed on every request

  • MessageId stored against every send for DLR reconciliation

  • OTPs stored in Redis/Memcached with TTL — not in relational database

  • Rate limiting on OTP endpoint — max 3 requests per number per 10 minutes

  • Retry logic with exponential backoff — not immediate blanket retry

  • Multi-channel fallback configured for OTP flows

  • Webhook or polling set up for DLR callbacks

  • Error logging capturing OperationCode and Remarks on non-zero responses

  • Load tested at your expected peak OTP volume

Frequently Asked Questions

What is a Bulk SMS API in India?
A Bulk SMS API is an HTTP interface that lets your application send SMS messages programmatically to Indian telecom networks. In India, every API request must include DLT-registered entity ID, sender ID, and template ID — these are mandatory fields enforced at the operator level, not just by the provider.

What is messageType 3 in MessageBot's API?
messageType: 3 designates the OTP route — a dedicated high-priority path separate from standard transactional (2) and promotional (1) routes. OTP messages on this route receive priority processing and are not affected by throughput limits that apply to bulk transactional sends.

Why does my SMS show as sent but the user didn't receive it?
The most common causes in India: DLT template mismatch (message content doesn't exactly match the registered template), grey route routing causing silent drops, or device-level filtering on certain Android brands. Poll the DLR endpoint for the specific DlrStatus and ErrorDescription — these tell you exactly where the failure occurred.

Is GET or POST better for SMS API requests?
POST with JSON payload. GET requests embed sensitive parameters (API token, message content) in the URL — these are logged by web servers, proxies, and browser history. POST keeps credentials and message content in the request body.

How do I handle OTP retry logic?
Wait for DLR confirmation before triggering a retry. If DlrStatus is Expired or Undelivered after 30 seconds, retry on an alternate operator route. If a second attempt also fails, trigger WhatsApp OTP as fallback. Never retry a Rejected status without first verifying your DLT template — repeated rejected sends waste credits and may flag your sender ID.

Conclusion

A well-built SMS API integration is invisible to users — they just get their OTP in 3 seconds and move on. That invisibility is the goal. It requires getting the DLT compliance right before the first send, choosing the right SMS provider with Tier-1 routing and operator-side DLRs, and building retry logic that responds to specific failure reasons rather than blanket resending.

The integration itself is an afternoon's work for an experienced developer. The reliability that makes it production-grade comes from understanding what happens between your API call and the message arriving on a device — and building defensively around every failure point in that chain.

MessageBot's SMS API is available for Indian businesses with full DLT parameter support, dedicated OTP routing, and webhook-based delivery callbacks. 


Share

Ready to Transform Your Business Communication?

Join thousands of Indian businesses already using MessageBot to engage customers effectively and drive business growth.

Free 100 SMS credits
24/7 support
TRAI compliant setup
No setup fees