> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp: templates and campaigns

> Manage Meta-approved HSM templates and send bulk WhatsApp campaigns to CSV recipient lists from the Jelou CLI.

The CLI covers the outbound WhatsApp surface: **templates** (reusable HSM
messages that Meta must approve to send outside the 24-hour window)
and **campaigns** (bulk sends to recipient lists using an approved
template).

<Note>
  The `--bot-id` these commands ask for is the `referenceId` that appears in
  `jelou channels list --type Whatsapp`.
</Note>

## `jelou channels testers`

Before connecting a real WhatsApp Business number, you can use the shared
Jelou sandbox to test sending and receiving messages with your own phone.

| Subcommand                                          | Description                                           |
| --------------------------------------------------- | ----------------------------------------------------- |
| `testers list <channel-id>`                         | Lists whitelisted tester phone numbers on the sandbox |
| `testers add <channel-id> --phone <phone>`          | Whitelists a phone number on the sandbox              |
| `testers remove <channel-id> --phone <phone> --yes` | Removes a phone number from the sandbox               |

```bash theme={null}
jelou channels testers list channel-id-xyz
jelou channels testers add channel-id-xyz --phone +14155550100
jelou channels testers remove channel-id-xyz --phone +14155550100 --yes
```

<Note>
  `<channel-id>` is the channel's own id, not the `--bot-id`/`referenceId` used
  by `template` and `campaign`. It only applies to sandbox channels — once you
  connect a real WhatsApp Business number, the tester whitelist is no longer
  needed. The phone must be in E.164 format (`+14155550100`).
</Note>

## `jelou template`

| Subcommand                        | Description                                           |
| --------------------------------- | ----------------------------------------------------- |
| `list --bot-id <id>`              | Lists templates (filters: status, type, category)     |
| `get <id> --bot-id <id>`          | Shows a template with its body and parameters         |
| `validate <file>`                 | Validates a template payload locally                  |
| `create --bot-id <id> …`          | Creates (and optionally submits to Meta) a template   |
| `update <id> --bot-id <id> …`     | Updates an APPROVED or REJECTED template              |
| `delete <id> --bot-id <id> --yes` | Deletes a template                                    |
| `upload-media <file>`             | Uploads an image/video to the CDN and returns its URL |

```bash theme={null}
jelou template list --bot-id bot-abc123 --status APPROVED --category UTILITY --type text
jelou template create --bot-id bot-abc123 \
  --display-name "Order confirmation" \
  --element-name order_confirmation \
  --template "Hi {{1}}, your order {{2}} is ready." \
  --language es --category UTILITY --type text
jelou template create --bot-id bot-abc123 ... --draft   # save without submitting to Meta
jelou template create --bot-id bot-abc123 --from-file ./template.json   # full payload as JSON
jelou template upload-media ./header.png
```

Categories: `UTILITY`, `MARKETING`, `AUTHENTICATION`. Statuses: `APPROVED`,
`PENDING`, `REJECTED`. Content types (`--type`): `text` (default), `IMAGE`,
`VIDEO`, `DOCUMENT`, `CAROUSEL`. Use `--header` to add a text header, and
`{{1}}`, `{{2}}`, … as placeholders in the body. Just like `campaign create`,
`--from-file` takes the path to a JSON file with the full template payload
instead of passing every flag individually.

<Warning>
  Only `APPROVED` or `REJECTED` templates can be edited; `PENDING` ones are
  locked. Templates without `--draft` go to Meta immediately, and the approval
  quota is finite per company (rejections count). Meta prepends a random
  4-character prefix to the `elementName` — use the returned `elementName`
  (with prefix) in campaigns.
</Warning>

### Updating a template

`jelou template update` only accepts discrete flags: `--display-name`,
`--template`, `--header`, `--footer`, `--media-url`, and `--draft`. The CLI
fetches the template's current state, applies only the overrides you pass,
and PATCHes the full payload — you don't need to repeat fields that stay the
same.

```bash theme={null}
jelou template update template-id-xyz --bot-id bot-abc123 \
  --template "Hi {{1}}, your order {{2}} just shipped." \
  --footer "Support team"
jelou template update template-id-xyz --bot-id bot-abc123 \
  --footer "New footer" --draft   # save without resubmitting to Meta
```

### Header with media

```bash theme={null}
URL=$(jelou template upload-media ./banner.png --json | jq -r '.url')
jelou template create --bot-id bot-abc123 \
  --display-name "Weekly offer" --element-name weekly_offer \
  --template "This week: {{1}} with {{2}}% off" \
  --media-url "$URL" --category MARKETING
```

## `jelou campaign`

| Subcommand                   | Description                                                                          |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `list`                       | Lists campaigns (filters: bot, status, type, template name, elementName, date range) |
| `get <id>`                   | Detail of a campaign (recipients and schedule)                                       |
| `create --from-file <file>`  | Creates a bulk campaign from a local JSON + CSV                                      |
| `cancel <id>`                | Stops a SCHEDULED or IN\_PROGRESS campaign                                           |
| `reschedule <id> --at <iso>` | Reschedules or revives a cancelled campaign                                          |

```bash theme={null}
jelou campaign list --bot-id bot-abc123 --status SCHEDULED --json
jelou campaign list --type text --name "Order confirmation" --element-name xy12_order_confirmation
jelou campaign create --from-file ./campaign.json --json
jelou campaign cancel campaign-id-xyz
jelou campaign reschedule campaign-id-xyz --at "2026-06-20T15:30:00-05:00"
jelou campaign reschedule campaign-id-xyz --at "2026-07-01T09:00:00-05:00" --revive
```

`--type` filters by campaign type (`text`, `carousel`, …), `--name` by the
template's display name, and `--element-name` by its `elementName`.

Statuses: `SCHEDULED` → `IN_PROGRESS` → `COMPLETED`; `CANCELLED` is terminal
unless you use `--revive`.

<Warning>
  `--start-at` and `--end-at` filter by the campaign's **creation** date
  (`createdAt`), not its scheduled send date (`scheduledAt`). If you're looking
  for "campaigns scheduled for next week", these filters won't get you there —
  they return campaigns created in that range, regardless of when they're
  scheduled to send.
</Warning>

### Campaign file structure

```json campaign.json theme={null}
{
  "campaignName": "Seasonal blast",
  "botId": "bot-abc123",
  "elementName": "xy12_holiday_offer",
  "language": "es",
  "csvPath": "./recipients.csv",
  "date": "2026-06-15T10:00:00-05:00",
  "params": [
    { "param": 1, "column": "name" },
    { "param": 2, "column": "discount" }
  ]
}
```

```csv recipients.csv theme={null}
phone_number,name,discount
+14155550100,Alice,20
+14155550101,Bruno,30
```

The CSV includes a phone column in E.164 format. The parameter mapping
is explicit: `{ "param": <n>, "column": "<column>" }`. Use the `date` field
to schedule (omit it for immediate sending).

<Warning>
  A campaign consumes real credits when dispatched. Review the recipient
  count and cost before creating it. A wrong parameter mapping
  sends the literal placeholder text (`Hi {{1}}`) to thousands of people —
  verify first. Cancelling an `IN_PROGRESS` campaign produces a partial send.
</Warning>
