Skip to main content

Recycled-Number Complaint Hygiene: Deactivation, RND, and the List Scrub Working Together

A phone number is a lease, not a person. A subscriber cancels or lapses, the carrier deactivates the number and eventually reassigns it, and months later your re-engagement campaign lands with a stranger who never agreed to hear from you. That stranger’s complaint lands on your sender reputation, and in the US a text to a reassigned number can carry TCPA exposure even though the original subscriber opted in. This guide walks the receiver-side posture: how you keep your list clean as contacts churn, before the recycled number complains. Everything here is a tenant-owned control you configure and own: Orbit provides the feeds, the check endpoints, and the ledgers; whether you opt in, how you pace your scrubs, and what you do with a scrubbed contact are your organization’s decisions. This page is not legal advice — confirm your obligations with qualified counsel. All deactivation endpoints below are rooted at https://api.orbit.devotel.io/api/v1/compliance/deactivations.

1. Lifecycle of a recycled-number complaint

A recycled-number complaint follows a predictable path:
  1. Churn. The subscriber cancels, stops paying, or ports away; the numbering carrier deactivates the number.
  2. Recycling. After an aging period the carrier returns the number to inventory and reassigns it to a new subscriber.
  3. Contact. Your list still holds the number under the old relationship, and the next campaign sends to the new subscriber.
  4. Complaint. The new subscriber marks the message as spam, or their counsel claims you messaged a reassigned number without consent.
Three Orbit surfaces catch the number at different points of that path, and none substitutes for the other two: Run them as a stack, in that order: the deactivation feed sweeps churn out of your list early, the RND check screens what remains before contact, and the suppression ledger gates every send. A number that passes all three has a live relationship, a current holder, and no standing objection.

2. The hygiene runbook

Work these steps before every re-engagement campaign, and on a schedule for long-lived lists. Each step’s copy-paste request is in §3.

Step 1: Enable the deactivation scrub

The scrub is a per-organization opt-in and defaults to off. Read your current state with GET /deactivations/settings, then flip it with PUT /deactivations/settings (owner/admin only). The response carries feed_synced; while it is false no carrier snapshot is loaded and every verdict degrades to no_data, so enable the toggle first and confirm the feed before trusting a clear result.

Step 2: Pre-flight before the campaign

Check one number with GET /deactivations/check while you are wiring things up, then run the audience through POST /deactivations/scrub in pages of up to 1,000 numbers before the audience is frozen. Pass last_seen as the date you last validated each relationship (the most recent consent or two-way interaction); deactivations strictly before that date are ignored, because the relationship postdates them. Omit last_seen only for cold lists and imports with no validated date to defend. Drop every should_scrub: true row from the campaign, and store the verdict and the last_seen you used on the contact record so the next sweep only re-checks contacts whose state aged.

Step 3: Stack with your suppression entry points

A clean deactivation verdict does not exempt a number from the suppression ledger: the audience that survives the scrub still passes your opt-out gates at send time. Keep one of the three entry points as the owner of your STOP ledger, per Choose your suppression entry point (CSV import, Consent API, or Preference Center). Keep the ledgers separate in the other direction too: a deactivation verdict is a carrier network event, not a person’s instruction, so route should_scrub: true rows to a contact-status field (for example carrier_deactivated with the last_deactivation_date) rather than writing them into suppression as if they were STOP replies. If the new holder of a recycled number texts STOP, that opt-out enters suppression through the normal keyword path. One ledger per truth; see Opt-Out & Suppression Lists and Message suppression.

Step 4: Tie the scrub to the subscriber-relation clock

Consent records carry a valid_until (or expires_in_days) window that defines how long the relationship counts as validated; a grant past its valid_until reads expired: true and should be treated as no-longer-consented at your send gate, per Consent Management & Receipts. Sweep GET /compliance/consent/expiring on a schedule (within_days=30 is a sensible horizon) and treat every returned grant as a scrub candidate: run that cohort through POST /deactivations/scrub with last_seen set to the grant’s most recent validated date, and drop the scrubbed rows before you plan any re-permission send. A scrubbed number re-enters your sendable pool only through a fresh opt-in recorded via the Consent API whose new consent date postdates the carrier’s last_deactivation_date. Consent windows therefore drive the scrub cadence: stale numbers are scrubbed per window, not discovered per complaint.

3. curl examples

Read the org opt-in and the feed state:
Enable the scrub for your organization (owner/admin):
Pre-flight one number against the date you last validated it:
Scrub a page of the re-engagement audience; a top-level last_seen applies to every number unless a per-number last_seen overrides it:
Read the response as: scrub_count + kept_count equals checked, and your drop list is exactly the rows with should_scrub: true. While feed_synced is false, expect no_data verdicts and do not treat them as clear. Find consent grants whose validation window has lapsed or is about to, as the input cohort for step 4:

4. Caveat: the scrub is opt-in and fail-open

The deactivation scrub is a configuration you enable; it is not a platform-wide block applied to every tenant. Until your organization sets enabled: true, the check and scrub endpoints answer 403 DEACTIVATION_SCRUB_NOT_ENABLED and your sends proceed unguarded by the carrier feed, exactly as with the other opt-in gates described in Send Gates. Once enabled, lookups also fail open: with no snapshot loaded (feed_synced: false) or on a degraded lookup, verdicts degrade to no_data / should_scrub: false rather than blocking you. A clear verdict is therefore never a safe harbor by itself. Keep the stack layered (deactivation feed, RND safe-harbor check, suppression ledger) and verify feed_synced before you rely on it.