Message Suppression API
Configure content-hash duplicate suppression: a send is silently skipped when the same message body already reached the same contact on the same channel inside a window you choose. This is the “two different campaigns fired the identical promo back-to-back” guard — distinct from frequency caps (which count send firings per contact) and opt-outs (which block a channel entirely). Enforcement happens inside the message send pipeline, before frequency capping, so a duplicate never burns a cap slot. A suppressed send reports as skipped (reasonduplicate_content) rather than an error, so campaigns and batches keep moving.
GET: returns the org’s single policy or null when none is set.
DELETE returns the same 204).
/api/v1/message-suppression
Authentication: API key (X-API-Key) or session JWT. Reads require any role; the upsert and delete writes require owner, admin, or developer.
Using the SDKs
client.request() escape hatch above. See the Python SDK.
Returns the typed ApiResponse envelope. See the SDK index at SDK quickstart.
Policy shape
A single opt-in policy per organization.Example — dedupe marketing sends within 7 days
GET)
applies_to_categories to marketing means OTP and transactional resends are never suppressed. Omit channels (or send null) to cover every messaging channel.
Suppression fails open: if no policy applies, or the backing store is briefly unavailable, the send is dispatched normally — a transient blip never silently swallows outbound traffic. Bodies are compared by SHA-256 of the whitespace-normalized text, so cosmetically-identical copy (reflowed spacing) collapses to the same marker while different case or wording does not.
Recipes — cookbook-style
Recipe 1: same body to the same recipient, twice inside the window
A marketing campaign fires; a rerun from a different campaign name, journey step, or operator action replays the same body. Suppression catches the second send before it consumes a slot.status: "skipped" with either reason: "duplicate_content" (suppression) or reason: "frequency_capped" (inter-campaign cap skip, campaign sending) and can drop the row from the “true delivery” count.
Recipe 2: any channel blocked by opt-in (still respected)
Suppression is parallel to the opt-out gate, not instead of it. Aduplicate_content skip only happens when the contact is otherwise eligible; if they’ve opted out of the channel, the compliance gate blocks the send outright (no skipped reason for compliance — the send is rejected at the front).
applies_to_categories: ["marketing"] in your PUT body keeps transactional re-sends (password resets, receipts, OTPs) outside suppression’s scope while marketing traffic is still deduped.
Recipe 3: bulk on-call import + poll-and-verify
For bulk suppression-file imports (an on-call policy download that should become organization-wide rule), do the write throughPOST /api/v1/contacts/optouts/bulk — suppression’s PUT is for dedupe policy; opt-outs are the per-contact block list. Pair it with a poll loop on the async contact importer when the row count exceeds a few hundred.
GET /api/v1/contacts/imports/{id} (for async-import flows) or GET /api/v1/contacts/{id}/channel-state (synchronous verify) — sms.opted_out should be true on the response.
Suppress-only-bulk has no separate endpoint; the policy is a single org-level object set via PUT. Use POST /contacts/optouts/bulk when the intent is “suppress this cohort on channel X” (per-contact block) and PUT /api/v1/message-suppression/ when the intent is “dedupe identical bodies within a window for everyone” (org policy). See the Opt-Outs API for the CSV import wizard endpoints.
Errors
See also
- Frequency Caps API — rolling-window send limits per contact; enforced alongside suppression.
- Opt-outs API — hard channel-level opt-out, enforced regardless of suppression or caps.
- Compliance → opt-out & suppression