# Sending email

Every outbound message — written by the engine or typed by a person — leaves through `outbound.py`. One boundary, one set of checks: compliance on the recipients, the daily allowance, then the provider. The word **sent** is reserved for a message the provider has acknowledged; anything else is *uncertain* or *failed*, and is shown as what it is.

- **Status:** Available
- **Audience:** both
- **Channels:** email
- **In the app:** #/inbox, #/needs-you
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/email/sending/

## One door

There is no second path. A reply released from Needs You, a message you compose yourself, a follow-up draining at its due time and an outreach message all converge on the same function before any provider is contacted. That is a deliberate architectural choice with a specific payoff: a compliance rule, a limit or an evidence requirement added once is added everywhere, and cannot be missing from the path somebody wrote last month.

It is the same principle the whole product is built on — new behaviour lives in the module both audiences call, and the routers only add a door. [One implementation, two audiences](/research/two-audiences-one-implementation/) is the longer version of that argument.

## The checks, in order

1. **Compliance.** Suppression, unsubscribe and do-not-contact are checked in one place, on the recipients as they stand at this moment. A suppressed address stops the send rather than being silently dropped from the list.
2. **Autonomy.** Whether this may go without a person is already settled by the time the boundary is reached, but the boundary is where an unauthorised attempt fails rather than succeeding quietly.
3. **The daily allowance.** A workspace's plan bounds the work. When it is spent, the send is held rather than dropped — described on [The daily email allowance](/docs/email/daily-allowance/).
4. **The provider.** Gmail, Microsoft Graph, or SMTP for an IMAP mailbox. Which one is a property of the mailbox, not of the message.
5. **The evidence.** The provider's acknowledgement is captured and stored with the outcome. No acknowledgement, no `sent`.

> **Careful** The order matters more than it looks. Checking compliance last would mean a message could be handed to a provider and then found to be one that should never have gone — and at that point there is no taking it back.

## Three outcomes, not two

**Sent** — The provider acknowledged it. This is the only state that justifies telling a colleague the customer has been answered.
**Uncertain** — Connect handed the message over and did not get a usable answer — a timeout, a dropped connection, a response it cannot interpret. The message may well have gone. It is shown as uncertain because guessing in either direction is worse.
**Failed** — The provider refused it, or a check before the provider did. The reason is recorded and the message did not go.

Most systems collapse the middle state into one of the outer two. Collapsing it into *failed* invites a retry, and a retry on a message that actually went is how a customer receives the same reply twice. Collapsing it into *sent* is a quiet lie that surfaces a week later when somebody asks why there was no answer. The reasoning is in [Why 'sent' must require provider evidence](/research/provider-evidence-for-sent/).

## What sending does not promise

Acknowledgement means the provider accepted the message for delivery. It does not mean it arrived, that it reached an inbox rather than a spam folder, or that anybody read it. Those depend on authentication records, sending reputation and the receiving system's own judgement, none of which Connect controls and none of which it will claim. What can be influenced is covered on [Deliverability](/docs/email/deliverability/); the records themselves have pages under [Technology reference](/docs/technology/).

A message that is accepted and then rejected further along comes back as a bounce, which is a separate event with its own handling and its own consequences for later sending. It does not retroactively change this send's state — see [Bounces](/docs/email/bounces/).

## Questions

### Does a person's own message go through the same checks as the engine's?

Yes, including compliance. That surprises people occasionally — being unable to type a message to an address on the do-not-contact list feels like an obstruction — but a compliance boundary a human can walk round is not a compliance boundary.

### Which mailbox does a reply leave from?

The one the thread belongs to, decided by the mailbox's role. It is not a per-message choice, for the identity-alignment reasons on [Editing a draft before it goes](/docs/email/edit-draft/).

### Is there a limit separate from the allowance?

Providers apply their own, and Connect cannot raise them; a message refused for that reason is a provider failure and is reported as one. The allowance is Connect's own bound and behaves differently — it holds work rather than failing it.

## Related

- [Proof that a message was sent](https://connectbyjbrh.com/docs/email/send-evidence/)
- [A send that failed](https://connectbyjbrh.com/docs/email/failed-send/)
- [The daily email allowance](https://connectbyjbrh.com/docs/email/daily-allowance/)
- [Deliverability](https://connectbyjbrh.com/docs/email/deliverability/)
- [Why 'sent' must require provider evidence](https://connectbyjbrh.com/research/provider-evidence-for-sent/)

## What this page is based on

- CHANNELS.md §1 — sending, the one boundary, and the third state
- GENERAL.md §5 — autonomy and approvals
- `backend/app/outbound.py`, `outbound_api.py`
- Connect capability registry (docs-source/facts.py) — `send_evidence`
