# Drafting and approving a reply, end to end

Connect drafts a reply on every thread it is allowed to answer, then asks one question before sending: what does autonomy say at the narrowest scope that has a rule? `autonomous` sends through `outbound.py`; `ask_before_send` puts the finished reply in Needs You; `draft_only` writes it and stops. A send is reported as sent only once the provider acknowledges it.

- **Status:** Available
- **Audience:** both
- **Channels:** email
- **In the app:** #/inbox, #/needs-you, #/approvals, #/autonomy-audit
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/workflows/email-reply-approval/

## The chain from message to sent reply

1. Trigger — a canonical thread the engine has picked, ranked by `threads.priority` rather than by arrival time.
2. User or external event — the customer's message is already a canonical record; see [inbound email, end to end](/docs/workflows/inbound-email/) for how it got there.
3. Authentication and workspace resolution — the engine runs inside one workspace; the mailbox, its signature and its autonomy are read only after that.
4. Ingest — the thread, its history and the person behind the address are loaded together, so the reply answers a conversation rather than a message.
5. Canonical record — the answer is written as a record before anything is sent, which is what makes holding it, editing it and auditing it possible.
6. Reasoning — the reply is generated against the thread, grounded in the workspace's knowledge rather than improvised.
7. Knowledge, memory and rules — memory resolves workspace, channel, endpoint and contact narrowest-first; a `block:email` directive on the contact stops the draft being written at all.
8. Autonomy and approval — the mode at the narrowest applicable scope decides: send, ask, or stop. A refusal is a decision and is recorded as one.
9. Action through a provider — `outbound.py` is the single send boundary, used identically by the engine and by a person pressing Send.
10. Result — sent, failed, or *uncertain*. The third state is shown as uncertain rather than guessed either way.
11. Relationship, timeline and memory — the reply joins the person's timeline; a commitment made in it becomes a follow-up rather than a promise nobody tracks.
12. Audit, usage and Needs You — the send is metered, the Decision Log records what released it under which rule, and the Needs You entry drains itself.

## The gate in the middle

Autonomy has four modes and four scopes. The scopes are `contact`, `endpoint` (one mailbox), `channel` and `workspace`, and the narrowest one that has a rule wins — which is how a workspace can run autonomously everywhere and still hold every word going to one account.

| Mode | What you see | What changes | What can fail |
|---|---|---|---|
| `autonomous` | A sent message on the thread | An outbound message, metered, with provider evidence | The provider rejects it, or the allowance is already spent |
| `ask_before_send` | An item in Needs You with the original above the draft | A held draft row, ranked into the queue | Nobody works the queue; the customer is waiting on no reply at all |
| `draft_only` | A finished reply on the thread and nothing in Needs You | A row that is never queued for approval | Being mistaken for `ask_before_send` — the one difference the screen does not make obvious |
| `off` | Nothing | Nothing is written | A channel switched off for a test and never switched back on |

> **Note** Approving is not re-drafting. Your edits to the subject, body and recipients are what gets sent; Connect does not write over them. The sending mailbox is fixed, because it was decided by that mailbox's role and by the thread the reply belongs to.

## What counts as verified success

A reply is reported as **sent** when the provider has acknowledged it, and at no earlier point. Handing a message to an SMTP server is not evidence; neither is a request that has not returned. Where the acknowledgement never arrives — a connection dropped after the message was accepted, a timeout on a call that may well have succeeded — the state is **uncertain**, and it is shown that way.

Uncertain is deliberately not resolved by guessing. Re-sending on a maybe is how a customer receives the same reply twice, and treating an uncertain send as a failure is how one thread gets answered twice by two different people. [The send result is uncertain](/docs/troubleshooting/uncertain-send/) explains how one resolves.

1. Open the thread on Conversations after approving.
   - Result: The reply is on the thread with its state, not merely in a queue somewhere.
2. Read the Decision Log entry for that action.
   - Result: It names what was decided, under which rule, by which person or by the engine — including refusals.
3. If Gmail is the provider, check the label and the read state on the original.
   - Result: Write-back is best-effort and is allowed to fail without affecting the reply, so a missing label is not a failed send.

## What can go wrong on this path

**The draft never appears** — Autonomy is `off` for this scope, or a `block:email` directive is set against the contact. Both are deliberate, and both are visible on the contact's memory.
**The draft appears and never moves** — One of four holds. [The draft is still waiting](/docs/troubleshooting/draft-never-sent/) takes them in the order they are evaluated.
**Approval succeeds and nothing is sent** — The daily allowance was spent between the draft and the approval. The approval stands and the send waits for the allowance to reset.
**The reply is stale by the time it goes** — The thread moved on while it waited. Regenerating gives the model the newer messages; editing the old text does not.
**The reply went from the wrong address** — The mailbox role decided the sender. [The reply went from the wrong address](/docs/troubleshooting/wrong-mailbox-sent/) covers the fix.

## Questions

### Does the customer see anything while a draft is held?

No. A held reply has been handed to no provider, so there is no message in flight and nothing in the sent folder. A held reply is no reply rather than a slow one, which is why the queue is ranked instead of chronological.

### Can the Assistant approve a draft for me?

It has `approve_draft`, `send_draft_now` and `reject_draft` among its tools, and its rights are deliberately narrower than a person's elsewhere — it cannot set pricing and cannot clear a do-not-contact entry.

### Is the engine's send different from mine?

No. `outbound.py` is one boundary shared by both, so metering, suppression checks and provider evidence apply the same way whether a person or the engine pressed the button.

## Related

- [How a reply is drafted](https://connectbyjbrh.com/docs/email/draft-generation/)
- [Held drafts](https://connectbyjbrh.com/docs/email/held-drafts/)
- [Approving and rejecting a draft](https://connectbyjbrh.com/docs/email/approve-reject/)
- [Proof that a message was sent](https://connectbyjbrh.com/docs/email/send-evidence/)
- [Inbound email, end to end](https://connectbyjbrh.com/docs/workflows/inbound-email/)
- [The draft is still waiting](https://connectbyjbrh.com/docs/troubleshooting/draft-never-sent/)
- [Human approval without blocking low-risk work](https://connectbyjbrh.com/research/approval-without-blocking/)

## What this page is based on

- Connect source pack — channels, §1: the single send boundary, provider acknowledgement and the uncertain state (`docs-source/sources/CHANNELS.md`)
- Connect source pack — autonomy modes and scopes, Needs You, the audit trail (`docs-source/sources/GENERAL.md` §5)
- Connect capability registry (`docs-source/facts.py`) — `draft_generation`, `held_draft_approval`, `send_evidence`
