Skip to main content
This is the deep dive on how a voice campaign actually runs after you call 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), and max_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.