> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voicebip.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voicebip.com/_mcp/server.

# Outbound Reminder Campaign

> Upload a list, run an outbound voice or SMS campaign that confirms appointments or payments, and tally the results — the classic "reach everyone before tomorrow" flow.

**What you build:** a campaign that dials (or texts) a list of recipients, plays a confirmation message, retries no-answers on a schedule, and reports how many succeeded. The outbound orchestrator does the pacing, retries, and per-recipient tracking for you.

```
recipients ─▶ POST /v1/campaigns ─▶ add targets ─▶ start
                                                     │
                ┌─────────────────────────────────── │ (paced dialing, retries)
                ▼                                     ▼
         call/SMS each target              webhooks: call.completed / message.delivered
                │                                     │
                ▼                                     ▼
         disposition per target ──────────────▶ your tally (succeeded / failed / no_answer)
```

**Who it's for:** clinics reducing no-shows, lenders confirming repayments, schools and event organisers confirming attendance.

## Prerequisites

* An [agent](/use-cases/inbound-support-kb) to run the conversation (for voice campaigns).
* A webhook receiver for `call.completed` (voice) or `message.delivered` / `message.failed` (SMS).
* `ENABLE_OUTBOUND_VOICE` / `ENABLE_OUTBOUND_MESSAGES` enabled on your workspace (they are, on managed accounts).

## Step 1 — Create the campaign

Only `channel` is required. Channel-specific execution settings (caller-ID, call window, webhook) go in the free-form `config` block, which the orchestrator owns; retries go in `retry_policy`:

```bash
curl -X POST https://api.voicebip.com/v1/campaigns \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d @campaign.json
```

```json
{
  "channel": "voice",
  "agent_id": "agt_PAEZ_njcfm2kycpjs",
  "config": {
    "from_number": "+2341002223344",
    "call_window": { "start": "09:00", "end": "18:00", "timezone": "Africa/Lagos" }
  },
  "retry_policy": { "max_attempts": 3, "backoff_seconds": 600, "backoff_multiplier": 4 }
}
```

```json
{ "id": "camp_6r…", "channel": "voice", "status": "draft", "counters": { "total": 0 } }
```

The campaign starts in `draft` so you can add targets before any dialing happens.

## Step 2 — Add recipients (targets)

Add targets in batches. Each target has a `to_address` and its own `params` — arbitrary per-recipient values the agent references (name, appointment time), so every call is personalised:

```bash
curl -X POST https://api.voicebip.com/v1/campaigns/camp_6r…/targets \
  -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
  -d '{
    "targets": [
      { "to_address": "+2348031234567", "params": { "name": "Ada", "time": "10am Thursday" } },
      { "to_address": "+2348082223344", "params": { "name": "Bode", "time": "2pm Thursday" } }
    ]
  }'
```

```json
{ "added": 2, "skipped_duplicate": 0, "rejected_invalid": 0 }
```

Reference the params in the agent's `system_prompt` / `greeting_text`, e.g. *"Hello \{\{name}}, this is a reminder about your appointment at \{\{time}}. Press 1 to confirm."*

## Step 3 — Start it

```bash
curl -X POST https://api.voicebip.com/v1/campaigns/camp_6r…/start \
  -H "Authorization: Bearer pk_xxx"
# → { "id": "camp_6r…", "status": "running" }
```

The orchestrator now dials at its pacing, inside the call window, retrying failures per your `retry_policy`. Lifecycle controls:

| Action                | Endpoint                               |
| --------------------- | -------------------------------------- |
| Pause                 | `POST /v1/campaigns/{id}/pause`        |
| Resume                | `POST /v1/campaigns/{id}/resume`       |
| Cancel                | `POST /v1/campaigns/{id}/cancel`       |
| Retry only the failed | `POST /v1/campaigns/{id}/retry-failed` |

## Step 4 — Tally results from webhooks

Each finished call fires `call.completed`. There is no per-call "confirmed" flag — `status` is `completed` and the outcome you tally comes from the target's `disposition` (voice: `completed`/`failed`/`no_answer`/`busy`) and, if you need intent, Voicebip's AI `resolution` classification (`resolved`/`unresolved`). List targets to read their disposition:

```bash
curl "https://api.voicebip.com/v1/campaigns/camp_6r…/targets?status=failed" \
  -H "Authorization: Bearer pk_xxx"
```

```json
{
  "targets": [
    { "id": "tgt_1a…", "to_address": "+2348082223344", "disposition": "no_answer", "attempt_count": 3 }
  ]
}
```

For SMS campaigns, watch `message.delivered` and `message.failed`; the SMS dispositions are `sent`/`delivered`/`undeliv`/`opted_out`.

Poll live counters any time:

```bash
curl https://api.voicebip.com/v1/campaigns/camp_6r… \
  -H "Authorization: Bearer pk_xxx"
```

```json
{
  "id": "camp_6r…",
  "status": "running",
  "counters": { "total": 2, "queued": 0, "dispatched": 1, "succeeded": 1, "failed": 0 }
}
```

## SMS variant

For a text-only reminder, set `channel: "sms"` and drop the agent — put the message body (with per-target `params`) in the campaign `config`. Everything else (targets, retries, lifecycle) is identical.

## Test it (sandbox)

Run the whole campaign against `pk_test_` with targets in the test range (`+234800000xxxx`) — the orchestrator paces and "dials" synthetically, ₦0 billing. Then simulate a completion:

```bash
curl -X POST https://api.voicebip.com/v1/webhooks/test \
  -H "Authorization: Bearer pk_test_xxx" -H "Content-Type: application/json" \
  -d '{ "webhook_url": "https://you.example.com/webhooks", "event_type": "call.completed" }'
```

## Troubleshooting

| Symptom                           | Likely cause                                                |
| --------------------------------- | ----------------------------------------------------------- |
| Campaign stuck in `draft`         | You never called `/start`.                                  |
| Nothing dials                     | Outside the call window, or workspace outbound flag off.    |
| Targets `rejected_invalid` on add | Malformed E.164 in `to_address`.                            |
| Variables not spoken              | The agent prompt doesn't reference `{{name}}` / `{{time}}`. |

## Next steps

* The full lifecycle, counters, and retry semantics → [Campaigns deep-dive](/use-cases/campaigns-deep-dive).
* Fall back across channels when voice doesn't connect → [Multi-channel cascade](/use-cases/multi-channel-cascade).
* Have the agent record structured responses via a tool → [Tool-calling](/use-cases/tool-calling).