Skip to main content

Settings → Message Suppression console

Open Settings → Message Suppression. There are three mechanisms in Orbit that answer to “suppression,” and which one this page operates depends on which of those you need: the duplicate-content guard managed here, the suppression list (the imported block list), and consent suppression (the per-contact opt-out state). This guide walks all three from the operator’s chair, then tells you which console to visit next when the job crosses into consent territory.
Suppression is a tenant-owned control: your organization decides which addresses and bodies it blocks and on which channels. Orbit enforces the gate; the lawfulness of your sends stays with you. This guide is not legal advice.

The console at /settings/message-suppression manages one policy per workspace: the duplicate-content guard. On every outbound send, the pipeline hashes the whitespace-normalized body and skips the send when the identical body already reached the same contact on the same channel inside the window you set. The outcome on the message reads skipped with reason duplicate_content — a safety net, not a compliance event. Consent flows are different in kind. A suppression list entry hard-blocks an address on a channel scope — a recipient who replies STOP, a row you imported from a legacy DNC registry, a Preference Center opt-out. A consent record asserts the lawful basis behind a grant or revocation. Both of those live under the Compliance section, not here. The correct order of gates on the send path:
  1. Suppression list / opt-out — an address-level block. Drops first.
  2. Duplicate-content guard (this console) — skips a second identical body.
  3. Frequency cap — per-channel send-count limit.
  4. Dispatch — hand to the channel provider.
Because this console’s guard sits after the suppression list, a suppressed-in-list address never even reaches the duplicate check. The full model is on Message suppression model; the recipient-state side is on Consent, opt-out, and suppression.

2. Read and edit the duplicate-content policy

The page is gated to owner, admin, and developer roles. It reads GET /api/v1/message-suppression and writes PUT on the same endpoint, so anything you change here is the same JSON the API exposes. The form carries four fields:
  • Enabled — a switch. Off keeps the saved policy but stops it firing on the send path.
  • Suppression window — a preset (1 hour to 90 days) or a custom value in hours, clamped to the same bounds. A repeat body inside this window is skipped; the marker expires per-send.
  • Channels — a checklist. Leave every channel unchecked and the policy fires on all channels; tick any subset to restrict it. The canonical channel order is preserved on save.
  • Categories — a comma- or space-separated list, matched against the send’s metadata.category (or metadata.message_type). The common shape is marketing alone, so OTPs and receipts are never held back. Leave it empty and the policy fires on every category. Cap: 32 entries.
Save PUTs the whole policy; a Turn off confirmation dialog deletes it. A suppressed send is silent by design — no webhook, no callback — so verify from the Delivery log when you suspect the guard fired. The policy’s edge behavior — the per-channel key, whitespace normalization before hashing, the compare-and-set atomic claim, the fail-open posture when the backend hiccups — is covered in the Message suppression guide. The API surface is on the Message Suppression API reference.

3. Add and remove suppression-list entries per address and per channel

One-off blocks belong in the Compliance → Opt-Out surfaces, not in this console. The single-address write is the Consent API: record an opt_in: false on the channel the opt-out arrived on and Orbit fans it out to the recipient-state stores the send gate reads.
Scope is the one decision to make deliberately. Pick the scope that matches the signal the opt-out carried: Conservatively, behave like STOP: write all on phone identifiers unless narrowing is deliberate. The two failure shapes — under-blocked (scope narrowed off the channel you’re sending on) and over-blocked (scope all where a channel was meant) — are diagnosed on Troubleshooting: suppression scope mismatch. Removal is symmetric. Record the re-grant with opt_in: true on the same channels — the revocation row stays in the ledger for audit and the new grant is appended; history is never rewritten.

4. Bulk-import a suppression list from CSV

The bulk path is POST /api/v1/compliance/suppression-list/import — a multipart/form-data upload that parses up to 100,000 rows, dedupes inside the file and against prior imports, and returns per-row verdicts. The header is case-insensitive and column-order-independent; a row carries a phone, an email, or a WhatsApp ID, plus an optional channel scope override and reason. Worked example — a safe-harbor DNC import. A marketing team inherits an external Do-Not-Call registry export from a legacy sender and needs every phone on it fenced before the first campaign runs on Orbit:
Row 1 and row 3 land scope all (the bare-phone safe-harbor default: the number fences every channel); row 2 is narrowed to sms by its explicit column. Dry-run with dry_run: true first against a real export — accepted stays zero and nothing is written, but the per-row errors tell you which rows the real pass would reject. Re-importing the same (channel, address) pair with a corrected scope replaces it cleanly, so a mis-scoped first pass is repairable by exporting, fixing the scope column, and re-uploading. The full import contract — column aliases, error reasons, dry-run semantics — is on Opt-Out & Suppression Lists; the entry-point decision among CSV, the Consent API, and the Preference Center is on Choose your suppression entry point.

5. Audit trail — what the ledger records

Every write above is auditable in three places:
  • Suppression ledger export — GET /api/v1/compliance/suppression-list/export?status=active&format=csv returns every row with channel, address, status, reason, source, contact_id, and suppressed_at / revoked_at. A revoked row is never deleted; it flips status and stamps revoked_at. Export with status=all to see both.
  • Consent history — GET /api/v1/compliance/consent/history?identifier=… returns grant and revocation pairs tied by timestamp, which is the answer to “when did this contact opt out, and who recorded it?”.
  • Org audit log — policy PUTs and DELETEs on this console, CSV import runs, and ledger exports each emit an audit entry under Settings → Audit Log, with the actor, timestamp, and before/after values.
Pair the suppression export with the consent export for an external audit: the consent row carries the lawful-basis and proof fields the suppression ledger does not. Together they are the evidence binder — the consent row proves the event, the suppression row proves the fence.
Stay on Settings → Message Suppression when the job is “the same promo must not reach a contact twice in a window” — that is what this page is for, and no other surface does it. Cross over when the job is about the recipient’s state rather than the message’s repetition: The rule of thumb: this console decides whether an identical body re-sends; the compliance consoles decide whether the recipient is reachable at all. They operate independently and neither substitutes for the other.

See also