Send Bulk SMS
Send one approved SMS template to many numbers with a single request, each with its own variables. Up to 1,000 numbers per request.
We check the whole list first, then send in the background. You get a bulk_id straight away and can follow progress with SMS status, or get each result on your server with delivery reports.
Any approved template works here, whatever its type: promotional, transactional, service implicit or service explicit. The type, sender ID and price come from the template.
Note: SMS must be Active on your account and the template must be Approved. See Set up SMS and SMS templates.
Endpoint
https://api.splashifypro.com/api/v1/public/sms/send-bulk
Headers
Request body
{
"dlt_template_id": "1107160000000000002",
"name": "Order alerts 26 Sep",
"messages": [
{ "to": "+919876543210", "variables": ["Aarav", "AB-1042"] },
{ "to": "+919876543211", "variables": ["Diya", "AB-1043"] },
{ "to": "+919876543212", "variables": ["Kabir", "AB-1044"] }
]
}What we check before sending
Nothing is sent until the whole request passes these checks:
- The template can send. It is approved, its sender ID is approved, and your DLT chain is verified.
- Every row has the right variables. A row with too many or too few, or with a value that is empty or over 30 characters, is refused with
400andcode: "invalid_variables". Themessageandrowname the first bad row. Nothing is queued. - Every number is an Indian mobile. Numbers that are not are skipped and counted in
skipped.invalid. - Duplicates are sent once. The first row with a number is sent. Later rows with the same number count in
skipped.duplicate. - Opted-out numbers are skipped. People who asked not to get your messages count in
skipped.opted_outand are not charged. - Your wallet covers the estimate. This is checked on the numbers left after opted-out ones are taken out, so your balance only needs to cover the messages that will go. If it does not, the request is refused with
402and nothing is queued. - Something is left to send. If every row was skipped, the request is refused with
400andcode: "no_recipients".
Every skipped row is listed in skipped_rows, so you can fix your data.
Examples
cURL
curl -X POST https://api.splashifypro.com/api/v1/public/sms/send-bulk \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-alerts-26-sep" \
-d '{
"dlt_template_id": "1107160000000000002",
"name": "Order alerts 26 Sep",
"messages": [
{ "to": "+919876543210", "variables": ["Aarav", "AB-1042"] },
{ "to": "+919876543211", "variables": ["Diya", "AB-1043"] }
]
}'Node.js / TypeScript
type Row = { to: string; variables: string[] };
// idempotencyKey: make it once for this list, and send the same one on every retry.
async function sendOrderAlerts(rows: Row[], idempotencyKey: string) {
const res = await fetch("https://api.splashifypro.com/api/v1/public/sms/send-bulk", {
method: "POST",
headers: {
"Authorization": `Basic ${process.env.SPLASHIFY_API_KEY!}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
dlt_template_id: "1107160000000000002",
name: "Order alerts 26 Sep",
messages: rows, // up to 1,000
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.code ?? ""}: ${data.message ?? ""}`);
return data.bulk_id; // follow it with GET /api/v1/public/sms/bulk/:bulk_id
}Python
import os, requests
# idempotency_key: make it once for this list, and send the same one on every retry.
def send_order_alerts(rows: list[dict], idempotency_key: str) -> str:
r = requests.post(
"https://api.splashifypro.com/api/v1/public/sms/send-bulk",
headers={
"Authorization": f"Basic {os.environ['SPLASHIFY_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json={
"dlt_template_id": "1107160000000000002",
"name": "Order alerts 26 Sep",
"messages": rows, # up to 1,000
},
timeout=60,
)
data = r.json()
if r.status_code != 200:
raise RuntimeError(f"{r.status_code} {data.get('code')}: {data.get('message')}")
return data["bulk_id"]For more than 1,000 numbers, split the list and send one request per 1,000.
Successful response (200 OK)
{
"success": true,
"bulk_id": "9d2f4b61-1c7e-4a35-8f90-6b3e2d7c5a18",
"name": "Order alerts 26 Sep",
"queued": 2,
"skipped": { "opted_out": 1, "invalid": 0, "duplicate": 0 },
"skipped_rows": [
{ "row": 3, "to": "+919876543212", "reason": "opted_out" }
],
"estimated_parts": 2,
"estimated_cost": 0.32,
"status": "queued"
}Retry safely
A bulk send can take a while to check. If your request times out or you get a 5xx, you cannot tell whether the list was queued. Send an Idempotency-Key header, and you can simply send the same request again with the same key:
- If the first request went through, you get its answer back: the same
bulk_id, the send'sstatusas it is now, and"replayed": true. The response also has the headerIdempotency-Replayed: true. Nothing is sent or charged again. - If the first request failed, the key is not kept, and the retry is a normal send.
- If the first request is still running, you get
409withcode: "idempotency_in_progress". Wait a few seconds and try again with the same key. - The same key with a different body gets
422withcode: "idempotency_key_reused". Use a new key for a new list.
A key is kept for 24 hours. Use one key per list you decide to send, such as order-alerts-26-sep, and reuse it on every retry of that list. See Idempotency.
Errors
Errors look like this. Switch on code, and show message to a person:
{
"success": false,
"code": "invalid_variables",
"message": "Row 2: this template needs 2 variable(s) and 1 were given.",
"row": 2
}A 402 also tells you the estimate, and for insufficient_balance your balance. The estimate leaves out the opted-out numbers we found. When your balance cannot cover the list, we stop checking for opt-outs as soon as that is clear, or do not check at all when it cannot cover one message, so the estimate can include numbers that opted out:
{
"success": false,
"code": "insufficient_balance",
"message": "Your wallet balance is too low for this send. Nothing was queued. Add funds and try again.",
"estimated_parts": 1000,
"estimated_cost": 160,
"balance": 42.5
}The checks every Public API request goes through answer the same way as on Send SMS: 401 for a missing or invalid API key, 403 when your plan does not include the Public API, your IP is not on the allowlist or your account is suspended, and 429 over your plan's rate limit. One bulk request counts as one request toward that limit.
Rate limits
Bulk sends have their own limit: 10 a minute for your account. It counts only bulk sends, and the 60 a minute limit on single sends does not apply here. Over it, you get 429 with a Retry-After header and this body:
{
"success": false,
"code": "rate_limited",
"error": "rate_limit_exceeded",
"message": "Too many bulk sends. You can make 10 a minute. Wait a moment and try again.",
"retry_after": 42,
"limit": 10
}Nothing is queued by a request that gets 429. To send more numbers, put up to 1,000 in each request.
Good to know
- Send an
Idempotency-Key. Without one, a second identical request is a second send, and everyone on it gets the SMS twice. With one, a retry gets the first answer back. See Retry safely. - Promotional SMS do not reach numbers on Do Not Disturb. Use a transactional or service template for messages people asked for.
- Opted-out numbers are never charged. They are left out before anything is sent.