Broadcast Campaigns
Send bulk email/SMS messages to targeted audiences. A campaign combines a template with an audience.
Headers (every request)
Authorization: Bearer {token}
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
All endpoints include ?o={organization_id} for tenant context.
Concepts
| Resource | Type | Description |
|---|---|---|
| Campaign | broadcast-campaigns |
Orchestrates sending; tracks status and progress |
| Template | broadcast-templates |
Email/SMS content with i18n and variable placeholders |
| Audience | broadcast-audiences |
Recipient targeting based on dynamic customer, booking, or community criteria |
| Message | broadcast-messages |
Individual message instance per recipient |
Status Lifecycle
draft → scheduled → pending → in-progress → completed
| Status | Meaning |
|---|---|
draft |
Not yet started; can be edited |
scheduled |
Queued for future send (scheduled_at) or next cron run |
pending |
About to be processed |
in-progress |
Messages are being sent |
completed |
All messages processed |
delivered |
Message delivered (per-message status) |
failed |
Send failed |
Audience Types
| Type | Description |
|---|---|
customers |
Dynamic — uses customer filters (same filters as /customers) |
bookings |
Dynamic — uses booking filters (same filters as /bookings) |
community |
All members of a linked community |
Template Variables
Templates support placeholder variables like {first_name}, {last_name}, etc. These are replaced per-recipient using the recipient model's getVariableBindings(). The template body is localized per customer's preferred locale.
Step 1 — Create a Template
POST {BASE_URL}/broadcast-templates?o={org_id}
{
"data": {
"type": "broadcast-templates",
"attributes": {
"name": "March Newsletter",
"subject": "Hello {first_name}!",
"body": "<p>Hi {first_name}, here's your monthly update...</p>",
"body_short": "Hi {first_name}, check your monthly update.",
"action_label": "View Details",
"action_url": "https://example.com/updates",
"channels": ["mail"],
"is_template": false
}
}
}
| Attribute | Type | Description |
|---|---|---|
name |
string | Internal name |
subject |
string | Email subject (supports variables and i18n) |
body |
string | HTML email body (supports variables and i18n) |
body_short |
string | SMS body (supports variables and i18n) |
action_label |
string | CTA button text |
action_url |
string | CTA button URL |
channels |
string[] | ["mail"], ["sms"], or ["mail", "sms"] |
is_template |
boolean | true = reusable template; false = one-off |
binding_type |
string | Variable binding context |
For i18n, send localized versions via subject_i18n, body_i18n, etc. as objects keyed by locale.
Includes
| Include | Description |
|---|---|
files |
Attached files |
Filters
| Filter | Description |
|---|---|
filter[search] |
Name search |
filter[binding_type] |
Filter by binding type |
filter[is_template] |
true for reusable templates only |
Step 2 — Create an Audience
POST {BASE_URL}/broadcast-audiences?o={org_id}
Dynamic audience (filtered customers)
{
"data": {
"type": "broadcast-audiences",
"attributes": {
"name": "Active Customers 2026",
"audience_type": "customers",
"filters": {
"search": "active"
},
"is_template": false
}
}
}
The filters object accepts the same filters available on the /customers endpoint (for customers type) or /bookings endpoint (for bookings type).
Dynamic audience (filtered bookings)
{
"data": {
"type": "broadcast-audiences",
"attributes": {
"name": "Upcoming Bookings",
"audience_type": "bookings",
"filters": {
"status": "accepted",
"date_from": "2026-04-01"
}
}
}
}
| Attribute | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Audience name |
audience_type |
string | yes | customers, bookings, or community |
filters |
object | no | Dynamic filter criteria |
is_template |
boolean | no | Reusable audience template |
Includes
| Include | Description |
|---|---|
community |
Linked community (for community type) |
Step 3 — Create a Campaign
POST {BASE_URL}/broadcast-campaigns?o={org_id}
{
"data": {
"type": "broadcast-campaigns",
"attributes": {
"name": "March 2026 Newsletter"
},
"relationships": {
"template": { "data": { "type": "broadcast-templates", "id": "{template_id}" } },
"audience": { "data": { "type": "broadcast-audiences", "id": "{audience_id}" } }
}
}
}
| Attribute | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Campaign name |
scheduled_at |
datetime | no | Schedule for future send |
recurring |
boolean | no | Enable recurring sends |
cron |
string | no | Cron expression (required when recurring=true). Format: minute hour day month weekday |
Optional: Schedule or make recurring
{
"data": {
"type": "broadcast-campaigns",
"attributes": {
"name": "Weekly Reminder",
"scheduled_at": "2026-04-01T09:00:00Z",
"recurring": true,
"cron": "0 9 * * 1"
},
"relationships": {
"template": { "data": { "type": "broadcast-templates", "id": "{template_id}" } },
"audience": { "data": { "type": "broadcast-audiences", "id": "{audience_id}" } }
}
}
}
Step 4 — Preview Draft Messages
Before sending, preview the generated messages (max 10):
GET {BASE_URL}/broadcast-campaigns/{id}/draft-messages?o={org_id}
Returns a collection of broadcast-messages with variable placeholders resolved per recipient, channels determined (invalid channels auto-removed if customer lacks email/mobile), and content localized.
Step 5 — Start the Campaign
GET {BASE_URL}/broadcast-campaigns/{id}/start?o={org_id}
Behavior depends on campaign config:
| Scenario | What happens |
|---|---|
No scheduled_at |
Sends immediately |
scheduled_at in the future |
Queues for scheduled delivery |
recurring=true |
Calculates next run from cron, sends at that time, then reschedules |
The campaign transitions through: scheduled → pending → in-progress → completed.
Progress is tracked via sent_count, failed_count, and total_count attributes. These update in real-time (a WebSocket event broadcast-campaigns.updated fires every 5 messages and on completion).
Stop a Campaign
Reverts the campaign to draft and deletes all unsent (pending/draft) messages:
GET {BASE_URL}/broadcast-campaigns/{id}/stop?o={org_id}
List Campaigns
GET {BASE_URL}/broadcast-campaigns?o={org_id}&include=template,audience
Filters
| Filter | Description |
|---|---|
filter[search] |
Name search |
Sorting
| Sort | Description |
|---|---|
name |
Campaign name |
created_at |
Creation date |
Includes
| Include | Description |
|---|---|
template |
The message template |
template.files |
Template with file attachments |
audience |
The target audience |
List Campaign Messages
GET {BASE_URL}/broadcast-campaigns/{id}/messages?o={org_id}&include=customer
Message Attributes
| Attribute | Type | Description |
|---|---|---|
subject |
string | Resolved email subject |
body |
string | Resolved HTML body |
body_short |
string | Resolved SMS body |
action_label |
string | CTA text |
action_url |
string | CTA URL |
channels |
string[] | Delivery channels for this recipient |
locale |
string | Recipient's locale |
email |
string | Recipient email |
mobile |
string | Recipient mobile |
status |
string | draft, pending, completed, delivered, failed |
created_at |
datetime |
Message Includes
| Include | Description |
|---|---|
campaign |
Parent campaign |
recipient |
The recipient (customer or booking) |
customer |
Related customer |
sent_email |
Tracked email record |
Full Workflow
1. POST /broadcast-templates → Create message content
2. POST /broadcast-audiences → Define recipients
3. POST /broadcast-campaigns → Create campaign linking template + audience
4. GET /broadcast-campaigns/{id}/draft-messages → Preview
5. GET /broadcast-campaigns/{id}/start → Send
6. GET /broadcast-campaigns/{id}/messages → Monitor delivery
All endpoints include ?o={organization_id} for tenant context.