Keepable docs
Sender API

Campaigns

Promotional banners and info-boxes that ride alongside delivered mail, targeted by tag, scheduled, reviewed before they go live, and measurable, without becoming a content type.

A campaign is a promotional overlay (a banner or info-box) that Keepable renders above or below a delivered mailpiece. It is not a content type and it is not a separate delivery. You author a campaign once, target it with a tag, and any mail that carries that tag picks it up at render time.

A campaign never changes what you send. A delivery opts into a campaign by carrying a campaign_tag in its metadata (on a single send, a batch, or an ingest job), never by setting content_type: campaign. There is no campaign content type.

Campaigns use their own scopes, separate from content delivery: campaigns.write to author and run them, campaigns.read to read them and their metrics. See Authentication.

Lifecycle

A campaign moves through a small, explicit set of states. You only ever change state through the dedicated endpoints: PUT /campaigns/{id} replaces the config but never the status.

StatusMeaning
draftCreated and editable. Not shown to any recipient yet.
pending_reviewSubmitted with publish; waiting on Keepable's human review gate.
scheduledApproved and live, eligible to render on tagged mail.
pausedTemporarily withdrawn from rendering; can be resumed by publishing again.
archivedTerminal. Immutable; writes return 404.
draft ──publish──▶ pending_review ──review──▶ scheduled
  ▲                                              │
  └──────────────── pause ◀─────────────────────┘

                   (archive ─▶ archived, terminal)

Every campaign goes through review before it can render. This is a deliberate guardrail: promotional content is held to a moderation gate (and DPO/counsel sign-off) before it can appear next to official mail.

Create a campaign

POST /campaigns creates the campaign in draft. The new id is also returned in the keepable-campaign-id header.

Create a campaign
curl https://api.keepable.co/campaigns \
  -H "Authorization: Bearer $KEEPABLE_TOKEN" \
  -H "Keepable-Version: 2026-05-24" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "June savings push",
    "placement": "bottom",
    "cta_text": "See offers",
    "cta_url": "https://example.com/offers",
    "targeting": { "tag": "june-savings" }
  }'
FieldRequiredNotes
nameyesInternal label for the campaign.
placementyesWhere the banner renders relative to the mailpiece: top or bottom.
cta_textnoThe call-to-action label.
cta_urlnoWhere the CTA points. Clicks are tracked through a Keepable redirect.
targeting.tagyesThe tag that joins this campaign to deliveries. A mailpiece carrying metadata.campaign_tag equal to this value picks up the campaign.

Banner imagery is added in a second step with POST /campaigns/{id}/assets (PNG/JPEG); list and remove them under the same path. Read engagement with GET /campaigns/{id}/metrics.

Targeting a delivery

To put a campaign in front of a recipient, tag the mail. The campaign's targeting.tag and the delivery's metadata.campaign_tag must match.

A tagged single send
{
  "recipient": { "identifier_type": "nin", "identifier": "12345678901" },
  "subject": "Your March statement",
  "content_type": "statement",
  "generated_at": "2026-05-24T09:00:00Z",
  "metadata": { "campaign_tag": "june-savings" },
  "parts": [ { "name": "statement.pdf", "media_type": "application/pdf", "data": "JVBERi0xLjcK..." } ]
}

The same metadata.campaign_tag works on a batch send (Mode A metadata, or a Mode B manifest column), so a whole run can opt into a campaign at once.

Guardrails

Campaigns sit next to official, identity-anchored mail, so they are deliberately constrained:

  • Recipients can opt out of promotional banners, and the opt-out is honored at render time. A banner is never shown to a recipient who declined.
  • Identity-critical mail is protected. Banners are not rendered on the most sensitive types (statements, payslips, agreements) by default.
  • Every go-live is reviewed. publish moves the campaign to pending_review; it cannot render until a human approves it.
  • Clicks are tracked server-side through a redirect, so the cta_url destination never sees Keepable identifiers.

Because targeting is by tag, a typo in campaign_tag simply means the mail renders with no banner. It is never an error. Keep the tag a stable constant in your sender code, set once where you build the metadata.