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

# SMS complaint feedback loop: ingest, rollup, and auto-suppression

> How Orbit ingests carrier and CTIA SMS spam-complaint reports per DID, computes a complaint rate against your sent volume, classifies the resulting level, and trips an operator-visible auto-suppression hold at the critical threshold — the SMS parallel of the email complaint loop.

# SMS complaint feedback loop

Email has a complaint feedback loop: a spam complaint lands as a webhook event, the address is suppressed, and the damage is readable the same day. SMS traditionally has no webhook equivalent — carriers aggregate handset spam complaints surfaced through the 7726 report-as-spam short code and carrier/CTIA messaging programs, and hand them to you or your messaging aggregator as periodic counts. Orbit's SMS complaint feedback loop turns those counts into a per-DID complaint rate, classifies it against tuned thresholds, and records a suppression hold before the carrier blocks your traffic. This page explains how the loop fits together. The endpoint-by-endpoint contract lives in [Number health, warming, and carrier reputation tracking](/numbers/health); read this page before you build complaint ingest or respond to a critical event.

## What's a complaint in SMS

Two different signals get called "complaints," and the loop distinguishes them:

* **Carrier/CTIA complaint report** — a per-period aggregate count of spam complaints attributed to your DID, coming from the CTIA aggregator, a carrier feed (T-Mobile, AT\&T, Verizon, US Cellular), an upstream messaging aggregator, or an operator-supplied manual report. This is what the loop ingests: `source`, `period_start`, `period_end`, `complaint_count`, `message_count`, and an optional `reference` case id.
* **Handset block** — a per-recipient opt-out (STOP reply or a user-side block). That signal flows through the [consent and suppression model](/concepts/consent-and-suppression-model), not the complaint loop. The complaint loop watches *aggregate reputation pressure on the DID itself*; the opt-out gate refuses specific recipients.

The health score's complaint component counts complaint-coded carrier rejection error codes from your own delivery receipts — internal deliverability. The complaint loop is the third surface: the inbound, carrier-filed reputation-damage signal that never appears in your DLRs. Spam complainers do not get delivery failures; the carrier stops your traffic instead.

## The complaint-rate math

Each ingested report is appended to the DID's report set (bounded retention: the most recent 200 reports are kept). The rollup runs over a 30-day default window:

* `complaint_count` and `message_count` are summed across in-window reports.
* `complaint_rate = complaint_count / message_count`, clamped to \[0, 1].
* `sample_sufficient` is true once the window message count reaches 500, because below that the denominator is too thin for the rate to mean anything.

When the sample is insufficient, classification falls back to absolute complaint-count floors, and a raw volume of 100 or more complaints escalates to critical regardless of sample size — carriers act on raw complaint volume too, so a thin denominator does not excuse a swollen numerator.

## The threshold ladder

Complaint rates are classified into five levels. The rate thresholds are tuned to the CTIA A2P guidance that a sustained \~0.1% complaint rate draws carrier scrutiny:

| Level      | Rate threshold (sufficient sample) | Absolute floor (thin sample) | Meaning                                              |
| ---------- | ---------------------------------- | ---------------------------- | ---------------------------------------------------- |
| `ok`       | \< 0.1%                            | \< 5 complaints              | Within accepted A2P bounds                           |
| `elevated` | ≥ 0.1% (0.001)                     | ≥ 5 complaints               | Above the watch line; monitor                        |
| `high`     | ≥ 0.2% (0.002)                     | ≥ 20 complaints              | Approaching carrier-intervention territory; throttle |
| `critical` | ≥ 0.5% (0.005)                     | ≥ 100 complaints             | Carrier-block risk; auto-suppression trips           |
| `unknown`  | —                                  | —                            | No complaint data ingested for the window            |

The GET response echoes these thresholds in its `thresholds` object so a dashboard panel can render the ladder without hardcoding constants.

## Suppression modes

Auto-suppression at the critical level is a **recorded hold on the DID, not a carrier call and not a send-path block**: the hold is stamped onto the DID and surfaced on the read route and dashboard so an operator pauses the campaign. Suppression state has two modes:

* **`auto`** — threshold-driven. A critical rollup stamps `suppressed: true`; once the level recovers below critical, an auto hold self-clears. A repeated critical ingest refreshes the level and timestamps without resetting the hold's original `since`.
* **`manual`** — operator override, set through the PATCH endpoint. A manual lift (`suppressed: false`) is recorded and respected: a later critical ingest does not re-flip it into an auto hold. A manual hold (`suppressed: true`) stays until lifted. The override takes effect only when the report surface acknowledges an `id` is an owned number and the POST/PATCH audit actions record who changed what.

This mirrors the platform's compliance stance: pause-or-resume is a tenant-owned control. Orbit records and surfaces the hold so your operator acts on it; it does not silently block outbound sends.

## Update cadence

The rollup is recomputed **per ingest**, not on a scheduler. Every POST of a report re-runs the rollup math and the suppression reconciliation, so the level, recommended auto-suppression, and hold state you read back are always computed from the full retained report set. This keeps the ingest path and the read route classifying identically — both call the same shared kernel — and means remediation (a manual lift, or new reports with zero complaints) takes effect immediately rather than at the next daily pass.

## Interaction with health and throttling

The complaint loop composes with two sibling surfaces rather than replacing them:

* **Health score** — the daily deliverability kernel folds delivery rate and complaint-coded error codes into a 0–100 score and health tier. Complaint-ingest output (`ok`…`critical`) is a parallel, faster-moving signal on the same DID; a critical complaint level should also push you toward a `poor`/`critical` health tier within a day's recompute. Neither surface gates sends — they inform the warming ramp and operator decisions.
* **Warming throttle** — the [sender warming ramp](/concepts/sender-warming-and-reputation) cuts its ceiling factor to 50% (`holding`) at `poor` and 25% (`throttled`) at `critical` health tier. Ingest complaint reports early in a ramp and the warming curve reacts to complaint-driven health; the complaint loop's own hold is the immediate operator signal while the health pass catches up.

## Responding to a critical event

A level reaching `critical` means carrier block is the credible next step, and every carrier blocked send is worse than one you voluntarily pause. Follow this playbook:

1. **Pause the campaign on the affected DID.** Confirm the suppression hold the ingest recorded (`suppression.suppressed: true`, `mode: "auto"`).
2. **Identify the offending traffic.** Cross-reference the reported period against which campaigns ran on the DID; the report's `source` and `reference` point at the carrier program that filed it.
3. **Fix the root cause.** Wrong audience (re-verify the registered campaign use case), wrong content (canned URLs, forbidden categories per the [CTIA messaging principles](/concepts/ctia-messaging-principles)), or missing consent (a recipient scope the [consent and suppression model](/concepts/consent-and-suppression-model) would have refused before ingest was ever needed).
4. **Rotate volume.** Move traffic to a healthy DID while complaints decay out of the 30-day window — use the per-DID health kernel and the [number health page](/numbers/health) to pick the candidate.
5. **Lift deliberately.** Once remediation is verified, lift the hold via `PATCH /api/v1/numbers/:id/sms-complaints/suppression` with `suppressed: false` and a reason. The manual lift means a subsequent critical ingest does not silently re-hold the DID — you own the decision.
6. **Watch the level recover.** As complaints age out of the window, an auto hold self-clears; a manual lift is recorded and survives later critical ingests.

If complaints recur after remediation, treat the DID as burned for that program — re-register the campaign and re-warm a replacement rather than hoping the rate decays.

## Cross-links to suppression and health

| Surface                                                                | What it covers                                                                                         |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| [Consent & suppression model](/concepts/consent-and-suppression-model) | Recipient-side opt-outs and consent — a handset block is refused here, not on this loop                |
| [Message suppression model](/concepts/message-suppression-model)       | Duplicate-content guard and the imported suppression ledger                                            |
| [Sender warming & reputation](/concepts/sender-warming-and-reputation) | Trust score vs health score, the warming ramp, and how health tier throttles the ceiling               |
| [Number health](/numbers/health)                                       | The endpoint surface: warming, health snapshot/trend, reputation, spam-likelihood, warming progression |

## See also

* [CTIA messaging principles](/concepts/ctia-messaging-principles) — what counts as spam content a complaint references
* [Email delivery lifecycle](/concepts/email-delivery-lifecycle) — the email-side complaint feedback loop this page parallels
* [Send gating and quiet hours](/concepts/send-gating-and-quiet-hours) — the send-admission chain reputation surfaces compose with
