Batch SMS
API endpoints related to managing Batch SMS campaigns. Batch SMS lets you schedule outbound text campaigns to multiple recipients at once. Each campaign is linked to an Organization and uses the organization's primary Agent plus SMS configuration to handle replies after the opening message is sent.
Use Cases
Batch SMS is ideal for:
- Appointment reminders and confirmations
- Promotional campaigns and announcements
- Service updates and time-sensitive alerts
- Re-engagement campaigns
- Follow-up check-ins after calls or visits
- Personalized outreach at scale
Create Batch SMS Campaign
Creates a new batch SMS campaign for an organization.
POST /v1/organizations/{organizationId}/batch-sms
Path Parameters:
organizationId(string, required): The unique identifier of the organization sending the campaign.
Headers:
Content-Type(string, required): Must be set toapplication/json.x-api-key(string, required): Your API key for authentication.
Request Body:
{
"name": "Spring Launch Follow-Up",
"prompt": "You are following up on our spring launch campaign. Keep replies concise, friendly, and focused on answering questions or helping the customer book a next step.",
"firstMessage": "Hi {{name}}, Benny here from Example Corp. We just opened a few new consultation slots this week. Want me to send over the available times?",
"scheduledFor": 1699920000000,
"recipients": [
{
"name": "John Smith",
"phone_number": "+15551234567",
"language": "en",
"customVariables": {
"offerName": "Spring Tune-Up",
"bookingLink": "https://example.com/book"
}
},
{
"name": "Maria Garcia",
"phone_number": "+15559876543",
"language": "es",
"customVariables": {
"offerName": "Consulta de Primavera",
"bookingLink": "https://example.com/es/book"
}
}
]
}
Field Descriptions:
idempotencyKey(string, optional): A stable key (1-255 characters) that makes campaign creation idempotent. Retrying a request with the sameidempotencyKeyreturns the existing campaign instead of creating a duplicate.name(string, required): A descriptive name for the SMS campaign (1-255 characters).prompt(string, optional): Instructions for how Benny should handle follow-up replies in the SMS thread (max 3000 words). If omitted, Benny will use the saved shared outbound SMS configuration for the organization.firstMessage(string, optional): The first outbound SMS message. Supports{{variableName}}interpolation per recipient (max 1600 characters). If omitted, Benny will use the saved shared outbound SMS configuration for the organization.scheduledFor(number, optional): Unix timestamp in milliseconds when the campaign should start. Defaults to 1 minute from now if omitted.recipients(array, required): List of SMS recipients (minimum 1, maximum 10000).name(string, optional): Recipient's name for personalization.phone_number(string, required): Recipient's phone number in E.164 format (e.g., +15551234567).language(string, optional): SMS language code. Options:en,es,fr,de,pl. Defaults toen.customVariables(object, optional): Key-value pairs available forfirstMessageinterpolation.source(string, optional): Import source for the recipient. Accepted values:file_upload,manual_entry.rowNumber(number, optional): Original row number when the recipient came from an uploaded file.
Response:
- 200 OK: Batch SMS campaign created successfully.
{"message": "Batch SMS campaign created successfully","body": {"id": "batch-sms-123abc","organizationId": "{organizationId}","agentId": "agent_123abc","name": "Spring Launch Follow-Up","prompt": "You are following up on our spring launch campaign...","firstMessage": "Hi {{name}}, Benny here from Example Corp...","scheduledFor": 1699920,"totalRecipients": 2,"sentCount": 0,"failedCount": 0,"repliedCount": 0,"status": "scheduled","recipients": [{"name": "John Smith","phone_number": "+15551234567","language": "en","customVariables": {"offerName": "Spring Tune-Up","bookingLink": "https://example.com/book"}}],"deliveries": [],"createdAt": 1699919340000,"updatedAt": 1699919340000}}
- 400 Bad Request: Invalid input data, an invalid/expired API key, SMS is not enabled for the organization, no Twilio number is configured, or
prompt/firstMessagewere omitted and no saved outbound SMS configuration provided them. - 401 Unauthorized: The API key does not belong to the organization in the path.
- 404 Not Found: Organization not found or no agent configured for the organization.
Try it yourself
Get Campaign Status
Returns a campaign with each recipient's dispatch status and carrier delivery receipt, so you can reconcile without relying on webhooks alone.
GET https://api-v2.askbenny.ca/v1/organizations/{organizationId}/batch-sms/{campaignId}
Headers: x-api-key (string, required).
Response (200):
{
"message": "Campaign retrieved",
"data": {
"id": "batch-sms-3f9a2c",
"name": "Portal notifications",
"status": "in_progress",
"idempotencyKey": "portal-notification-0001",
"scheduledFor": 1780000000,
"startedAt": 1780000003,
"totalRecipients": 3,
"counts": { "pending": 1, "sent": 1, "failed": 0, "replied": 0, "suppressed": 1, "cancelled": 0, "delivered": 1, "undelivered": 0 },
"deliveries": [
{
"phoneNumber": "+15551234567",
"status": "sent",
"messageSid": "SM00000000000000000000000000000001",
"threadId": "thread-+15551234567",
"sentAt": 1780000004000,
"providerStatus": "delivered",
"providerStatusAt": 1780000009000
},
{ "phoneNumber": "+15557654321", "status": "suppressed", "suppressedAt": 1780000004500, "error": "Recipient opted out of SMS" },
{ "phoneNumber": "+15550001111", "status": "pending" }
]
},
"requestId": "6f1c…"
}
- Campaign
status:scheduled,in_progress,paused,completed,failedorcancelled. - Recipient
status:pending: not sent yet.sent: accepted by the carrier.failed: the send was rejected.replied: the recipient answered.suppressed: skipped because the recipient opted out or is blocked.cancelled: never sent because the campaign was cancelled first.
providerStatusis the carrier receipt (sent,delivered,undelivered,failed); see delivery status.- Units: campaign-level times (
scheduledFor,startedAt,completedAt) are unix seconds. Per-recipient times are unix milliseconds. - Prompts, template variables and other recipient data are not returned.
- Returns
404(CAMPAIGN_NOT_FOUND) for an unknown campaign.
Cancel a Campaign
Stops a campaign: every recipient still pending becomes cancelled and is never texted.
POST https://api-v2.askbenny.ca/v1/organizations/{organizationId}/batch-sms/{campaignId}/cancel
Headers: x-api-key (string, required). No request body.
Guarantees:
- The sender re-reads the campaign immediately before every send, so every recipient not yet reached is cancelled.
- At most one message per campaign that was already being sent at that moment can still go out. It is reported as
sent, notcancelled. - Cancellation is final: a cancelled campaign cannot be resumed or edited. Create a new campaign to send again.
- Cancelling an already-cancelled campaign returns
200with"message": "Campaign was already cancelled".
Response (200):
{
"message": "Campaign cancelled",
"data": {
"cancelledRecipients": 1,
"campaign": { "id": "batch-sms-3f9a2c", "status": "cancelled", "cancelledAt": 1780000020000, "counts": { "pending": 0, "cancelled": 1, "sent": 1 }, "deliveries": [] }
},
"requestId": "6f1c…"
}
Errors:
404(CAMPAIGN_NOT_FOUND): the campaign does not exist.409(CAMPAIGN_ALREADY_FINISHED): the campaign has already sent to every recipient.
To stop texting one recipient across all campaigns, for example when they turn off notifications in your portal, use Suppress a recipient instead.
Opt-outs and Webhooks
Recipients who text STOP, or whom you suppress through the API, are skipped by every campaign. Opt-outs, replies, delivery receipts and campaign correlation are documented in SMS Webhooks and Consent.
Best Practices
Prompt Scope
Use prompt for reply-handling behavior after the opening text is delivered. Keep it focused on:
- how Benny should answer follow-up questions
- what next step to guide the recipient toward
- tone, escalation rules, and any compliance constraints
Using Saved SMS Config
If your team already saved an outbound SMS config in the dashboard, you can omit prompt and firstMessage in the public API request. Benny will fall back to the saved shared SMS config for that organization.
Message Personalization
Use customVariables to personalize the opening message:
{
"firstMessage": "Hi {{name}}, your order #{{orderNumber}} is ready for pickup at {{location}}.",
"recipients": [
{
"name": "Sarah",
"phone_number": "+15551234567",
"customVariables": {
"orderNumber": "12345",
"location": "Downtown"
}
}
]
}
SMS Readability
- Keep
firstMessageshort and clear - Lead with the business name or a recognizable context
- End with a simple next action or question
- Avoid cramming multiple asks into one text
Phone Number Format
Always use E.164 format for phone numbers:
- Correct:
+15551234567 - Incorrect:
(555) 123-4567,555-123-4567,15551234567
Scheduling
scheduledForaccepts Unix timestamps in milliseconds- If omitted, the campaign starts 1 minute from now
- Schedule during the recipient's likely waking or business hours
- Make sure SMS is enabled in the organization and a Twilio number is configured before sending
Consent
- Include opt-out instructions such as "Reply STOP to opt out" in
firstMessage - Pass a stable
idempotencyKeyper notification. It makes retries safe and is echoed in every webhook for that campaign