Skip to content

Contacts and consent: who agents may message and call

Last updated November 3, 2026

On this page

Agents send emails and WhatsApp messages, and later place calls, often without a person pressing Send. Before any of that leaves, OrchKernel checks the person on the other end: did they opt in, did they opt out, did they ask not to be contacted at all, is their number on a do-not-disturb register, is it a sensible hour where they live. A send that fails the check is refused before anyone is asked to approve it, with a sentence that says why.

Your company decides the rules. OrchKernel ships presets for India, the United States and the European Union as starting points. They are not legal advice: your company is responsible for its own policy.

This page covers the words used, the channel policies and presets, how a send is checked, recording consent (by people, forms, imports, STOP replies and webhooks), the Contacts page, a record's consent line, connector tools that send, and the API. The steps use the demo company (ok demo serve): Ada is the admin, Sam leads Sales, Alice is a rep. What is not possible yet is listed at the end.

Words used on this page#

  • Address: an email address or a phone number. OrchKernel keeps each one in one form: an email in lower case, a number with its country code (+919820012345). "+91 98200 12345", "09820012345" and "9820012345" are the same number for a company in India. A WhatsApp number is the same address as the phone number.
  • Channel: email, WhatsApp, SMS, calls and AI calls.
  • Purpose: marketing, sales or service. Service is a transactional message: a reply to the person, an appointment reminder.
  • Opted in: the person said yes, through a form, at an event, on the phone. Recording it needs a note saying where it came from.
  • Opted out: the person said stop on that channel. It stops marketing and sales there; service messages still go.
  • Do not contact: stop everything, service included, on every channel (or on the one named). An admin may mark an address a legally required contact, so service messages (an invoice, a safety notice) still go.

Channel policies and presets#

Company → Contacts and consent → Channel policies sets, for every channel and purpose, what a send needs:

  • Only with an opt-in: a recorded opt-in.
  • Unless they opted out: anyone who has not opted out. People nobody asked yet are allowed.
  • Anyone, even people who opted out: no consent check, though do-not-contact still applies. Saving a rule like this asks first.

For SMS and calls a rule can also check the do-not-disturb register, and for calls it can set calling hours, read in the person's own time zone: their record's time zone when the call names a record that has one, else every zone of their number's country. A call to a number whose zone is not known is refused while calling hours apply ("The local time for … is not known"). A register that cannot answer refuses the call too.

The page's Country says how phone numbers written without a country code are read. Picking India or the United States sets it when it is empty; the European Union asks for the member state.

Channel India United States European Union
Email, marketing Unless they opted out Unless they opted out Only with an opt-in
Email, sales and service Unless they opted out Unless they opted out Unless they opted out
WhatsApp, marketing and sales Only with an opt-in Only with an opt-in Only with an opt-in
SMS, marketing and sales Unless they opted out, register checked Only with an opt-in Only with an opt-in
Calls, marketing and sales Unless they opted out, register checked, 09:00 to 21:00 Register checked, 08:00 to 21:00 Unless they opted out, 08:00 to 20:00
AI calls, marketing and sales Unless they opted out, register checked, 09:00 to 21:00 Only with an opt-in, 08:00 to 21:00 Only with an opt-in, 08:00 to 20:00
Every channel, service Unless they opted out Unless they opted out Unless they opted out

None checks only opt-outs and do-not-contact. A company that existed before this feature starts on None, so nothing changes overnight, and an admin sees a banner: "Choose how your company may message and call people." A new company picks its country in the setup wizard's first step, which chooses the matching preset and tells OrchKernel how to read national phone numbers. Saving the wizard again with another country changes how numbers are read and asks before replacing the rules you edited.

If the saved policies cannot be read (a damaged row), every message and call is refused until an admin saves them again; the page says so.

To change a rule: pick a preset to start from, change any row, and Save. Only admins change the policies; everyone can read them. The same page has Members may record opt-ins too (off by default).

How a send is checked#

Every tool that sends says what it sends: its channel, its purpose and which argument names the recipient. The bundled apps' sends are declared already (the CRM and Sales apps' emails and calendar invitations as sales, the helpdesk's replies to a ticket's customer as service, the inbox assistant's email and WhatsApp replies as replies, and WhatsApp templates as sales). For each call:

  1. OrchKernel finds the recipients: the address in the argument, or, for a tool that names a record (a lead's id), the address on that record. Every other argument is searched too, at any depth: an address of the same kind in a cc or a bcc is a recipient as well, and an argument the tool's schema does not take never reaches the vendor. A send whose recipient cannot be told is refused: "Cannot tell who conn.ats.send_message sends to; an admin must say which argument names the recipient". One whose recipient is there but is not a number OrchKernel can read says so: "09820012345 is not an address or a number OrchKernel can read; set the company's country under Contacts and consent".
  2. It looks up each recipient's latest choice for the channel and purpose.
  3. It applies the company's rule. A refusal is one of these sentences:
    • "+919820012345 has not opted in to WhatsApp messages" (for calls, "to calls" and "to AI calls")
    • "+919820012345 opted out of WhatsApp on 2 Nov 2026"
    • "+919820012345 asked not to be contacted"
    • "+919820012345 is on the do-not-disturb register"
    • "It is 22:15 for +919820012345; calls are allowed 09:00 to 21:00"

A send to several people where one of them is refused is refused as a whole, naming that person: nothing is sent to the others behind your back.

The check runs for agents and for people's own sends through OrchKernel alike, admins included. It runs before anyone is asked to approve, and again when an approved send actually leaves: an opt-out that arrives while a send waits for approval stops it.

A reply in a conversation the contact started (a reply to their email, or a WhatsApp reply within 24 hours of their last message) needs no opt-in, but an opt-out on that channel and do-not-contact still stop it. A WhatsApp template is the business writing first, after the window: it follows the channel's sales rule.

Explain shows the same check as a step: approving a send the policy now refuses (an opt-out arrived while it waited) is refused, and the approval waits until someone rejects it.

People. On the Contacts page, or from a record's consent line, choose Record a change: opted out, do not contact, or (for admins and team leads) opted in or clear their choice. Pick the channel and purpose, or leave them as every channel and every purpose. An opt-in is for one channel (the sheet asks which) and needs a note. Clearing a choice needs a note too, and only an admin clears a do-not-contact: a later opt-in, from a person or a form, does not lift it.

STOP replies. A reply that is only "STOP" (or "unsubscribe", "remove me", "band karo", "बंद करो", "mat bhejo", "abmelden" and other common forms) is recorded at once as an opt-out on that channel, with the message's id as evidence, and the person whose inbox it reached (and the owner of the record the address is on) gets a notice. A longer message that only contains the word, such as "please don't stop sending offers", is not an opt-out. No model decides this: it is a fixed word list (CANCEL and STOPALL are on it). An email reply is matched on its new text, above the quoted mail and the signature. An email STOP is recorded only when the receiving server vouches for the sender (DMARC, or DKIM and SPF, pass for the From's domain); one that may be forged is put to the person whose inbox it reached, to record if it is genuine. A WhatsApp sender is read as the international number it is.

Forms. A web form posts what it collected to POST /api/contacts/consent with an API token scoped consent:write (Profile → API tokens; the token reaches that route only, and what it posts is recorded as a form's):

bash
curl -X POST https://ok.example.com/api/contacts/consent \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"address": "+91 98200 12345", "channel": "whatsapp", "state": "opted_in",
       "note": "signed up on the website", "evidence": "form:signup-42"}'

Imports. Import a list takes a CSV with the columns address,channel,purpose,state,source,date (and an optional note). Leave channel or purpose empty for all of them; source defaults to import (reply_stop and provider_webhook are OrchKernel's own and are refused). The latest choice by its date decides: an old opt-in in a list never reverses a newer STOP, and the report says so for that line. A do-not-disturb list (India's NCPR, the US Do Not Call list) is a CSV of numbers with source registry: those numbers are refused for the channels whose rule checks the register. The import says how many rows it recorded and which lines it skipped, and why.

Webhooks. For a webhook source that delivers replies or a provider's unsubscribe events, an admin can say which field holds the text, the sender and the channel (PUT /api/contacts/webhook-maps/<source>). A STOP in the text, or an unsubscribe event, records an opt-out.

The Contacts page#

Company → Contacts and consent: type an email or a number and choose Look up. You see, per channel and purpose, whether agents may reach it now and, when not, the sentence they would get; whether the number is on the do-not-disturb register. Admins and team leads also see the records it is on and its history, newest first: what changed, from where ("from the import on 3 Nov", "from the reply"), by whom, with the note.

A lead or contact page shows one line for each address on the record: "WhatsApp: Opted in 2 Nov from a form; SMS: not opted out". History opens the Contacts page; Record records a change for that address and links it to the record.

Connector tools that send#

A connector's write tool can send messages from your own system. Under Apps → the connector → Which tools?, an admin answers Sends messages? for each write tool: no, or yes with the channel, the purpose and the argument naming the recipient (an address, or a record's id and the field holding its address). Tick The server ignores a repeated send when the server honours an idempotency key.

Until an admin answers, a write tool whose call holds an email address (anywhere in its arguments, in a list or deeper) is checked as a marketing email, and one holding a phone number (written with its country code, or under a name such as phone, mobile, to or recipient) is checked under the strictest of the SMS, WhatsApp and call rules. Your company's own addresses, and a sender's (from), are not counted. A refusal for this reason says so on a second line, with where to answer. Such a tool also counts toward an agent's daily limits until it is answered. The same check applies to an app's tool that has an outside effect and does not say what it sends.

What is kept#

The ledger keeps the address, the channel, purpose and state, where it came from, who recorded it and when, and evidence: a record, a message id or an import id. It never keeps the text of a message. The event log records each change with the address's hash (keyed with a secret of your install, so a list of numbers cannot reverse it), never the address.

When an admin erases a person's data by their address or number (data rights), their consent history goes with it, except an opt-out or do-not-contact marker kept by the address's hash, so the company keeps honouring it.

API#

Route What it does
GET /api/contacts?address= An address's state per channel and purpose; its history and records for admins and team leads
POST /api/contacts/consent Record a change (address, channel, purpose, state, note, evidence, record)
POST /api/contacts/import Import a CSV ({ csv })
POST /api/contacts/legally-required Mark an address a legally required contact (admins)
GET, PUT /api/settings/channel-policy The channel policies; PUT a policy (with its country), { country } for a country's preset, or { country, keep_rules: true } for the country alone (admins)
PUT /api/connectors/:slug/tools/:tool/sends What a connector tool sends (admins)
GET, PUT /api/contacts/webhook-maps/:source How a webhook's deliveries are read for STOP replies (admins)

Not possible yet#

  • Reading opt-outs live from your own system (such as DINGG ATS); for now, import them or send them by webhook.
  • Checking a do-not-disturb register through a vendor: the hook is there and CSV imports work, but no vendor is connected.
  • Reading replies through a connector's read tool (comes with Goals).
  • Daily caps per agent, team and recipient, and never sending the same message twice, have their own page, Limits.