Frequency Caps
A frequency cap is a rule that says “don’t send this contact more than N messages of this kind within a rolling time window.” It exists to protect the two things over-messaging damages: the recipient’s patience (and their opt-out rate) and your sender reputation with carriers. Frequency caps are enforced centrally, inside the message-send pipeline, so the limit holds regardless of which campaign, flow, or API call is trying to send — a contact who already hit today’s cap from Campaign A is also blocked from Campaign B. For the full endpoint list and request schema, see the Frequency Caps API reference.When to use a frequency cap
Use a frequency cap when the risk is too many sends to the same person, not a duplicate message body (that’s message suppression) and not a hard channel block (that’s an opt-out). Typical rules:- “No more than 3 marketing SMS per contact per day.”
- “No more than 1 promotional WhatsApp message per contact per week.”
- “No more than 5 messages per contact per day, across every channel combined.”
Channels and scope
Every cap counts sends on one of these channels, or across all of them:sms, whatsapp, email, voice, rcs, viber, line, messenger, instagram, push, telegram, in_app
A cap’s scope decides what counts toward its limit:
Evaluation is shared across the whole
/api/v1/frequency-caps surface: the rules you GET /api/v1/frequency-caps are exactly the rules the send pipeline evaluates, and both the dashboard (Settings → Frequency caps) and the API read them through the same definitions. Two constraints on the schema to know about up front:
- A
globalcap must omitchannel— the API stores it as the*wildcard and rejects a create that names both (validation error:channel must be omitted when scope='global'). - A
channel-scoped cap requireschannel— omitting it with the default scope is a validation error.
Categories: keyless means fire-on-every-send
applies_to_categories is the only filter between a cap and the sends it gates:
applies_to_categoriesabsent (or null) — the cap applies to every send to the recipient, no matter the category. OTPs, receipts, drip steps, reactivation offers: all of them consume slots.applies_to_categories: ["marketing"]— the cap only fires on sends whosecategorymatches one of the listed values. A send with no category, or withcategory: "transactional", ignores this cap entirely.
Creating a cap
Managing rules
The full surface is plain REST over the org’s rule list:
Writes require an owner, admin, or developer role; reads only need a valid API key. Changes propagate within about 30 seconds — rule changes invalidate a short-lived rule cache, so a cap you just disabled stops gating within half a minute, not per-send.
When a cap is hit: FREQUENCY_CAP_EXCEEDED
A direct API send that would exceed an active cap is rejected with HTTP 429 and never dispatched — it does not queue, does not retry, and does not burn a slot against any other cap. The error body pinpoints the rule that fired:
details.cap_id— which rule blocked the send.GET /api/v1/frequency-caps/:idwith this id to see (and adjust) the full rule.details.retry_after_seconds— how long until the oldest in-window send ages out and a slot re-opens. Treat it like aRetry-After: wait this many seconds before retrying this recipient, not a fixed backoff.details.channel/window_seconds/max_count— a copy of the rule’s shape, so you don’t need a second round-trip to log what fired.
code === "FREQUENCY_CAP_EXCEEDED", mark the recipient as deferred until retry_after_seconds elapses, and move on to the next recipient. Do not treat it as a delivery failure or a provider error — the carrier never saw the message, and re-POSTing immediately will 429 again.
Campaign sends behave differently by design: a capped campaign recipient reports back status: "skipped", reason: "frequency_capped" (with the same frequency_cap_id and retry_after_seconds fields) instead of failing the batch, so one capped contact never stops the rest of the audience from sending.
Where the cap sits relative to opt-outs and suppression
Three independent controls answer three different questions, and they run in a fixed order inside the send pipeline:
Ordering matters for your cap budget. An opted-out contact is blocked at step 1, so their sends never reach the cap. A send suppressed as a duplicate at step 2 is skipped without consuming a cap slot — the suppression hit means the cap counter doesn’t move, which is exactly what you want: duplicates can’t burn the allowance you reserved for fresh messages. Conversely, a capped send also consumes nothing: on the deny path, no slot is recorded against any cap, so a rejected send can’t push a contact over the very limit that rejected it.
One nuance on the check itself: slot consumption is atomic with the check — two concurrent sends to the same contact can’t both slip past a
max_count: 1 rule, and a send rejected by one cap never claims slots on the other caps it passed.
Bypassing the cap for transactional sends
Tag time-sensitive sends — OTPs, receipts, appointment reminders — withcategory: "transactional" on the send request, and scope your caps to applies_to_categories: ["marketing"] (or similar) so those caps never fire on them. A cap with no applies_to_categories applies to every category, transactional included, so be deliberate about scoping caps you only intend for promotional traffic.
Playbooks
Marketing-only daily SMS cap
The most common configuration: 3 marketing SMS per contact per rolling day, transactional untouched.transactional (or untagged) never counts.
Global 24-hour hard ceiling
One rule that caps a contact at 5 messages per day no matter which channel — the anti-fatigue ceiling for orgs that send SMS + email + push in parallel:channel is omitted entirely — it comes back as "*" in the response. Because the counter aggregates across channels, a contact who got 3 marketing SMS and 2 marketing emails today is at their ceiling: the next marketing push notification is rejected with FREQUENCY_CAP_EXCEEDED even though the push channel itself has no rule.
Per-category mixing: marketing capped, transactional separate
Two rules side by side, each clean:Drip campaigns and flows
A drip campaign (see campaign-end-to-end) sends through the same pipeline as everything else, so caps apply with no campaign-side configuration — but the recipient experience is a skip, not an error:- Each drip step evaluates the caps at send time, one recipient at a time. A capped recipient is reported as
status: "skipped", reason: "frequency_capped"and the batch moves on; your campaign-level budget counters refund the skipped send, so capping a recipient never bills you. - Because caps are rolling windows, the same recipient may succeed on the next drip step a day later — delays between steps are what lets their window reopen. If you see large
frequency_cappedskip counts on a drip, either spread the steps wider or loosen the cap, don’t retry the step. - Before launching, use the audience preview’s advisory capped estimate (
frequency_cappedon the campaign preview response) to size how many sends the existing caps will hold back — it’s a snapshot, but it catches “my org-wide global cap will eat half this campaign” before you send.
See also
- Frequency Caps API reference — endpoint list and full field reference
- Message Suppression — content-hash duplicate suppression
- Opt-Outs API — per-channel opt-out records
- Campaign end-to-end — how caps surface as
skippedsends in campaigns and previews - Campaign A/B testing — how experiment cohorts (variants, hold-outs) interact with the same per-contact cap gates