Queue a bulk send
Stores an array of messages. Each item is one recipient and uses the same fields as POST /api/v2/messages/send. The response is 202 with status validating. Sending starts only after every row passes. messagesPerSecond is an integer from 1 to 10 and a ceiling shared by the account's jobs in creation order. There is no product item cap. Idempotency-Key is required and limited to 200 characters. The same key and the same name, messagesPerSecond, and messages return the current job even if JSON key order differs. The same key with a different body returns 409. Fields outside those three are ignored. scheduleAt is later rejected for the whole job. Duplicate recipients are allowed. One invalid row rejects the job and nothing is sent. An inactive subscription rejects the request before a job is created.
When to use it. Tens or thousands of different messages without waiting for delivery inside the request. For one message, or up to 100 recipients with the same text, use POST /api/v2/messages/send.
Code example
Correct: Correct: two messages
curl --location 'https://raselsms.com/api/v2/messages/bulk' \--header 'X-API-Key: YOUR_API_KEY' \--header 'Content-Type: application/json' \--header 'Idempotency-Key: bulk-example-001' \--data '{"name":"Morning notices","messagesPerSecond":10,"messages":[{"to":"+963912345678","channel":"local_sms","messageType":"free_text","content":{"text":"Example notice from Rasel."}},{"to":"+963987654321","channel":"local_sms","messageType":"free_text","content":{"text":"Example notice from Rasel."}}]}'The key is present, the rate is inside the ceiling, and the array is not empty.
Incorrect: Incorrect: missing key
curl --location 'https://raselsms.com/api/v2/messages/bulk' \--header 'X-API-Key: YOUR_API_KEY' \--header 'Content-Type: application/json' \--data '{"messagesPerSecond":10,"messages":[{"to":"+963912345678","channel":"local_sms","messageType":"free_text","content":{"text":"Example notice from Rasel."}}]}'Idempotency-Key is required.
Incorrect: Incorrect: rate above the ceiling
curl --location 'https://raselsms.com/api/v2/messages/bulk' \--header 'X-API-Key: YOUR_API_KEY' \--header 'Content-Type: application/json' \--header 'Idempotency-Key: bulk-example-002' \--data '{"messagesPerSecond":11,"messages":[{"to":"+963912345678","channel":"local_sms","messageType":"free_text","content":{"text":"Example notice from Rasel."}}]}'The value is not lowered automatically.
Incorrect: Incorrect: same key, different body
curl --location 'https://raselsms.com/api/v2/messages/bulk' \--header 'X-API-Key: YOUR_API_KEY' \--header 'Content-Type: application/json' \--header 'Idempotency-Key: bulk-example-001' \--data '{"messagesPerSecond":5,"messages":[{"to":"+963912345678","channel":"local_sms","messageType":"free_text","content":{"text":"Example notice from Rasel."}}]}'Response example
202 — Correct: two messages
{ "success": true, "id": "64b0c0ff0000000000000001", "name": "Morning notices", "status": "validating", "messagesPerSecond": 10, "pauseReason": null, "total": 2, "validatedCount": 0, "counts": { "pending": 0, "sent": 0, "failed": 0, "cancelled": 0 }, "errorCode": null, "errorMessage": null, "createdAt": "2026-09-24T12:00:00.000Z"}The key is present, the rate is inside the ceiling, and the array is not empty.
400 — Incorrect: missing key
{ "success": false, "code": "IDEMPOTENCY_KEY_REQUIRED", "error": "Idempotency-Key is required"}Idempotency-Key is required.
400 — Incorrect: rate above the ceiling
{ "success": false, "code": "MESSAGES_PER_SECOND_INVALID", "error": "messagesPerSecond must be an integer from 1 to 10"}The value is not lowered automatically.
409 — Incorrect: same key, different body
{ "success": false, "code": "IDEMPOTENCY_CONFLICT", "error": "Idempotency key was used with a different body"}400 — Invalid body, rate, or key. Codes: IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALID, INVALID_REQUEST_BODY, MESSAGES_PER_SECOND_INVALID, MESSAGES_REQUIRED, INVALID_MESSAGE. Code MESSAGES_REQUIRED.
{ "success": false, "code": "MESSAGES_REQUIRED", "error": "messages must be a non-empty array"}403 — Subscription is inactive before any job is stored. 403 can also be FORBIDDEN when permission is missing. Code SUBSCRIPTION_INACTIVE.
{ "success": false, "code": "SUBSCRIPTION_INACTIVE", "error": "Subscription is inactive"}401 — Missing or invalid API key.
{ "success": false, "requestId": "msgreq_1739012345678_abc123", "code": "UNAUTHORIZED", "error": "Missing or invalid API key"}403 — Phone is not verified or messages:create is missing. Code FORBIDDEN.
{ "success": false, "code": "FORBIDDEN", "error": "Phone not verified or missing permission"}429 — Rate limit exceeded. Honor Retry-After when present.
{ "success": false, "requestId": "msgreq_1739012345678_abc123", "code": "RATE_LIMITED", "error": "Rate limit exceeded", "retryAfterSeconds": 30}HTTP request
POST /api/v2/messages/bulk
Request Headers
| Header | Type | Description |
|---|---|---|
| X-API-Key | string | Your Rasel API key. Send it on every documented request except inbound webhooks. * |
| Idempotency-Key | string | Optional unique key for safe retries. Takes precedence over a body idempotencyKey. Successful and failed responses can be replayed for 24 hours when send idempotency is enabled. * |
Request body
| Field | Type | Description | Example |
|---|---|---|---|
| name | string | Display name or lowercase template identifier, depending on the endpoint. | |
| messagesPerSecond | integer | Integer from 1 to 10. Zero, fractions, and values above 10 reject the request. * | |
| messages | json | Non-empty array. Each item is one recipient object: to, channel, messageType, content, sender, template, and attachment under the single-send rules. Do not put an array in to, and do not send scheduleAt. * |
Status Codes
| Status | Description |
|---|---|
| 202 | Accepted and queued. |
| 400 | Invalid request, validation error, or the resource cannot be used in this state. |
| 401 | Missing or invalid API key. |
| 403 | Phone not verified, missing permission, or the account cannot perform this action. |
| 409 | Conflict, such as an idempotency key reuse with a different body, or a session that is not ready. |
| 429 | Rate limit exceeded, or the maximum number of verification attempts was reached. |
Cases to handle
- accepting: storage is not finished and nothing is sent. If storage stops, the job becomes rejected with INTAKE_INCOMPLETE.
- validating: every row is checked. validatedCount shows progress. Nothing is sent in this state.
- rejected: nothing was sent. errorCode is VALIDATION_FAILED, INSUFFICIENT_BALANCE, or INTAKE_INCOMPLETE.
- queued: accepted and waiting for a send slot. running: sending is in progress.
- paused: sending stopped. pauseReason is insufficient_balance, provider_rate_limit, or user.
- completed: the job finished even when some rows failed. Counts stay separate.
- cancelled: rows that were not sent became cancelled. The current window may finish a few sends after pause or cancel is requested.
Try it
This request may consume wallet balance or plan quota.
Parameters
Optional unique key for safe retries. Takes precedence over a body idempotencyKey. Successful and failed responses can be replayed for 24 hours when send idempotency is enabled.
Display name or lowercase template identifier, depending on the endpoint.
Integer from 1 to 10. Zero, fractions, and values above 10 reject the request.
Non-empty array. Each item is one recipient object: to, channel, messageType, content, sender, template, and attachment under the single-send rules. Do not put an array in to, and do not send scheduleAt.
Request
{ "name": "Morning notices", "messagesPerSecond": 10, "messages": [ { "to": "+963912345678", "channel": "local_sms", "messageType": "free_text", "content": { "text": "Example notice from Rasel." } }, { "to": "+963987654321", "channel": "local_sms", "messageType": "free_text", "content": { "text": "Example notice from Rasel." } } ]}Response
Send a request to see the response.