start. Campaigns are orchestrated by the Campaign Engine — a durable workflow engine — so a run survives worker restarts, respects calling windows, and retries without double-dialing.
For the API surface and states, see the Campaigns concept.
Orchestration model
Every campaign has one dispatcher: a durable loop that looks at the campaign’s recipients, finishes what has ended, and starts what is due. Waiting — for a free call slot, a retry, business hours — is just a recipient that isn’t due yet, not a process holding a place in line. Each pass of the dispatcher:Step by step
1
Start
POST /campaigns/{id}/start sets status scheduled and wakes the campaign’s dispatcher. If the Campaign Engine isn’t configured, you get 503.2
First pass
Sets status
running and emits campaign.workflow_bootstrapped. Recipients beyond max_recipients_per_campaign are marked cancelled (with a campaign.recipients_truncated event) — never silently dropped.3
Claim a recipient
Retries and callbacks that are due go first, then first attempts in import order. A recipient is claimed together with a call slot (your workspace’s and the campaign’s concurrency limits) in one step, so nothing is dialled without one, and records an attempt. While your workspace has API calls waiting, campaigns claim nothing — API calls go first.
4
Place the call
Marks the recipient
calling and places a real outbound voice session — minting a short-lived agent token, dispatching the agent, and dialing over SIP. If the call couldn’t be placed, it is retried later (a temporary failure) or the recipient is finalized failed (a configuration problem, or no credits).5
Finish the call
When the call ends, the dispatcher reads the result from the session — waiting briefly for the agent’s end-of-call report. A call still live past the longer of
call_timeout_seconds (default 600, bounds 30–3600) and the agent’s maximum call length is ended and counted as expired.6
Classify the outcome
The call is classified from its SIP result and transcript:
A retryable outcome with attempts left (
max_attempts, 1–10) puts the recipient back to scheduled, due after the retry delay and inside business hours. no_answer on the final attempt becomes failed.7
Complete
Once every recipient is final, the campaign completes —
completed, or failed if a recipient ended in a system failure (a declined or unanswered call is not one) — and emits campaign.completed.Pause & resume
POST /campaigns/{id}/pause sets status paused: the dispatcher starts nothing new, while calls already live finish and are recorded. POST /campaigns/{id}/start on a paused campaign resumes it. Deleting a campaign removes it and its recipients; live calls finish.
Reliability & idempotency
- At-least-once safe. Each recipient is claimed in one step, and a call whose result was lost in transit is checked against the media server before it is ever retried — so a restart or a retry can’t call the same person twice.
- Durable. The recipients themselves are the queue; a worker restart resumes mid-run.
- Bounded.
max_attempts(1–10),call_timeout_seconds(30–3600),retry_delay_minutes(1–10080), andmax_recipients_per_campaign(default 10,000, max 50,000) cap the blast radius of any run. - Auditable. Every transition is written to the campaign event log — see Reading events.
Running again
A finished campaign is terminal and read-only — re-running in place is disabled so a previous run’s recipients and results can’t be destroyed by accident. To run it again, clone it (POST /campaigns/{id}/clone): the clone copies the config into a fresh draft (no recipients), records its source in cloned_from_campaign_id, and leaves the original untouched. Publish the clone, upload a recipient list, then start.
whatsapp_voice campaigns hand recipients who should get a call to this same dispatcher, which places and retries the call exactly as above. See the WhatsApp guide.
