Features
Campaigns and broadcasts
Sending one approved template to many contacts, and what the rules are.
A campaign sends one approved template to every contact in an address book, immediately or at a scheduled time. Use it for an announcement that goes to a list, a sale, a restock, a store closure, rather than a message triggered by one customer's order.
What has to be true first
- A registered number
- Unregistered numbers fail every send with Meta error 133010, and a campaign will record every contact as failed.
- An approved template
- Only templates Meta has marked
APPROVEDappear in the dropdown. There is no free-form option. A campaign is business-initiated by definition, so the 24-hour window is closed for almost every recipient. - A contact list with contacts in it
- An empty list is refused with
Contact list is empty. See Contacts and opt-outs. - Opt-in for marketing
- Meta's Business Messaging Policy requires prior opt-in for marketing templates. Sending to a bought list is what gets a number's quality rating downgraded and, eventually, blocked.
- The campaigns feature on your plan
- Campaign creation is gated per plan; a plan without it returns an error at creation rather than at launch.
How to set one up
Build the list first
Go to Contacts, create an address book, and add or import the recipients. Campaigns target a whole book. There is no filtering at launch time, so the book is the audience.Open Campaigns and choose New Campaign
Name it something you will recognise in the list later.Pick the sender number, then the template
The template dropdown stays disabled until a number is selected, because templates belong to a number. Only approved ones are listed, each with its language.Map the template variables
If the template contains{{1}},{{2}}and so on, a Map Template Variables panel appears with one row per placeholder. Each can be mapped to a contact field,{{first_name}},{{last_name}},{{full_name}},{{phone}},{{email}}, to any custom field you have defined, or to fixed text.Schedule it, or leave the date blank to send now
The schedule field will not accept a time in the past. A campaign with no date launches as soon as you save it.

How the substitution works
Each mapped placeholder is resolved per contact at send time from that contact's stored fields, with custom fields read from the contact's saved attributes. A contact with nothing in the mapped field gets whatever the substitution resolves to for them, so it is worth exporting the list and checking the column is populated before a large run.
What happens when it runs
Contacts are processed one at a time, in list order. For each contact:
- The campaign's status is re-read; a campaign cancelled mid-run stops here.
- Your account's sending limits are checked.
- Payment for the message is reserved, priced by the template's category and your plan's markup.
- The template is sent and a message log row written.
- The run pauses about 55 milliseconds, roughly 18 messages a second, well under WhatsApp's per-number ceiling.
A send that fails is counted as failed and its charge refunded immediately. The progress bar on the card shows sent and failed counts while the run is in flight.
The same rule in the API
POST /v1/notifications/send/batch behaves identically: recipients are processed sequentially and the batch stops at the first credit or sending-limit failure. Recipients after that point are absent from results entirely. They are not marked failed, they are not there at all.
const res = await fetch(BASE + "/v1/notifications/send/batch", {
method: "POST",
headers: { Authorization: "Bearer " + KEY, "Content-Type": "application/json" },
body: JSON.stringify({ event: "back_in_stock", recipients }),
});
const body = await res.json();
// success: true and HTTP 200 are returned even when every recipient failed.
if (body.results.length < recipients.length) {
const unattempted = recipients.slice(body.results.length);
// Re-queue these yourself once the balance is topped up.
}Campaign states
Statuses on the campaign card
| Field | Required | Type | Description |
|---|---|---|---|
| DRAFT | No | state | Created but not launched. |
| SCHEDULED | No | state | Waiting for its scheduled time. Launch Now runs it immediately instead. |
| RUNNING | No | state | In flight. Cancel stops it at the next contact; contacts already sent to are not recalled. |
| COMPLETED | No | state | Every contact was attempted. Some may still have failed, read the counts. |
| FAILED | No | state | The run was cut short by insufficient balance or a sending cap, so contacts remain unattempted. |
| CANCELLED | No | state | Stopped by hand. |
When it does not fire
- The template is not approved. It never appears in the dropdown, so the campaign cannot be created against it.
- The list is empty. Creation is refused outright.
- The number has no access token, or does not exist. Creation returns
Phone number not found or not configured. - Credits ran out part-way. Everything after that point is unattempted and the campaign is marked
FAILED. - A sending limit was reached. Same behaviour. The run stops rather than queueing.
- The campaign was cancelled. The loop checks the status before each contact, so cancelling takes effect within one message.
- Meta rejected the individual send. That contact is counted as failed, the charge is refunded, and the run continues to the next one.
Cost
Each recipient is priced and charged individually, using the real category of the chosen template, read from your template record rather than assumed. If the category is unknown, the run is priced as MARKETING, which is the expensive assumption on purpose: under-billing a blast and discovering it later is worse than the alternative.
Under the shipped default rate card a marketing message costs 8 credits to an Indian number and 18 elsewhere, against 1 and 6 for utility. A 5,000-contact marketing campaign to Indian numbers is therefore around 40,000 credits. Check the balance before you launch.
