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

# Campaigns and number safety

> What a campaign checks before it sends: the plan gate, the throughput ceilings, the opt-out list, per-recipient holds, and the warm-up ramp for a new number.

Campaigns is the only bulk sending path in Polymorfa. A loop over
`POST /api/messages/send` opens the `bulk_outside_campaigns` finding on the
number that makes it, so outreach to people who have not written first belongs
in a campaign, where the pacing, the per-number caps, the opt-out list, and the
contact check are applied for you. See [Findings](/guides/safety/findings) and
[Send a campaign](/guides/campaigns/send-a-campaign).

This page states every check a campaign send passes and the exact refusal you
get when one fails. Each code is listed in
[Errors](/api/errors#bansafe-and-safe-mode-codes).

Both public surfaces run these checks. A campaign behaves the same whether you
created and launched it through `/messaging/projects/{projectSlug}/campaigns` or
through `/platform/campaigns`.

## Campaigns is a Pro capability

Campaigns send from numbers on a plan that includes Campaigns. You can create
and edit a draft on Free or Standard, and a draft can select numbers on any
plan. In the console, Free and Standard numbers appear disabled in the sender
selection, with an upgrade action.

At launch, every number selected with `senderConfig.sessionIds` must be in the
project, connected, and on a plan that includes Campaigns, and all selected
numbers must use one sending type. A campaign that selects no numbers draws
only from eligible numbers in its project, and its project must have at least
one number on a plan that includes Campaigns. It also sends through one sending
type: WhatsApp numbers when any are ready, otherwise Cloud API numbers, otherwise
test numbers.

| When | What happens |
| - | - |
| You create or edit the draft | The draft is saved on any plan; no message is sent |
| You change the selection after launch | The launch checks run on the new selection, including for a scheduled campaign that has not started |
| You launch it | `402 campaigns_not_entitled` when a selected number is missing or its plan does not include Campaigns, or when a campaign without a selection has no such number in its project. `400 invalid_parameter` when a selected number is not connected or the selection mixes sending types |
| While it is running | The campaign pauses when a selected number that is ready to send has lost Campaigns, or when no number in an unselected pool has Campaigns. `campaign.paused` carries the reason and the code `campaigns_not_entitled`. Upgrade the number or change the selection, then resume |

A selected number that disconnects or is cooling down while the campaign runs is
skipped until it is ready again; the campaign does not pause for it. If that
number also lost Campaigns, the campaign pauses once it is ready to send again.
If a selected number is removed from the project, or selected numbers end up
on different sending types (for example after a number moves to Cloud API), the
campaign pauses and `campaign.paused` carries the code `invalid_parameter`.
Change the selection, then resume.

A number whose plan cannot be resolved is treated as not entitled. The gate
fails closed.

## Throughput limits

`senderConfig.throughput` sets the campaign's pacing. A value above its limit
is refused with `400 campaign_throughput_capped` naming the field. The
per-number caps are checked when you save the draft and again at launch. The
rate limit depends on the selected numbers' plans, so it is checked at launch:

| Field | Default | Limit |
| - | -: | - |
| `ratePerMinute` | 20 | At most 6,000, the Pro plan's throughput |
| `perNumberHourlyCap` | 60 | At most 300 |
| `perNumberDailyCap` | 500 | At most 2,000 |
| `jitterMinMs` | 800 | At least 800 |
| `jitterMaxMs` | 3,500 | Above `jitterMinMs` |
| `ackTimeoutMs` | 5,000 | — |
| `coldSendPolicy` | `hold` | `hold` or `allow` |

A campaign that is already running is brought down to the limit rather than
stopped, so a change mid-flight slows the campaign instead of breaking it. The
caps are per number, not per campaign: two campaigns sharing a number share its
caps. While a number is paced by a restriction, the campaign sends at the lower
of its own rate and the paced rate, which
[Restrictions](/guides/safety/enforcement#pacing-rates) lists.

## Every campaign send is typed and paced

A send that comes from a campaign always uses at least a typing indicator
before each text message and jittered pacing, whatever Safe Mode settings the
number carries. A campaign never raises the number's presence or read-receipt
settings, because those are visible to your contacts in ways typing and pacing
are not.

The pause a send takes is bounded by the campaign's own gap between recipients,
so pacing never reduces the throughput you configured. See
[Safe Mode](/guides/safety/safe-mode).

## Opted-out numbers are never messaged

A campaign checks your team's [opt-out list](/guides/campaigns/opt-outs)
twice. When the campaign launches, every recipient on the list is marked
`skipped` with `lastError` `opted_out` and counted once in `skippedCount`. At
send time, a number on the list is never claimed, so a number added to the list
after the campaign launched is still suppressed, and re-queuing a skipped
recipient does not send to it.

## Recipients who never wrote first are held

The contact check is made per recipient, at send time, on the number that is
about to send. A recipient who has never written to that number is held rather
than messaged, and the hold carries `409 bansafe_cold_held`.

Two things change the outcome:

* Set `senderConfig.throughput.coldSendPolicy` to `allow` to message people who
  have not written first. This raises the number's ban risk; the default is
  `hold`.
* Send to people who wrote to that number. The check is per number, so a
  contact who wrote to one of your numbers is still cold on another.

The hold is not a failure of the number. It is a fact about the recipient list.
It applies today, on every plan that includes Campaigns, and it does not depend
on the restriction ladder: it is your campaign's own `coldSendPolicy`. See
[Restrictions](/guides/safety/enforcement) for the ladder itself and how each
step lifts.

## What safety does to a recipient

A campaign send passes the number's restriction step as well as the checks
above. What happens to the recipient depends on which check stopped it:

| What stopped the send | The recipient | The campaign |
| - | - | - |
| The recipient is on the team's opt-out list | Counted once in `skippedCount`, never attempted, with `lastError` `opted_out` | Keeps running |
| The recipient has never written to this number and `coldSendPolicy` is `hold` | Counted once in `skippedCount`, never attempted again, and reported by `campaign.cold_blocked` | Keeps running |
| The sending number is paced by a restriction | Queued again and retried after the reported delay, with no delivery attempt spent and nothing counted as a failure | Keeps running, and `campaign.throttled` reports the pass |
| The sending number or the whole workspace is suspended, the plan no longer includes Campaigns, or the requested throughput is above the safety ceiling | Left queued, with the attempt given back so no retry budget is spent | Paused, with `campaign.paused` carrying the reason and the code |

A recipient safety holds back is never a delivery failure. `failedCount`
counts sends WhatsApp refused, not sends Polymorfa never made.

## Stopping and deleting

Stop always means cancel. It moves a draft, running or paused campaign to
`cancelling`, then to `cancelled` once sends already in progress finish, and the
recipients not yet sent are not sent. Stop is never blocked by a workspace
suspension or by a lost plan entitlement, so a campaign a check paused can
always be stopped.

Delete removes a `draft`, `completed`, `failed`, `cancelled` or `archived` campaign. A
campaign that is still live, including a draft with a scheduled launch, is
refused with `409 state_conflict`: stop it first, then delete it.
If a completed or failed campaign still has a final event to deliver, delete or
archive returns `409 state_conflict`. Retry after that event is delivered.

## A new number ramps up

A number's daily cap starts low and rises to the campaign's
`perNumberDailyCap` over `warmupDays` days, in a straight line. With the
defaults — `warmupDailyStart` 20, `perNumberDailyCap` 500, `warmupDays` 14 —
the ceiling on a day is:

| Day | Daily cap | Day | Daily cap |
| -: | -: | -: | -: |
| 0 | 20 | 7 | 260 |
| 1 | 54 | 8 | 294 |
| 2 | 88 | 9 | 328 |
| 3 | 122 | 10 | 362 |
| 4 | 157 | 11 | 397 |
| 5 | 191 | 12 | 431 |
| 6 | 225 | 13 | 465 |
| | | 14 and after | 500 |

A number is treated as new for its first 14 days here and in the
`warmup_ignored` finding, which starts at the same 20 messages a day. The ramp
alone does not keep that finding closed: check it on the number while a new
campaign runs. See [Findings](/guides/safety/findings).

The ramp is anchored to how old the number is, not to when it first ran a
campaign: Polymorfa uses the earliest instant it can prove the number was in
use, such as when the number was linked or the oldest message its history
carries. The anchor only ever moves earlier, so a number that is already warm
cannot be pushed back onto the ramp. A number whose age cannot be established
is treated as warm and sends at the full cap.

While a number is on the ramp, recipients beyond the day's cap are not dropped.
They stay queued for the next window, and `campaign.cap_reached` reports the
number, the cap, and when the window resets.

WhatsApp can also cap how many new chats a linked-device number starts in a
cycle. When WhatsApp has capped a number, the number leaves the pool until the
cycle resets and `campaign.cap_reached` reports `capType` `new_chat`. See
[WhatsApp new-chat cap](/guides/numbers/new-chat-cap).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.