Skip to main content

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 to application/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 same idempotencyKey returns 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 to en.
    • customVariables (object, optional): Key-value pairs available for firstMessage interpolation.
    • 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/firstMessage were 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​

POST/v1/organizations/{organizationId}/batch-sms
Request URL: https://api.askbenny.ca/v1/organizations/{organizationId}/batch-sms

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, failed or cancelled.
  • 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.
  • providerStatus is 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, not cancelled.
  • Cancellation is final: a cancelled campaign cannot be resumed or edited. Create a new campaign to send again.
  • Cancelling an already-cancelled campaign returns 200 with "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 firstMessage short 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​

  • scheduledFor accepts 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
  • Include opt-out instructions such as "Reply STOP to opt out" in firstMessage
  • Pass a stable idempotencyKey per notification. It makes retries safe and is echoed in every webhook for that campaign