Campaigns Deep-Dive
Campaigns Deep-Dive
What you build: a complete mental model of the campaigns engine. Outbound reminder campaign is the “just make it work” recipe; this page is the reference — every lifecycle state, every knob, and how to reconcile counters against webhooks so your dashboard is never wrong.
Who it’s for: teams running outbound at scale who need to pause/resume safely, retry only failures, personalise every recipient, and reconcile delivery.
Lifecycle states
status enum: draft, running, paused, completed, cancelled.
Create with the full config
Only channel is required. Execution details (caller-ID, call window, per-channel body) live in the free-form, orchestrator-owned config; retries in retry_policy; optional scheduling in schedule:
Returned object carries id (camp_{nanoid}), status, counters, timestamps, and retryable_failed.
Per-recipient parameters
Every target has a to_address and its own params, referenced in the agent prompt or SMS body. This is how one campaign produces personalised contact for thousands of recipients:
skipped_duplicate = a to_address already in the campaign; rejected_invalid = malformed E.164. List targets and their per-target state any time:
A CampaignTarget carries: id (tgt_{nanoid}), campaign_id, to_address, params, disposition, attempt_count, next_attempt_at, last_exec_id (the call_/msg_ id of the latest execution). Disposition is channel-specific:
Lifecycle control
retry-failed re-queues the retryable_failed targets (genuine dial failures, excluding suppression terminals like opted_out). The campaign object surfaces that count as retryable_failed for completed/paused campaigns (omitted when 0), so you know how many retry-failed will touch.
Counters and reconciliation
The campaign object carries live counters. Treat webhooks as the source of truth for why a target landed where it did, and counters as the fast aggregate:
The invariant to check in your dashboard:
If your own tally drifts from counters, you dropped or double-counted a webhook — reconcile against delivery history rather than trusting your local count.
Webhook reference flow
Per target, you’ll see (channel-dependent):
* optional/best-effort. Every campaign event carries campaign_id and the target’s address, so you can attribute it. Increment your tallies off the terminal event only (call.completed / message.delivered / message.failed), never the intermediate ones.
Test it (sandbox)
Run an entire campaign with pk_test_ and targets in the test range (+234800000xxxx). The orchestrator paces and “dials” synthetically, emitting real-signed lifecycle events with ₦0 billing — end-to-end rehearsal of pacing, retries, and your reconciliation code without touching a carrier.
Troubleshooting
Next steps
- The quick, opinionated version → Outbound reminder campaign.
- Fall back across channels within one flow → Multi-channel cascade.
- Personalise what each call says with tools → Tool-calling.