Send Bulk SMS
Send the same SMS message to many recipients in one request
Endpoint
POST /api/sms/messages/bulkRequest body
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Non-empty message content, at most 1,600 characters. Longer messages are billed as multiple SMS parts — see Send Message |
sender | string | Yes | Sender ID (max 11 alphanumeric chars) |
payment_type | string | Yes | wallet or subscription |
receivers | array | Yes | Non-empty array of at most 5,000 phone numbers, each matching a provider pattern configured in the platform. A longer array returns 422 before any number is validated |
expires_after | integer | No | Seconds this message stays worth delivering, 60–604800. Defaults to 24 hours. Applies to every recipient in the batch; ones still waiting to be sent then are marked cancelled and refunded — see Message validity |
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer YOUR_API_TOKEN |
Content-Type | Yes | application/json |
Accept | Yes | application/json |
Idempotency-Key | No | Replaying the same key with the same body returns the original result instead of sending again |
Example
curl -X POST https://sms.lamah.com/api/sms/messages/bulk \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"message": "Your order has been shipped.",
"sender": "MyShop",
"payment_type": "wallet",
"receivers": [
"00218912345678",
"00218922654321",
"00218944678901"
]
}'Response
{
"message_id": "b9e8a1f2-4c60-4b0b-9f51-0c4f2f6c9e7a",
"cost": 3,
"details": {
"sent": 3,
"total": 3,
"skipped_unavailable": 0
}
}| Field | Description |
|---|---|
message_id | UUID for this batch — use it with Get Message |
cost | Total SMS parts charged: parts per message × accepted recipients |
details.sent | Recipients accepted for sending, after removing duplicates and numbers whose network the sender ID is not registered on |
details.total | Number of entries you submitted in receivers |
details.skipped_unavailable | Recipients dropped because their network operator is temporarily unreachable — a subset of the gap between sent and total |
details.sent can be lower than details.total. Numbers that are duplicates, or that belong to a network your sender ID is not registered on, are dropped silently — they are not itemised in the response. Compare the two values to detect partial acceptance.
A non-zero skipped_unavailable is retryable, unlike the rest of the gap. Those numbers were valid and reachable by your sender ID; the operator was simply down at the time. Re-send them once it is back — with a new Idempotency-Key, since the original key is bound to the batch that already went out.
A 200 means the batch was accepted and your balance reserved, not that the messages have reached the network. Poll Get Message with the returned message_id for per-recipient delivery status.
Error responses
422 Unprocessable Entity — Validation failed
Validation messages are in Arabic. An invalid number is reported by its position in receivers:
{
"message": "يجب أن يكون receivers.0 صالحًا في مزودي النظام.",
"errors": {
"receivers.0": ["يجب أن يكون receivers.0 صالحًا في مزودي النظام."]
}
}More than 5,000 entries in receivers:
{
"message": "يجب ألا يحتوي الحقل receivers على أكثر من 5000 عناصر.",
"errors": {
"receivers": ["يجب ألا يحتوي الحقل receivers على أكثر من 5000 عناصر."]
}
}404 Not Found — Sender not found
{ "message": "Sender not found" }400 Bad Request — No compatible recipients
Returned when none of the recipients belong to a network the sender ID is registered on, and none were dropped for operator unavailability.
{ "message": "The sender and receivers are not compatible." }400 Bad Request — Insufficient balance
{ "message": "Insufficient balance" }401 Unauthorized
{ "message": "Invalid or missing token." }409 Conflict — Idempotency key reused
{ "message": "Idempotency key is already in use" }503 Service Unavailable — Every recipient unreachable
Returned when the batch is left with nothing to send and at least one recipient was dropped because its network operator is temporarily unreachable. Nothing is charged. Retry the whole batch once the operator is back.
{ "message": "The provider for these receivers is temporarily unavailable" }A batch that still has at least one reachable recipient returns 200 instead, with the dropped ones counted in details.skipped_unavailable.
503 Service Unavailable — Ingest unavailable
{ "message": "SMS ingest unavailable" }Batching large lists
receivers takes at most 5,000 numbers per request. Split longer lists into batches. Smaller batches also keep each request fast:
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
arr.slice(i * size, i * size + size)
);
for (const [i, batch] of chunk(allReceivers, 1000).entries()) {
await sendBulk({ ...payload, receivers: batch }, { idempotencyKey: `campaign-42-batch-${i}` });
}If your company is not yet verified, receivers is replaced with your company's registered phone number alone. The request still returns 200 and still consumes balance.