Send SMS
Send a single SMS message to a phone number.
Request Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
From | Mandatory | String | ExoPhone or approved Sender ID. If your account has no valid Sender ID matching the SMS body, the API returns HTTP 400 |
To | Mandatory | String | Recipient mobile number, preferably E.164 format |
Body | Mandatory | String | Message content, max 2000 characters |
EncodingType | Optional | String | plain (default, auto-detected if not set) or unicode |
ShortenUrl | Optional | Boolean | true to shorten URLs in the SMS body. Chargeable feature — must be enabled for your account. See URL Shortening |
StatusCallback | Optional | String | URL to receive delivery status notifications (form-encoded POST). Callback fields: SmsSid, To, Status, SmsUnits, DetailedStatus, DetailedStatusCode, DateSent, CustomField |
DltEntityId | Mandatory (India) | String | DLT entity registration ID |
DltTemplateId | Optional | String | DLT-approved template ID |
SmsType | Optional | String | transactional (OTP/Service Implicit), transactional_opt_in (Service Explicit), or promotional. If not set, Exotel looks up the matching template on the dashboard |
CustomField | Optional | String | Custom reference field (e.g., Order ID, Payment ID) |
Priority | Optional | String | normal (default) or high. Use high only for OTP/bank/delivery SMS — misuse may impact delivery |
BindingChainDetails | Optional | String | For Telemarketer Aggregators in India only. Comma-separated PE and TM details: <PE_ID>,<YOUR_TM_ID> |
DLT Compliance (Required for India)
DLT (Distributed Ledger Technology) is a regulatory requirement by TRAI for all SMS sent to Indian phone numbers. You must:
- Register as a business entity on a DLT portal (Jio, Airtel, Vodafone-Idea, or BSNL)
- Get your DLT Entity ID — pass this as
DltEntityIdin every SMS request - Register message templates — get approved template IDs to pass as
DltTemplateId - Register your Sender ID (header) — e.g., "EXOTEL", used in the
Fromparameter
Without DLT registration, SMS to Indian numbers will be blocked by telecom operators. See the SMS Overview for more details.
Code Examples
- cURL
- Python
- Node.js
- PHP
- Go
- Ruby
curl -u '<api_key>:<api_token>' -X POST https://api.exotel.com/v1/Accounts/<your_sid>/Sms/send \
-d "From=EXOTEL" \
-d "To=+919876543210" \
-d "Body=Your OTP is 123456" \
-d "DltEntityId=1234567890" \
-d "DltTemplateId=9876543210"
import requests
data = {
'From': 'EXOTEL',
'To': '+919876543210',
'Body': 'Your OTP is 123456',
'DltEntityId': '1234567890',
'DltTemplateId': '9876543210'
}
response = requests.post(
'https://api.exotel.com/v1/Accounts/<your_sid>/Sms/send',
auth=('<api_key>', '<api_token>'),
data=data
)
print(response.json())
const apiKey = '<your_api_key>';
const apiToken = '<your_api_token>';
const accountSid = '<your_sid>';
const url = `https://api.exotel.com/v1/Accounts/${accountSid}/Sms/send`;
const params = new URLSearchParams({
From: 'EXOTEL',
To: '+919876543210',
Body: 'Your OTP is 123456',
DltEntityId: '1234567890',
DltTemplateId: '9876543210',
});
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + Buffer.from(`${apiKey}:${apiToken}`).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params,
});
const data = await response.json();
console.log(data);
<?php
$data = array(
'From' => 'EXOTEL',
'To' => '+919876543210',
'Body' => 'Your OTP is 123456',
'DltEntityId' => '1234567890',
'DltTemplateId' => '9876543210'
);
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_USERPWD => "<api_key>:<api_token>",
CURLOPT_URL => 'https://api.exotel.com/v1/Accounts/<your_sid>/Sms/send',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($data),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func main() {
endpoint := "https://api.exotel.com/v1/Accounts/<your_sid>/Sms/send"
data := url.Values{}
data.Set("From", "EXOTEL")
data.Set("To", "+919876543210")
data.Set("Body", "Your OTP is 123456")
data.Set("DltEntityId", "1234567890")
data.Set("DltTemplateId", "9876543210")
req, _ := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode()))
req.SetBasicAuth("<api_key>", "<api_token>")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(body))
}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.exotel.com/v1/Accounts/<your_sid>/Sms/send')
request = Net::HTTP::Post.new(uri)
request.basic_auth('<api_key>', '<api_token>')
request.set_form_data(
'From' => 'EXOTEL',
'To' => '+919876543210',
'Body' => 'Your OTP is 123456',
'DltEntityId' => '1234567890',
'DltTemplateId' => '9876543210'
)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
Response
{
"SMSMessage": {
"Sid": "sms_sid_value",
"AccountSid": "your_sid",
"From": "EXOTEL",
"To": "+919876543210",
"Body": "Your OTP is 123456",
"Status": "queued",
"DateCreated": "2024-01-15 10:30:00",
"DateUpdated": "2024-01-15 10:30:00",
"DateSent": null,
"Price": null,
"Uri": "/v1/Accounts/your_sid/SMS/Messages/sms_sid_value"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
Sid | String | Unique SMS identifier |
AccountSid | String | Your Exotel account SID |
From | String | Format: <ExoPhone>/<SenderID> |
To | String | Recipient number |
Body | String | Message content |
Status | String | High-level status: queued, sending, submitted, sent, failed-dnd, failed |
DetailedStatusCode | String | Exotel's numeric status code — use for decision logic. See Status Codes |
DetailedStatus | String | Human-readable status string — log this for debugging |
Direction | String | outbound-api (REST API), outbound-call (during call), outbound-reply (reply to incoming), incoming |
SmsUnits | Integer | Number of SMS units consumed (null until final state) |
DateCreated | DateTime | When the request was received (YYYY-MM-DD HH:mm:ss) |
DateUpdated | DateTime | Last status update timestamp |
DateSent | DateTime | When the SMS was delivered to the recipient |
Price | Double | Amount charged in INR or USD (if applicable) |
Uri | String | Path to the SMS resource |
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Request accepted successfully |
| 400 | Bad request — missing or invalid parameters |
| 401 | Authentication failed |
| 403 | Forbidden — insufficient permissions |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
note
A 200 response means the SMS request was accepted, not that it was delivered. Use the StatusCallback webhook or the SMS Details endpoint to confirm delivery.
Error Responses
When a request fails, the API returns an error JSON with details:
400 — Missing required parameter:
{
"RestException": {
"Status": 400,
"Message": "The 'From' parameter is required."
}
}
401 — Authentication failed:
{
"RestException": {
"Status": 401,
"Message": "Authentication failed. Verify your API key and token."
}
}
429 — Rate limit exceeded:
{
"RestException": {
"Status": 429,
"Message": "Rate limit exceeded. Max 200 requests per minute."
}
}
For a complete list of error codes and troubleshooting steps, see the Error Code Dictionary.