The 26-rule check every message and call has to pass, what we do not have yet, and how client data is kept apart.">
Most outbound tools describe compliance as a feature. Here it is a gate that every message passes through, it fails closed, and it is the same gate in every engine we run. This page documents how it actually works, rule by rule, including the parts that are unflattering.
Every outbound email, SMS, and call — human-dialled or automated — is
evaluated before it is queued. The decision is made by a single shared contract,
compliance/rules.json, which both of our engines load:
compliance_gate() for cold prospecting and dispatch_gate() for
consented inbound leads.
Two properties make the gate testable rather than decorative:
facts object
first — phone type, timezone, consent evidence, suppression scope, DNC list age —
and only then are rules applied. This is what makes a fixture suite meaningful.block with
reason NO_RULE_MATCHED. An unrecognised state never results in a message
reaching a consumer.| Verdict | Meaning |
|---|---|
| pass | Dispatch may proceed now. |
| block | Dispatch must not happen. Terminal for this attempt. An events row is written with the reason code. |
| defer | Dispatch is lawful but not yet permitted. The message is rescheduled to the next open window and re-evaluated then. |
The third verdict is the one that matters operationally. A message that arrives outside a lead's calling window is not dropped and is not sent anyway — it is rescheduled. Silently discarding it would look identical to a delivery failure in the logs, and we would never find out which one happened.
Twenty-two of the twenty-six rules exist to stop a message. Four allow one.
| Rule | Verdict | Reason code |
|---|---|---|
| R01 | block | DISPATCH_DISABLED |
| R02 | block | TENANT_MISMATCH |
| R03 | block | CLIENT_NOT_ACTIVE |
| R04 | block | LEAD_MARKED_INVALID |
| R05 | block | SUPPRESSED_ALL_CHANNELS |
| R06 | block | SUPPRESSED_CHANNEL |
| R07 | block | CONSENT_REVOKED |
| R08 | block | CONSENT_ABSENT |
| R09 | block | CONSENT_TYPE_UNACCEPTED |
| R10 | block | CONSENT_EVIDENCE_INCOMPLETE |
| R11 | block | CONSENT_DISCLAIMER_DRIFT |
| R12 | block | TCR_NOT_APPROVED |
| R13 | block | PHONE_TYPE_UNKNOWN |
| R14 | block | PHONE_INVALID |
| R15 | block | AI_VOICE_DISABLED |
| R16 | block | AI_VOICE_NOT_DISCLOSED |
| R17 | block | DNC_LIST_STALE |
| R18 | block | DNC_MATCH_NO_CONSENT |
| R19 | block | DUPLICATE_DISPATCH |
| R20 | block | RATE_LIMIT_EXCEEDED |
| R21 | defer | OUTSIDE_QUIET_HOURS_STATE |
| R22 | defer | OUTSIDE_QUIET_HOURS |
| R90 | pass | PASS_SMS_CONSENTED |
| R91 | pass | PASS_VOICE_HUMAN_CONSENTED |
| R92 | pass | PASS_VOICE_AI_CONSENTED |
| R93 | pass | PASS_EMAIL_CONSENTED |
Calling windows are computed from the lead's timezone, resolved from their ZIP or phone number — never from ours. Kelparo operates from IST, and using the operator's clock is the expensive version of this mistake.
| Scope | Window | Authority |
|---|---|---|
| Default | 08:00 – 21:00 | TCPA |
| Florida | 08:00 – 20:00 | Florida Telephone Solicitation Act |
| Oklahoma | 08:00 – 20:00 | Oklahoma Telephone Solicitation Act |
| Washington | 08:00 – 20:00 | Washington CEMA / mini-TCPA |
Quiet hours apply to SMS, human voice, and AI voice. They do not apply to email, which is governed by CAN-SPAM and carries no time-of-day restriction. We say so rather than claiming a protection we do not implement.
For the consented-inbound engine, express written consent is the only accepted
basis. A Meta lead form carrying a compliant custom disclaimer creates it; nothing
else qualifies. Five evidence fields must all be present or the message is blocked under
CONSENT_EVIDENCE_INCOMPLETE:
disclaimer_text — what the person actually agreed todisclaimer_hash — checked against the live form on every dispatchmeta_lead_id, form_id, submitted_atIf the disclaimer on the live form no longer matches the hash we stored, dispatch is
blocked under CONSENT_DISCLAIMER_DRIFT. Someone editing a lead form should not
be able to retroactively change what past leads consented to.
Consent evidence is retained for five years. Revocation is honoured immediately, against a legal floor of ten business days, and is detected in free text rather than only on the literal keyword. “Stop texting me”, “take me off your list” and “not interested, don't contact me” all revoke.
Our copy of the National Do Not Call registry may be at most 31 days old.
Past that, the gate blocks under DNC_LIST_STALE rather than scrubbing against
stale data and reporting a clean result.
A valid express written consent generally permits contact despite a DNC listing. We still run the scrub and we still write the override to the event log, because the log is the defence.
An AI voice agent cannot dial unless two separate conditions hold: the client has AI
voice enabled, and the consent on file explicitly disclosed automated calls. Failing either
produces AI_VOICE_DISABLED or AI_VOICE_NOT_DISCLOSED. Phone type is
resolved before any call is placed; an unknown or invalid number is blocked, not attempted.
Duplicate dispatch is blocked inside a 1,440-minute window. Meta redelivers leadgen webhooks, and a double-text is both a consumer complaint and a 10DLC campaign risk.
Our engines are separate repositories with separate deploy cycles, so the real risk is not a wrong rule — it is two copies of a right rule drifting apart. The mechanism:
compliance/rules.json and compliance/fixtures.json are
canonical. They are edited in one place..compliance-lock containing the SHA-256 of each file.A rule set that can be stale at runtime is worse than one that is obviously stale at build time. Drift is a red CI run instead of a fine.
The two gates deliberately default in opposite directions: cold outbound blocks a mobile number without express consent, while consented inbound passes it. That difference is a documented rule with fixtures on both sides, not an accident of two copies diverging.
Every data-layer function takes an organisation ID as its first argument and every query filters on it. Row-level security is applied in the database in addition to the application filter. Isolation is covered by dedicated test suites — tenancy, retrieval isolation, SSRF, and lockdown — rather than by convention.
Two environment flags, both defaulting to false, gate all outbound:
SEND_ENABLED for our own prospecting and DISPATCH_ENABLED for
messages to a client's leads. With the flag off, messages are rendered and queued but never
transmitted, and the dashboard labels the mode DRY-RUN SIMULATION rather than
showing numbers that imply delivery.
A failed transmission never looks like a send. The sequence step does not advance, the row is not marked sent, and a temporary failure is rescheduled rather than discarded — otherwise the database grows a send history that never happened.
The useful version of a page like this includes the gaps.
DISPATCH_ENABLED to true for any client.Before this page is treated as binding. Kelparo's compliance
configuration carries its own disclaimer in compliance/rules.json:
“Researched, not legal advice. A US attorney must review this configuration
before DISPATCH_ENABLED is set true for any client.” The same applies to this
document. Items marked TODO are facts we have not settled yet,
and we would rather show you the gap than fill it with something invented.