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.
| Status | Meaning |
|---|---|
draft | Created and editable. Not shown to any recipient yet. |
pending_review | Submitted with publish; waiting on Keepable's human review gate. |
scheduled | Approved and live, eligible to render on tagged mail. |
paused | Temporarily withdrawn from rendering; can be resumed by publishing again. |
archived | Terminal. 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.
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" }
}'| Field | Required | Notes |
|---|---|---|
name | yes | Internal label for the campaign. |
placement | yes | Where the banner renders relative to the mailpiece: top or bottom. |
cta_text | no | The call-to-action label. |
cta_url | no | Where the CTA points. Clicks are tracked through a Keepable redirect. |
targeting.tag | yes | The 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.
{
"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.
publishmoves the campaign topending_review; it cannot render until a human approves it. - Clicks are tracked server-side through a redirect, so the
cta_urldestination 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.
Content types in depth
The content types the send endpoint accepts, their stable vs preview status, and the structured attributes each carries. Grounded in the Nigerian market.
Agreements
Run e-signature workflows. Send a document to one or more signers, track signatures as they land, and download a tamper-evident PAdES-LTV covenant when everyone has signed.