Skip to main content

Batch Calling

When you need to dial more than a handful of contacts — or repeat the same dial on a schedule — use a campaign instead of looping the one-off endpoint. Campaigns are first-class objects: they're paced, retried, and observable from the dashboard.

There are two kinds:

  • Recurring — fire the same call on a cron schedule (daily standup reminder, weekly check-in). One destination, repeating instants.
  • Bulk — dial a list of contacts once at a controlled rate (Q3 follow-ups, RSVP confirmations). Many destinations, a single fan-out window.

Both run through the same materializer + dispatcher pipeline, so once you know one, the other is mostly the same. Exact request/response shapes for every endpoint mentioned on this page live in the API Reference.

Architecture in 30 seconds

You define a campaign. A singleton scheduler wakes every 30 seconds and materializes the next hour's worth of concrete calls into the queue. A pool of dispatchers pulls them off the queue at scheduled_at and dials them, respecting per-workspace and per-DID rate limits. Outcomes are emitted as webhooks and surfaced under the campaign's detail view.

You don't run any of that — Bolti operates it. You just create the campaign and watch it work.

Recurring campaigns

Use a recurring campaign for "fire this call on a schedule". Examples:

  • A daily standup reminder to a team line at 9:00 IST on weekdays.
  • A weekly RSVP nudge to a vendor every Tuesday at 10:00 PT.
  • A monthly check-in on the 1st of each month.

Create from the dashboard

Dashboard → Campaigns → New Campaign → Recurring
  1. Name and (optional) description.
  2. Pick the agent and the From number.
  3. Destination (To): a single E.164 number.
  4. Schedule: choose a preset (e.g. "Weekdays at 9 AM") or enter a custom 5-field cron expression.
  5. Timezone: cron is interpreted in this IANA timezone (defaults to Asia/Kolkata). DST is handled correctly.
  6. Max attempts: per-occurrence retry budget.
  7. Save → campaign starts in draft. Click Start to flip it to active.

Create via API

The campaigns endpoint takes a kind of recurring, the agent / outbound number, the cron expression and IANA timezone, retry policy, and any default prompt variables. After creation, separate start / pause / resume / cancel endpoints drive the lifecycle.

→ See API Reference for full schemas.

Lifecycle

StatusBehavior
draftCreated but not running. Materializer ignores it.
activeMaterializer expands the next hour's occurrences every 30 s.
pausedFuture expansion stops. Already-materialized rows in the queue still fire.
completedReached end_at. Terminal.
cancelledHard stop. Pending rows are unscheduled. Terminal.

You can edit name / description / cron / timezone / max attempts / end time on a recurring campaign at any time. Edits take effect on the next materialization pass, so worst-case visibility lag is ~30 s.

Bulk campaigns

Use a bulk campaign for "dial this list once". Examples:

  • Q3 customer follow-ups against a 5,000-row CSV.
  • RSVP confirmation calls before an event.
  • A churn-recovery sweep for accounts that haven't logged in this month.

Create from the dashboard

Dashboard → Campaigns → New Campaign → Bulk
  1. Name and (optional) description.
  2. Pick the agent and the From number.
  3. Destinations: paste E.164 numbers, one per line (or comma-separated).
  4. Rate limit: max calls per minute Bolti will dial from this campaign.
  5. Max attempts: per-target retry budget.
  6. Save → campaign starts in draft with the targets pre-loaded. Click Start to fan out.

The scheduler dispatches at the configured rate, so 1,000 targets at 30/minute take ~33 minutes to walk through.

Adding more targets after creation

Open the campaign → Add targets. Numbers already on the list dedupe automatically (the unique constraint is on (campaign_id, to_number)).

There's a separate targets endpoint for appending programmatically; the response reports how many rows were actually inserted vs deduped.

→ See API Reference.

Skipping targets

A pending target you no longer want to dial can be skipped from the Targets panel of the campaign detail view, or via the targets DELETE endpoint. Skipping marks the target as skipped so the materializer never generates a call for it. Already-dispatched targets are immutable.

Filtering and searching

The targets panel supports a status filter (pending, scheduled, dispatched, failed, skipped) and a free-text search over phone number / contact name. Useful when you want to inspect just the failures or pull up one specific row in a 5,000-row campaign.

Rate limiting

Two token buckets gate bulk dispatch:

  • Per-workspace — protects the whole tenant from runaway fan-out.
  • Per-DID (per outbound trunk number) — protects the carrier from rate spikes that would trip anti-spam heuristics.

Plus the campaign's own rate_limit_per_minute. The most-restrictive of the three wins.

Lifecycle

StatusBehavior
draftTargets uploaded; nothing dialed yet.
activeFan-out in progress.
pausedFan-out halted. In-flight calls finish; new ones don't start.
completedAll targets reached a terminal status.
cancelledHard stop. Pending targets are marked skipped.

Bulk campaigns are intentionally not editable after creation — only the target list grows. If you got the agent or caller-ID wrong, cancel and recreate.

Subscribing to outcomes

Don't poll. Register a webhook endpoint and subscribe to the events you care about:

EventFires when
scheduled_call.createdA row is materialized into the queue.
scheduled_call.dispatchedThe dial succeeded — a conversation has started.
scheduled_call.failedThe retry budget for one occurrence/target is exhausted.
scheduled_call.cancelledCancelled before it fired.
conversation.completedThe actual call finished and was billed.
campaign.completedA bulk campaign exhausted all targets, or a recurring campaign reached end_at.

Each delivery is HMAC-signed and retried with exponential backoff up to 6 attempts before moving to dead-letter. See Webhooks for verification and rotation.

Compliance reminders

  • DNC scrubbing — Bolti does not scrub bulk lists against do-not-call registries. That's on you and is jurisdiction-specific.
  • TCPA / regional autodialer rules — high-volume outbound is regulated in many jurisdictions. Make sure your use case is covered.
  • Quiet hours — recurring crons run when you tell them to. If 10 PM dials are illegal in your target market, build that into the cron.
  • Caller-ID accuracy — the From number must be one you legitimately control.

If you operate at enterprise scale or in regulated markets, reach out — see Enterprise.

Next steps