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
A run is a parent workflow that fans out one child per recipient:- Campaign workflow (parent) — bootstraps the run, starts one child per recipient, waits for all of them, then completes.
- Recipient workflow (child) — retries attempts for a single recipient until success or exhaustion.
- Activities — the side effects (planning a slot, creating a call, classifying the result) that run on the campaign worker.
Step by step
1
Start
POST /campaigns/{id}/start starts the workflow, persists its temporal_workflow_id / temporal_run_id, and sets status scheduled. If the engine isn’t configured, you get 503.2
Bootstrap
Loads the campaign’s recipients and execution policy, sets status
running, and emits campaign.workflow_bootstrapped.3
Plan the attempt
Computes the next allowed call time from the campaign timezone + business hours (default Mon–Fri 09:00–18:00), applying the retry delay for attempts after the first. The recipient goes
scheduled.4
Create the attempt
Records an attempt row (
in_progress), marks the recipient calling, and places a real outbound voice session — minting a short-lived agent token, dispatching the agent, and dialing over SIP. On failure the attempt and recipient go failed.5
Wait for completion
The recipient workflow blocks until the session-update webhook signals the call ended, or until
call_timeout_seconds (default 600, bounds 30–3600). A timed-out active session is marked expired.6
Classify the outcome
The call is classified from its SIP result and transcript:
Retryable outcomes loop back to plan with a business-hours-aware delay, bounded by
max_attempts (1–10). no_answer on the final attempt becomes failed.7
Complete
Once every recipient is final, the parent completes the campaign —
completed if there were zero failures, otherwise failed — and emits campaign.completed.Pause & resume
POST /campaigns/{id}/pause signals the workflow to stop starting new attempts (in-flight calls finish); status goes paused. POST /campaigns/{id}/start on a paused campaign resumes it. Deleting a campaign terminates the workflow.
Reliability & idempotency
- At-least-once safe. Attempt creation is de-duplicated so a redelivered signal or retried activity can’t place a second call for the same attempt.
- Durable. Workflow state is persisted; 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 reuse this exact voice attempt loop for the call-escalation step. See the WhatsApp guide.
