Skip to main content

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 global cap must omit channel — 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 requires channel — 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_categories absent (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 whose category matches one of the listed values. A send with no category, or with category: "transactional", ignores this cap entirely.
The keyless behavior trips people up in one specific place: a bare-and-blanket cap created “just to throttle marketing” with no category filter fires against your transactional traffic too. A contact who receives three password-reset OTPs inside the window can get their fourth transactional send rejected by a cap you thought was marketing-only. Scope your caps deliberately — either list the categories you actually want to throttle, or keep the blanket cap but size it so transactional traffic can never hit the ceiling (see the playbooks below).

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:
Read it as:
  • details.cap_id — which rule blocked the send. GET /api/v1/frequency-caps/:id with 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 a Retry-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.
Handle the 429 in your send loop: catch 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 — with category: "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.
The category list covers both labels your campaigns use; an OTP tagged 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:
Note 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:
Each send evaluates only the rule(s) whose category list includes its own category — the two counters never interfere, and a busy marketing day can’t push a receipt over the transactional backstop (or vice versa).

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_capped skip 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_capped on 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