# Campaigns and journeys

> How Caramel campaigns move from draft through review to deployment, and the rules for pause, resume, and deletion.

A campaign is an automated marketing sequence. Contacts enter when they match the entry segment; they receive a series of templates with configurable delays; they exit when they reach a terminal node, unsubscribe, or no longer match the segment.

**Journeys** are ongoing, trigger-based campaigns. **Broadcasts** are one-time sends to a fixed audience. Both follow the same state machine and share the same API tools.

> **Plan** Deploying campaigns requires Growth or above. AI-generated campaigns (`generate_campaign`) require Growth or above and consume AI credits. Library deploys (`deploy_template`) also require Growth or above.

## The campaign object

```json
{
  "id":            "cmp_01h7m9k0aabcdefghj1234567",
  "business_id":   "bus_01h7m9k0aabcdefghj1234567",
  "name":          "Welcome — first 7 days",
  "type":          "journey",
  "status":        "deployed",
  "entry_segment": "seg_new_signups",
  "steps": [
    { "type": "message", "template_id": "tpl_welcome_d0", "channel": "email" },
    { "type": "delay",   "duration": "P3D" },
    { "type": "message", "template_id": "tpl_welcome_d3", "channel": "email" },
    { "type": "delay",   "duration": "P4D" },
    { "type": "message", "template_id": "tpl_welcome_d7", "channel": "email" }
  ],
  "stats": {
    "entered":     847,
    "completed":   623,
    "in_progress": 181,
    "exited_early": 43
  },
  "deployed_at": "2026-05-22T08:14:55Z",
  "created_at":  "2026-05-15T10:11:22Z"
}
```

| Field | Description |
|---|---|
| `type` | `journey` (segment-triggered, ongoing) or `broadcast` (one-time, fixed audience). |
| `entry_segment` | The audience definition contacts must match to enter. |
| `steps` | Ordered sequence of `message` and `delay` nodes. `duration` is ISO 8601 (e.g. `P3D` = 3 days). |
| `stats` | Live aggregate counts for deployed journeys; static after `ended`. |

## Deployment state machine

```
  draft ──► pending_review ──► approved ──► deployed ──► ended
    ▲              │                            │
    │              └──► (back to draft)    ┌────┴────┐
    │                   if checks fail     ▼         ▼
    └──────────────────────────────── paused ◄─► (resume)
```

| Status | What it means | Allowed operations |
|---|---|---|
| `draft` | Being authored or refined. | `refine_campaign`, `delete_campaign` |
| `pending_review` | Automated quality checks running. | `refine_campaign`, `delete_campaign` |
| `approved` | Checks passed; ready to deploy. | `deploy_campaign`, `delete_campaign` |
| `deployed` | Live and sending. | `pause_campaign`, `get_campaign` |
| `paused` | Live but not sending. | `resume_campaign` |
| `ended` | Reached its scheduled end date, or was explicitly ended. | Read-only |

Calling a transition on a campaign already in the wrong state returns `409`. For example, `pause_campaign` on a paused campaign returns `409 not_running`.

## Three paths to a deployed campaign

### Path 1 — Deploy a library entry

The fastest path. Browse the library, pick a journey, deploy it. Caramel provisions the entry segment, every dependent template, and the journey atomically. The journey lands in `deployed` immediately — no review step, because library content is pre-vetted.

```bash
# Browse
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "method": "tools/call", "id": 1,
    "params": { "name": "list_template_library",
                "arguments": { "business_id": "bus_…", "type": "journey" } }
  }'

# Deploy
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "method": "tools/call", "id": 2,
    "params": { "name": "deploy_template",
                "arguments": { "business_id": "bus_…", "template_id": "lib_restaurant_welcome_v3" } }
  }'
```

See [Templates](templates) for full details on the library.

### Path 2 — Generate from natural language

Describe the campaign in plain text. Caramel generates a `draft` campaign using AI.

```bash
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "method": "tools/call", "id": 1,
    "params": {
      "name": "generate_campaign",
      "arguments": {
        "business_id":   "bus_01h7m9k0aabcdefghj1234567",
        "prompt":        "Re-engagement campaign for customers who haven'\''t visited in 90 days. Friendly tone. 15% discount in the third email.",
        "campaign_type": "journey"
      }
    }
  }'
```

This consumes AI credits. The campaign starts in `draft`. Refine it before deploying:

```bash
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "method": "tools/call", "id": 2,
    "params": {
      "name": "refine_campaign",
      "arguments": {
        "campaign_id": "cmp_01h7m9k0aabcdefghj1234567",
        "feedback":    "Make all subject lines under 8 words. Drop the third email."
      }
    }
  }'
```

Each `refine_campaign` call preserves conversation context and builds on the previous iteration. The campaign moves to `pending_review` once quality checks pass. A quality score below 0.65 keeps it in `pending_review` — refine to raise the score.

### Path 3 — Author in the dashboard

The **Journeys** builder in **Omnichannel** supports hand-authored campaigns. The public API does not expose journey-creation primitives directly. If your integration needs custom journeys, build them in the dashboard and reference them by `campaign_id`.

## Deploy

Once a campaign is `approved`, deploy it:

```bash
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "method": "tools/call", "id": 1,
    "params": {
      "name": "deploy_campaign",
      "arguments": {
        "business_id": "bus_01h7m9k0aabcdefghj1234567",
        "campaign_id": "cmp_01h7m9k0aabcdefghj1234567"
      }
    }
  }'
```

Deployment is atomic. Either the whole journey activates (entry segment, every step, every template) or none of it does. If a dependency check fails — for example, a referenced template is still in `draft` — you get an error and the campaign stays in `approved`.

## Pause and resume

```bash
# Pause
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -d '{ "jsonrpc":"2.0","method":"tools/call","id":1,
        "params":{"name":"pause_campaign","arguments":{"campaign_id":"cmp_…"}} }'

# Resume
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -d '{ "jsonrpc":"2.0","method":"tools/call","id":2,
        "params":{"name":"resume_campaign","arguments":{"campaign_id":"cmp_…"}} }'
```

While paused:

- Contacts already mid-journey stop progressing. No pending step fires.
- New contacts matching the entry segment do not enter.
- On resume, mid-journey contacts continue from where they stopped. New entrants start from step 1.

Pausing is the right tool for "fix a typo, then resume." Do not delete a paused campaign if you want to preserve in-progress contact state.

## Delete

Deletion is permanent and only allowed on `draft`, `approved`, or `paused` campaigns.

```bash
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -d '{ "jsonrpc":"2.0","method":"tools/call","id":1,
        "params":{"name":"delete_campaign","arguments":{"campaign_id":"cmp_…"}} }'
```

Deleting a `deployed` campaign returns `409 deployed_campaign` — pause first, then delete. When you delete a paused campaign, in-progress contact state is lost; remaining steps are never sent.

## Entry segments

Every journey has exactly one entry segment. A contact enters the journey when they first match the segment. Segment membership is evaluated continuously — a contact who gains a new tag or crosses a threshold re-enters matching journeys even if they've been in the audience for a long time.

Supported segment operators:

| Operator | Use case |
|---|---|
| `Equals` | Exact match (`tag == "vip"`) |
| `GreaterThanOrEqual` | Threshold (`total_spent >= 100`) |
| `Exists` | Presence check (`phone IS NOT NULL`) |
| `Within` | Recency window (`created_at WITHIN 7 days`) |
| `And`, `Or` | Composition |

> **Important** `Contains` and `GreaterThan` are not supported by the segment engine. Using them returns a `500` error and breaks referencing journeys. The dashboard's segment builder prevents this automatically.

## Per-tier step limits

| Tier | Max message steps per journey |
|---|---|
| Starter | 5 |
| Lite | 5 |
| Growth | 8 |
| Business | 12 |
| Enterprise | 20 |
| Lifetime | Unlimited |

Exceeding the limit causes `deploy_campaign` to return `422 validation`. Check current usage with `caramel.v1.meta.usage`.

## Next steps

- [Templates](templates) — the message content journeys send
- [Forms and audience](forms-and-audience) — how contacts enter entry segments
- [Tools reference](../reference/tools) — `generate_campaign`, `deploy_campaign`, `pause_campaign`, and more
