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 and
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.
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 is included on Pro numbers. The check runs against the numbers the campaign can actually send from: the numbers it pins withsenderConfig.sessionIds, or every number in its project when it pins none.
When the project has no numbers yet, the project’s default plan is used, because
that is the plan the next number linked to it inherits. The check runs three
times:
The launch check is not the create check remembered. A campaign drafted while
the numbers were on Pro is refused at launch if they moved to another plan in
between, and a running campaign whose numbers all lose Campaigns pauses instead
of continuing against a pool that can no longer send. Move the numbers back to
Pro and 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 when you send it, at create and at launch, with
400 campaign_throughput_capped naming the field:
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 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.Opted-out numbers are never messaged
A campaign checks your team’s opt-out list twice. When the campaign launches, every recipient on the list is markedskipped 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 carries409 bansafe_cold_held.
Two things change the outcome:
- Set
senderConfig.throughput.coldSendPolicytoallowto message people who have not written first. This raises the number’s ban risk; the default ishold. - 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.
coldSendPolicy. See
Restrictions 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:
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 tocancelling, 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’sperNumberDailyCap over warmupDays days, in a straight line. With the
defaults — warmupDailyStart 20, perNumberDailyCap 500, warmupDays 14 —
the ceiling on a day is:
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.
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.