Skip to main content

Tenant compliance defaults — the opt-in envelope

Most Orbit defaults you can tune sit behind per-channel or per-campaign settings pages. Two of them do not: the GDPR erasure cooling-off window, and the default quiet-hours window campaign sends use when a campaign does not set its own. Because both apply org-wide, Orbit gates them behind an opt-in envelope: a master org toggle and one sub-toggle per feature, both of which must be the boolean value true before the resolver reads your values. Until that happens, the platform constants apply — 7 days for erasure cooling-off, 21:00–09:00 for quiet hours — for every tenant, so flipping anything has no surprise effect.
This page documents tenant-owned controls. Compliance for your traffic stays your responsibility; the envelope simply controls which defaults the resolver returns when your opt-in says to override. It is not legal advice — what window is adequate for your recipients is a question for counsel.

1. Why these two need an envelope

The erasure cooling-off and quiet-hours defaults started as platform constants because almost nobody changed them. That was right until it was wrong:
  • A strict GDPR reading (for example some EU supervisory authorities) wants erasure processed with no cooling-off delay; an enterprise review workflow may legitimately want 14 days to inspect a request before deleting. Both had to leave the platform constant and explain themselves later.
  • 21:00–09:00 lines up with US TCPA expectations; EU/UK B2B email traffic often needs evenings, and the platform default made that awkward for no reason that survived scrutiny.
The envelope exists so you can change those two org-wide defaults without every other tenant inheriting the change by default. It is deliberately harder to open than a normal settings page: you flip the master toggle, then flip the sub-toggle for the specific feature, then set the value on the organization record.

2. The opt-in envelope shape

The envelope lives on your organization settings object. Two levels must both be true before the resolver honors your values:
The gates are strict-true: the resolver checks value === true. String "true", number 1, and every other truthy shape are rejected. This is deliberate: a misconfigured admin UI or a patch script writing string values must not flip org-wide behavior silently. If either the master toggle or the feature sub-toggle is missing, false, or not the boolean true, the envelope is closed and the resolver falls through to the platform constants. The override values themselves live on typed organization columns, not in settings JSON, so PostgreSQL CHECK constraints bound what can persist: The two quiet-hours columns exist because start may exceed end (21:00–09:00 overnight), which a single “window” field cannot express. Erasure values above 90 days are rejected at the column and clamped again in the resolver as defense-in-depth.

3. What the resolver returns

Two resolution paths read the org row once, then decide:
  • Erasure cooling-off. If the envelope passes for erasure, the resolver reads gdpr_erasure_cooling_off_days, floors it to a whole number, and clamps it into 0–90. Anything non-numeric, missing, or out of range resolves to the platform constant (7), never to a junk value the DELETE scheduler must serialize later.
  • Quiet hours. If the envelope passes for quiet_hours, the resolver reads both hour columns, floors each, clamps into 0–23, and returns { start, end }. Campaign code hands these to its quiet-window helper; a degenerate window (start equals end) or an overnight window (start above end) is still a legitimate configuration and is returned as-is.
The resolver reads four specific organization columns and settings in one SELECT, keeps the read inside a small cache, and never throws — every failure degrades to the platform constants. That is the fail-safe posture you want for an org-wide default: a malformed settings blob or a missing org row costs you a platform-shaped window, not a send.

4. How it differs from the platform fail-closed gates

This pattern is explicitly opt-in and is the mirror image of the always-on fail-closed rails Orbit does not let you toggle (the federal TCPA dialing window and the emergency routing rail — Plane A on the audit map). Those rails gate on input resolution and block outbound when the input fails closed; the defaults resolver gates on your opt-in and applies the platform constants when the opt-in is missing. The distinction matters when you read the toggle map: the map’s Plane B (tenant opt-in gates, tenant-owned defaults) is where this envelope sits. It is the second-order version of the model: not “which surfaces can I gate,” but “when I gate them, which defaults do my values feed to Plane C hygiene send-paths.”

5. Caching and the observable shape

Resolution is cached for 60 seconds per tenant schema, in two parallel entries (one for erasure days, one for the quiet-hours pair). Operator settings change rarely; the cache keeps campaign send and erasure code from re-reading the org row on every send or scheduled delete. A bad tenant schema, a missing org row, or a database read error degrades to the platform constants with a warning log and a counter increment, and a Sentry-visible error capture so twenty-four-by-seven observability sees resolution failures rather than eating them. The strict-true check, the [0, 90] clamp for erasure, and the [0, 23] clamp for hours are all applied after the cache lookup and before the cache write, so nothing junk survives one bad read to poison the cache.

Resolution flow