> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Work a suppression entry in Settings → Message Suppression (and the two consoles it borders)

> Operate the /settings/message-suppression console end to end — enable or rescope the duplicate-content guard, bulk-import a suppression list from CSV, read the audit trail, and hand off to the consent-management console when the block should track lawful basis instead.

# 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.

<Note>
  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.
</Note>

***

## 1. What the console blocks — and how that differs from consent

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](/concepts/message-suppression-model); the recipient-state side is on [Consent, opt-out, and suppression](/concepts/consent-and-suppression-model).

***

## 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](/guides/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](/guides/message-suppression). The API surface is on the [Message Suppression API reference](/api-reference/message-suppression).

***

## 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](/compliance/consent-management): 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.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/consent \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "+14155550101",
    "channels": ["sms"],
    "opt_in": false,
    "source": "inbound_keyword"
  }'
```

Scope is the one decision to make deliberately. Pick the scope that matches the signal the opt-out carried:

| Opt-out signal | Scope to write | Why |
| - | - | - |
| `STOP` keyword on a phone number | `all` (written for you) | The number revoked; every channel on it |
| Email unsubscribe | `email` | An email address has no cross-channel parity to assert |
| Carrier complaint | `all` | Most conservative reading of the event |
| A channel-specific legacy import | that channel (`sms`, `whatsapp`, …) | Recipient stays reachable elsewhere by design |

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](/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:

```csv theme={null}
phone,channel,reason
+14155550101,,replied STOP on the legacy short code
+14155550202,sms,carrier complaint forwarded to compliance
+442071838750,,external DNC registry sync 2026-09
```

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/suppression-list/import \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -F "file=@suppressions.csv" \
  -F "default_country=US" \
  -F "default_reason=migrated_from_external_dnc_registry"
```

```json theme={null}
{
  "data": {
    "run_id": "supimp_4d…",
    "total_rows": 3,
    "accepted": 3,
    "duplicates": 0,
    "by_channel": { "all": 2, "sms": 1 }
  }
}
```

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](/compliance/opt-out-suppression); the entry-point decision among CSV, the Consent API, and the Preference Center is on [Choose your suppression entry point](/guides/suppression-three-entry-points).

***

## 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 `PUT`s and `DELETE`s 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.

***

## 6. When to leave this console and go to Consent Management instead

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:

* A recipient blocked on a channel, or a block to be lifted — use the [Consent API](/compliance/consent-management).
* A list to import, dedupe, or export — use the [Opt-Out & Suppression Lists](/compliance/opt-out-suppression) endpoints.
* A recipient-facing unsubscribe page to stand up — use the [Preference Center](/guides/preference-center-opt-out-page).
* An opt-in/opt-out scope to reason about (per-channel vs `all`) — read [Suppression vs consent precedence](/compliance/consent-vs-suppression-model).

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

* [Message suppression guide](/guides/message-suppression) — the duplicate-content guard's behavior, edge cases, and troubleshooting.
* [Message suppression model](/concepts/message-suppression-model) — the three mechanisms, told apart.
* [Consent, opt-out, and suppression](/concepts/consent-and-suppression-model) — the recipient-state stores the send gate reads.
* [Choose your suppression entry point](/guides/suppression-three-entry-points) — CSV import vs Consent API vs Preference Center.
* [Opt-Out & Suppression Lists](/compliance/opt-out-suppression) — the import/export contract.
* [Consent Management & Receipts](/compliance/consent-management) — the per-contact grant/revoke endpoints.
* [Troubleshooting: suppression scope mismatch](/troubleshooting/suppression-scope-mismatch) — under- and over-blocked shapes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.