Bulk Messaging
Overview
Bulk Messaging is a two-step workflow for sending many SMS rows with the same approved template and sender selection.
Upload first. FutureSMS parses the CSV, renders each row against the selected template, and stores valid rows as pending bulk items. Execute second. FutureSMS creates SMS orders only for rows that are still valid at send time.
This split is intentional: it lets you find file-format problems before messages are sent, and it keeps row-level failures visible after execution.
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/sms/bulk-jobs | Upload and itemize a CSV file. |
POST | /api/v1/sms/bulk-jobs/{job_id}/executions | Execute accepted rows. |
GET | /api/v1/sms/bulk-jobs/{job_id} | Read job-level counts and status. |
GET | /api/v1/sms/bulk-jobs/{job_id}/items | Read row-level outcomes. |
Authentication: Bearer access token.
Before You Begin
You need an authenticated merchant API user, an approved template, an authorized sender or enabled auto routing, and a UTF-8 CSV file with recipient rows.
Upload and execution are separate. Store the returned bulk_job_id after upload, then use it to execute the job or inspect row-level results.
Request fields
File Format
The upload file must be a UTF-8 CSV file with a .csv filename. UTF-8 with BOM is accepted.
phone_number is required. var is optional and is used only when the selected template needs a variable.
phone_number,var
+639171230001,Alice
+639171230002,Bob
Only these columns are supported:
| Column | Required | Rules |
|---|---|---|
phone_number | Yes | E.164 phone number, for example +639171234567. |
var | Depends on template | Optional template value. For {{var}} templates, max 24 characters. |
Extra columns are allowed only when they are empty. If an unsupported column has a value, that row is rejected with an error like unsupported columns present: note.
Template Rules
The upload request chooses one template with the multipart form field template_id. The CSV only supplies phone_number and optional var; it cannot choose a different template per row.
| Template content | CSV var behavior |
|---|---|
Welcome to FutureSMS, {{var}} | var is required and replaces {{var}}. |
{{free}} | var is required and becomes the full message body. |
Service notice from FutureSMS | var must be empty. |
Rendered content must fit in one SMS: GSM-7 content can use up to 160 units, and Unicode content can use up to 70 characters.
Upload Request
POST /api/v1/sms/bulk-jobs uses multipart/form-data.
| Field | Type | Required | Meaning |
|---|---|---|---|
template_id | string | Yes | Approved template logical ID, such as TPL-3644. |
sender_id | string | Yes | Authorized sender logical ID, such as SID-3425, or AUTO when FutureSMS has enabled auto routing for your merchant. |
file | file | Yes | UTF-8 CSV file. |
{
"template_id": "TPL-3644",
"sender_id": "AUTO",
"file": "bulk-upload.csv"
}
Execute Request
POST /api/v1/sms/bulk-jobs/{job_id}/executions starts execution for an items_ready job.
Execution does not just replay upload validation. Each pending row goes through send-time checks again, including account state, approved template, sender or auto-routing availability, destination policy, order creation, and dispatch registration.
Rows that pass become filled and receive an order_id. Rows that fail become rejected with an error_message.
Item Filters
GET /api/v1/sms/bulk-jobs/{job_id}/items supports row-level filters.
| Query parameter | Type | Example |
|---|---|---|
bulk_job_item_id | string | BKI-5519 |
row_no | integer | 1 |
order_id | string | SDPS26032510301200003MC |
bulk_item_status | enum | filled |
phone_number | string | +63917105134 |
error_message | string | Invalid destination number |
created_at_from | date-time | 2026-03-25T10:30:00Z |
created_at_to | date-time | 2026-03-25T10:30:00Z |
updated_at_from | date-time | 2026-03-25T10:30:00Z |
updated_at_to | date-time | 2026-03-25T10:30:00Z |
page | integer | 1 |
limit | integer | 500 |
sort_by | enum | row_no |
sort_order | enum | asc |
Response fields
Upload Counts
After the file is accepted, FutureSMS starts itemization. The upload response waits for itemization to finish when it completes within the configured wait window.
During itemization FutureSMS:
- Reads rows in CSV order.
- Trims header names and cell values.
- Validates
phone_number. - Applies the selected template rules to
var. - Validates rendered message length.
- Stores valid rows as
pendingbulk items. - Records rejected rows with a row-level error message.
| Count | Meaning |
|---|---|
total_rows | Number of data rows in the CSV. |
valid_rows | Rows accepted as pending bulk items. |
invalid_rows | Rows rejected during upload itemization. |
If no valid rows remain, the job becomes failed and cannot be executed.
Job Status
| Status | Meaning |
|---|---|
items_ready | Upload itemization finished and at least one row can be executed. |
executed | Execution was accepted and row processing has completed. |
failed | Upload itemization or job processing failed before execution. |
ordered_rows is the number of bulk items that produced orders. It is populated after execution, not after upload.
Item Status
| Status | Meaning |
|---|---|
pending | Row was accepted during upload and is waiting for execution. |
filled | Row produced an SMS order. |
rejected | Row failed upload validation or send-time checks. |
Common rejected reasons include missing phone_number, unsupported non-empty columns, invalid phone number, missing var for a variable template, var provided for a fixed template, message content too long, unavailable destination, unavailable auto routing when sender_id is AUTO, or insufficient account state.
Behavior
Upload counts describe file itemization only. They do not mean messages have been sent.
valid_rows as sent messages. Orders are created only after the execution step accepts and processes eligible rows.Execution creates SMS orders only for rows that pass send-time checks. Query the job and item endpoints to inspect final row outcomes.
Status codes
| Status | Where | Meaning |
|---|---|---|
201 | Upload | Bulk job was created and itemization result is returned. |
202 | Execute | Execution command was accepted. |
200 | Query | Job or item rows were returned. |
401 | All | Missing, invalid, or expired access token. |
403 | All | Authenticated caller is not allowed to use this job, sender, template, or auto-routing setup. |
404 | Job routes | Bulk job was not found or is not visible to the caller. |
409 | Execute | Job is not executable, has no valid rows, or has already moved from items_ready. |
422 | All | Body, path, query, file, or row validation failed. |
Examples
{
"message": "Created",
"details": null,
"data": {
"bulk_job_id": "BKJ-9001",
"total_rows": 2,
"valid_rows": 2,
"invalid_rows": 0,
"bulk_job_status": "items_ready"
},
"meta": null
}
{
"message": "Accepted",
"details": "Bulk execution command accepted",
"data": {
"bulk_job_id": "BKJ-9001"
},
"meta": null
}
{
"message": "OK",
"details": null,
"data": {
"bulk_job_id": "BKJ-9001",
"total_rows": 2,
"valid_rows": 2,
"invalid_rows": 0,
"ordered_rows": 2,
"bulk_job_status": "executed",
"started_at": "2026-03-25T10:40:58Z",
"completed_at": "2026-03-25T10:41:05Z",
"created_at": "2026-03-25T10:40:58Z"
},
"meta": null
}
{
"message": "OK",
"details": null,
"data": [
{
"bulk_job_item_id": "BKI-12001",
"row_no": 1,
"order_id": "SDPB260325104102A1B2C3E",
"bulk_item_status": "filled",
"phone_number": "+639171230001",
"error_message": null,
"created_at": "2026-03-25T10:41:02Z",
"updated_at": "2026-03-25T10:41:02Z"
},
{
"bulk_job_item_id": "BKI-12002",
"row_no": 2,
"order_id": null,
"bulk_item_status": "rejected",
"phone_number": "+639171230002",
"error_message": "Destination is not available for this account",
"created_at": "2026-03-25T10:41:04Z",
"updated_at": "2026-03-25T10:42:33Z"
}
],
"meta": {
"pagination": {
"page": 1,
"limit": 50,
"total": 2
}
}
}