Campaigns
"New bags are in" to everyone tagged interested: bags. A workflow answers something a customer did; a campaign is you deciding to speak first, to many people at once.
Owners only
Writing, sending, stopping and archiving a campaign need the owner role - a campaign messages many customers at once, and every message is billed by Meta. Anyone in the workspace can read campaigns and how they went.
Who it reaches
Everyone with a tag, or every customer. Each message goes through the same checks as one sent by hand or by a workflow, so a campaign can never reach someone a person could not:
- customers who opted out are skipped, always;
- for a marketing template, customers with no opt-in on record are skipped - a utility template does not need one;
- customers whose conversation a person has taken over are skipped until it is handed back;
- nobody receives the same campaign twice.
Ask before you send - the answer is counted the way the send decides:
curl "https://api.engentra.io/v1/campaigns/audience?tag=vip&template=new_stock" \
-H "Authorization: Bearer $TOKEN"| Field | Type | Notes |
|---|---|---|
total | number | Customers with the tag (or all of them). |
noWhatsApp | number | No WhatsApp number on record. |
optedOut | number | Said STOP. |
noOptIn | number | No opt-in on record - counted for marketing templates only. |
reachable | number | Everyone else. |
Write it, then send it
POST /v1/campaigns
{
"name": "New bags in stock",
"template": "new_stock",
"variables": { "1": { "from": "customer.name", "fallback": "there" } },
"tag": "interested: bags"
}That saves a draft; nothing is sent. Variables work as in a workflow's send step, except there is no event behind a campaign - each one is a customer field (customer.name, customer.phone, customer.email, customer.externalId) or fixed text. Change a draft with PUT, delete it with DELETE.
POST /v1/campaigns/{id}/schedule
{ "scheduledAt": "2026-09-20T09:00:00+01:00" }Leave the body out to start within a minute, or give a time up to 90 days ahead. Who receives it is decided when it starts, not when you schedule it - someone tagged in between is included, and someone who opts out in between is not messaged.
While it sends
Messages go out in batches, a batch a minute. The campaign pauses by itself - and carries on by itself - when:
- Meta rates your number yellow or red and the template is marketing (
pausedReason: quality). Sending more promotions then is how a number loses the right to send them; - Meta pauses or rejects the template (
template), or the WhatsApp channel is disconnected (channel).
POST /v1/campaigns/{id}/cancel stops it: customers not reached yet are skipped, and messages already sent stay sent. It cannot be restarted.
Getting one out of the way
POST /v1/campaigns/{id}/archive hides a campaign from the list. POST /v1/campaigns/{id}/unarchive brings it back, with whatever status it finished on - archiving is not a status of its own. GET /v1/campaigns?archived=true returns both.
Archiving changes the list and nothing else. The recipients, the delivery figures and every sale credited to the campaign stay exactly as they are, so a number you have already reported does not move because somebody tidied up. A campaign still going out is refused: stop it first, so nothing is hidden while it is still sending.
Delete is for drafts, archive is for everything else
DELETE /v1/campaigns/{id} works on a draft and nothing else, and that is deliberate. A campaign that has sent owns the recipient rows its results are built from, and those rows are what attribute a sale back to it. Deleting one would not tidy a list, it would remove revenue from your results. Archive it instead.
Never sent twice, sometimes not sent at all
Each customer is marked before their message goes to Meta. If the connection fails in a way that leaves it unclear whether the message went out, that customer is left as it is and not tried again - the count shows them as unknown. A missed promotion is invisible; a duplicate one is billed and annoys the customer.
How it went
GET /v1/campaigns/{id} returns progress - sent, skipped, pending and unknown - and results: how many messages were delivered and read, how many customers replied within 7 days, the sales marked for them in those 7 days, and the most common reasons for skipping. Replies and sales are credited by timing, like a workflow's.
Next
Suppression and consent →
The rules every campaign message passes.
Sending to a segment, not just a tag
A tag reaches everyone you have labelled. A segment reaches everyone who meets a condition right now - “bought once and not since”, “asked about something in the last fortnight and did not buy” - without anybody having to tag them first, and without the tag being stale by the next day.
{
"name": "Win back",
"template": "win_back",
"tag": "vip",
"conditions": [
{ "field": "last_order", "op": "older_than_days", "value": "60" },
{ "field": "orders", "op": "at_least", "value": "1" }
]
}The conditions are the same ones a trigger rule watches for, read by the same whitelist and compiled by the same code - so a segment means the same thing whether it is being watched for on a timer or sent to this afternoon. At most eight, ANDed with each other and with the tag.
POST /v1/campaigns/audience counts a segment before you save it, the way the send will choose: the preview and the recipient list come from one clause, because a campaign reaching people the preview said were not in it is the one failure nobody would think to look for.