Skip to main content

Preflight review of a campaign audience

An audience is approved for launch by looking at it, not by hoping the send resolves the way you drew it up. POST /api/v1/campaigns/audience/preview is the instrument for that look: feed it the same audience shape the campaign carries and it returns the gross match count, a 10-contact sample, and — when you pass a channel — the net projection the launch will actually attempt. This page walks one campaign through that review: a summer-sale SMS blast built on a static list. The preview is a decide step, not a discover step. Send a campaign end-to-end covers the full lifecycle where this review sits; Outbound compliance pre-flight checklist covers the gate chain (wallet, opt-out sync, quiet hours, frequency caps) that runs after the audience itself is sound. Consensus between those three passes is what clears launch.

1. Why preflight beats post-launch debugging

Post-launch, a bad audience surfaces as absence: rows that never appear in the send log, a stat total that visibly under-delivers, or a suppression cohort you learn about once the ledger has already counted them. At that point the campaign state is real — credits committed (or quietly not spent), and the debugging happens against launch artifacts whose zeroes are ambiguous (was the row suppressed, opted out, address-less, or capped?). Preflight, the same defects are cheap and disambiguated. The preview is read-only: it mutates nothing, touches no wallet, enqueues nothing. Veto a bad list and the cost is an edited CSV and a re-check; veto three hours later and the cost is a launch artifact to explain. Run the review while the campaign is still a draft and the cost of a wrong audience stays at zero.

2. Run the preview and read the net projection

Post the same audience shape the campaign carries. For the summer-sale blast, the audience is a static list named list_summerVIPs and the channel is SMS:
A healthy response for this audience looks like:
matching_count is the gross match — the audience shape on its own, before any channel exclusions. Pass channel and the response adds the cohort block, the net projection that partitions the gross into the cohorts the launch will see: Four reading rules:
  • Read net, not matching_count, as the headcount. A healthy summer-sale audience yields most of its gross in net and a small suppressed / opted_out cohort. If net is materially below gross, the difference is assigned to specific cohorts in the response — that assignment is what remediates.
  • unreachable should trend to zero on a phone channel for a well-kept list. Non-zero unreachable means missing contact data, not a compliance block.
  • Expect suppressed + opted_out to be small but non-zero. A list that has ever been sent will carry some excludes; a perfectly clean pair is the anomaly to double-check, not the goal.
  • frequency_capped is advisory, not a gate. It is the estimate of sends your own configured caps will hold back — it never blocks launch, and a large value means “your cap policy is about to shave this many,” nothing more.
The same suppressed / opted_out / unreachable / deliverable math reappears in the dry-run (audience.* buckets), so the preview’s reading carries forward verbatim into the end-to-end gate — the reconciliation stays exact.

3. The five-minute cache in the render loop

Results are cached for five minutes on a hash of the criteria — cached: true on a hit. Two consequences for the wizard render loop:
  • Re-rendering the same drawer is free. Repeated preview calls with the same audience shape replay the cached projection instead of re-evaluating, so the preview card can refresh on every keystroke without paying for a resolution pass.
  • Edits break the cache. Change the criteria — a different audience_id, a regenerated csv_recipients, an added contact_ids member — and the hash changes, so the next call re-evaluates live. If you’re troubleshooting and a fix you just made doesn’t move the numbers, confirm the criteria actually changed before assuming the cache is stale; identical criteria within the five-minute window is a cache hit by design.

4. The matching_count = 0 fix ladder

A zero gross match means the audience shape itself resolved to nothing — before any suppression, opt-out, or address math ran. Work the ladder top to bottom; each rung names one cause and its one fix. Rung membership matters: a zero matching_count is never a compliance condition. Suppression and opt-outs reduce net from a healthy gross, they never zero matching_count — so a zero is a roster problem with one of the five fixes above, not a remediation of the block list.

5. Where this sits in the launch path

The audience review is the first gate, not the only one. Two hand-offs close the path:
  • Back into Send a campaign end-to-end §3 — that guide runs this preview as step 3 of the lifecycle and immediately consumes the cohort counts in the dry-run; return there once the audience reads clean.
  • Across to Outbound compliance pre-flight checklist — a healthy audience still walks the admission chain; the checklist then owns the wallet posture, out-of-band opt-out sync, quiet hours, and frequency-cap posture this page deliberately left aside.
Run both and the campaign launches against evidence, not assumption.