# Replying on WhatsApp

`whatsapp_engine` writes the reply — grounded in Knowledge, memory, what the same person has said on email, phone and SMS, their instructions and the regional style policy — and hands it to `whatsapp.dispatch`. Dispatch asks the autonomy policy, which sends it, queues it for approval, or holds it as a draft. A reply is recorded as sent only once Meta has acknowledged it.

- **Status:** Available
- **Audience:** both
- **Channels:** whatsapp
- **In the app:** #/whatsapp, #/needs-you, #/approvals
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/whatsapp/replying/

## From message to draft

The agent loop is one loop for every channel. It reads the new message in the context of its thread, works out what kind of thing it is, gathers the grounding it is allowed — bounded extracts from the workspace's Knowledge, the memory tiers that apply to this contact and this endpoint, a short summary of who the Person is, what that same person has said recently on email, phone and SMS, the standing instructions for this conversation, and the regional speaking-style policy the phone channel also uses — and writes a reply.

Before any of that, `whatsapp_engine` checks four things and stops if any holds: the person replied STOP or the owner blocked WhatsApp for them; the conversation is paused or taken over, by somebody in Connect or by somebody answering from the WhatsApp Business app; automation is off for this conversation; or the 24-hour window has closed, which makes a free reply impossible. No model is asked in any of those cases. A callback or an appointment the reply commits to becomes a follow-up on the same queue the rest of Connect uses.

What differs on WhatsApp is length and register. A reply that reads well as an email reads as a wall of text in a chat, and the standing instructions for the channel are the right place to say so. A direction recorded at the channel tier — "on WhatsApp, answer in two sentences and offer to call" — applies to every contact on the channel without being repeated per conversation.

If the answer is not in the workspace's Knowledge, Connect is expected to say it does not know rather than to produce something plausible. That behaviour is deliberate and it is discussed in [Why uncertainty is a valid answer](/research/uncertainty-is-an-answer/).

## The autonomy decision

| Mode | What happens | What the customer sees |
|---|---|---|
| `autonomous` | Sent within the gates — allowance, suppression, the 24-hour window, the hourly cap | The reply |
| `ask_before_send` | Queued in Needs You for a person to approve, edit or reject | Nothing until somebody acts |
| `draft_only` | Written and kept; nothing is queued and nobody is asked | Nothing |
| `off` | No reply is drafted at all | Nothing |

The mode is resolved at the narrowest applicable scope: a rule against one contact beats a rule on the endpoint, which beats the channel, which beats the workspace. That is how a workspace answering WhatsApp autonomously can still route one sensitive account through a person, without slowing everything else down.

> **Note** `draft_only` and `ask_before_send` look similar and are not the same decision. `ask_before_send` puts the reply in front of somebody; `draft_only` writes it and stops on purpose, so a workspace can watch what Connect *would* say without anyone having to keep saying no.

## Gates the send still has to pass

1. **Suppression and do-not-contact.** A person who has opted out is not written to, whatever the mode says, and the Assistant cannot clear a do-not-contact entry.
2. **A `block:whatsapp` directive.** A block held as a memory tag against the contact stops this channel specifically, and holds across future conversations.
3. **The plan's allowance**, on a customer workspace. A spent allowance holds the send rather than losing it.
4. **Meta's own rules.** A free-form reply outside the 24-hour window is refused before the network call, and a template Meta has not approved — or a name that is not on the synced list at all — is refused with the reason.
5. **The hourly cap for the connection type.** 120 automatic sends per number per hour on an Official API number, 20 on a QR-linked one. Over the cap, the rest wait for the next hour rather than being lost.

The last two are specific to this channel and are the ones that surprise people, because they can refuse a reply that every other rule permitted. [The WhatsApp reply would not send](/docs/troubleshooting/whatsapp-send-failed/) is the page for working out which of the four stopped yours.

## What 'sent' means here

Connect does not mark a message sent because it handed it to Meta. It marks it sent when Meta returns a message id, and it stores that id because every later status callback is keyed on it. Between the two the state is uncertain — a distinction that matters because the wrong answer produces a system that cheerfully reports success it cannot substantiate, which is the argument made in [Why 'sent' must require provider evidence](/research/provider-evidence-for-sent/).

Anything Meta reports afterwards — delivered, read, failed — is stored as it arrives, with the category Meta billed the conversation under, and is described on [Delivery and read status](/docs/whatsapp/message-status/). Where Meta reports nothing, Connect shows nothing rather than assuming the best case.

> **Careful** This half of the channel has no production evidence behind it yet. No WhatsApp number is connected in production, so no outbound WhatsApp message has gone to Meta from the live build. A real send, and the delivery and read statuses that follow it, are proven against a stubbed Meta in `test_whatsapp_platform_v1` and nowhere else. Drafting, holding for approval and the approval itself are proven live.

## Questions

### Can I edit a held WhatsApp reply before it goes?

Yes. Edit it in Needs You and approve; what you approved is what is sent, and Connect does not rewrite over your edit. The approval is recorded against you in the decision log.

### Why was a reply drafted but never queued for approval?

The channel, endpoint or contact is on `draft_only`, which deliberately asks nobody. Change the mode, or send the draft from the conversation.

### Does rejecting a draft teach Connect anything?

A rejection is recorded as a decision about that message and stops the same draft coming straight back. It is not a lesson on its own — if the reason should change future behaviour, record it as a standing instruction or a memory.

## Related

- [WhatsApp in Connect](https://connectbyjbrh.com/docs/whatsapp/)
- [Continuing a WhatsApp conversation](https://connectbyjbrh.com/docs/whatsapp/conversation-continuity/)
- [Delivery and read status](https://connectbyjbrh.com/docs/whatsapp/message-status/)
- [The WhatsApp reply would not send](https://connectbyjbrh.com/docs/troubleshooting/whatsapp-send-failed/)
- [What Connect may do](https://connectbyjbrh.com/docs/autonomy/)
- [Why 'sent' must require provider evidence](https://connectbyjbrh.com/research/provider-evidence-for-sent/)
- [Human approval without blocking low-risk work](https://connectbyjbrh.com/research/approval-without-blocking/)

## What this page is based on

- `backend/app/whatsapp_engine.py` — `respond`, and the four checks before any model is asked
- `backend/app/whatsapp.py` — `dispatch`, `send_message`, `AUTO_SENDS_PER_HOUR`
- Connect capability registry (docs-source/facts.py) — `WHATSAPP`, what is and is not proven
- `docs-source/sources/GENERAL.md` §5 — autonomy modes and scopes
