Email in Connect
Connect reads a workspace's mail through the mailboxes you connect — Gmail, a Microsoft mailbox over Graph, or any IMAP and SMTP server — copies each message into canonical records the engine reads, decides what it needs, and drafts a reply that goes out under the autonomy rule for that mailbox. Several mailboxes run at once, each with its own role, signature, autonomy and health.
What the channel does#
Email is the channel Connect was built around first, and it is the one where the most of the product is visible at once. A connected mailbox is fetched on a schedule; each message is canonicalised into a thread, a message and a contact; the engine triages it, grounds an answer in the workspace's knowledge and memory, and writes a reply. Whether that reply leaves the building is decided by autonomy, not by the drafting.
Everything a person does by hand goes through the same code as everything Connect does on its own. outbound.py is the single send boundary for both, which is why a message you send from the Conversations screen and a message the engine sends are recorded identically, and why proof that a message was sent means the same thing in both cases.
The channel status is available for both audiences. The Owner's workspace and a customer workspace run the same implementation; the only difference is commercial, because a customer's daily volume is bounded by their plan's allowance and the Owner has no plan.
The three providers#
| Provider | How it authenticates | What is distinctive |
|---|---|---|
| Gmail | Google OAuth, then the Gmail API | History-ID catch-up; label and read-state write-back |
| Microsoft | Microsoft Graph | Graph's own mail API and change tracking |
| Any IMAP/SMTP server | Host, port and credentials you supply | Reads by UID; sends over your own SMTP |
The adapter is chosen by the mailbox's transport, and providers.build() reads only transport and config to construct it. That narrow contract is why a fourth provider would be an adapter rather than a change to the engine, and why nothing downstream of the bridge knows which provider a message arrived through.
Mail exists twice, on purpose#
tenant_* tables hold what the provider adapter fetched, in the provider's own shape. The canonical threads, messages and contacts are what the engine and every screen read. connect_core.bridge_* copies between them, and the engine never reads the provider tables.
Two consequences follow, and both matter more than the storage cost. Adding a provider changes nothing downstream, because the canonical shape is the only thing anything after the bridge has ever seen. And a bug in one adapter cannot corrupt a workspace's conversation history, because the adapter writes to its own table and the bridge is the only thing that promotes a row. From a provider message to a canonical one follows one message through that path.
One console, two audiences#
Mailboxes are served by one mailbox_console over one mailboxes.py policy, for the Owner and for a customer alike. The Owner's handlers were rewired onto that console rather than left as a second implementation — a second implementation is how one audience quietly acquires a capability the other lacks, which is the failure the parity suite exists to catch.
The rows still live in two tables. TenantMailbox gained the columns Mailbox always had — role, signature, autonomy and the health verdicts — and presents transport and config. Merging the tables would mean moving live customer OAuth credentials and is not required for the capability, so it has not been done.
Where the rest of this section is#
- Connecting a mailbox
- Mailboxes, Connecting Gmail, Connecting an IMAP and SMTP server, Connecting a Microsoft mailbox
- Settings on one address
- Mailbox roles, Mailbox signatures, Autonomy on one mailbox
- Keeping it running
- Mailbox health, A quiet mailbox, Syncing an inbox, Disconnecting a mailbox
- What happens to a message
- Conversation threads, Resolving a sender to a person, Triage and priority
- Replying
- How a reply is drafted, Held drafts, Sending email
- Not sending
- The suppression list, Do not contact, Unsubscribe
Everything in this section#
44 pages, each with its own status and the date it was last checked against the running system.
| Page | What it covers |
|---|---|
| A quiet mailbox | A mailbox that authenticates and returns nothing: what the state means, the four ordinary reasons for it, and why it is reported as a signal rather than an error. |
| A send that failed | A message that did not go: the failure classes, which ones are worth another attempt, what Connect completed anyway, and where the failure appears. |
| Approving and rejecting a draft | The two decisions on a waiting reply, what each one records, why a rejection is not a lesson, and who is accountable afterwards. |
| Autonomy on one mailbox | Giving one address its own rule about what Connect may do: how the endpoint scope resolves against the channel and the workspace, and when it is the right tool. |
| Blocking a sender | Blocking a sender writes a memory row tagged block:email, not a column — which is why it holds across channels and across future conversations. |
| Bounces | What Connect does when a message comes back undelivered: permanent against temporary failure, which one takes an address out of use, and how a person clears it. |
| Connecting a Microsoft mailbox | Connecting a Microsoft mailbox over Graph: what the adapter covers, the two places it differs from Gmail, and the much larger part that is identical. |
| Connecting an IMAP and SMTP server | Connecting a mailbox on your own mail server: the two halves of the connection, what a saved connection proves, and the ladder of checks that proves the rest. |
| Connecting Gmail | Connecting a Google mailbox: the consent path, why signing in and connecting a mailbox are different acts, and what the mailbox row holds afterwards. |
| Conversation threads | How a canonical thread is formed, which signals attach a message to one, what a person's own marks on a thread control, and the ordinary ways a thread breaks. |
| Deliverability | What Connect controls about whether mail arrives, what belongs to your own sending domain and its history, and the order worth fixing things in. |
| Disconnecting a mailbox | Removing a mailbox from a workspace: exactly what stops, exactly what is kept, what happens to work in flight, and the safer order to do it in. |
| Do not contact | The strongest entry in Connect's compliance record: what a do-not-contact instruction stops, how it is set, and why the Connect Assistant cannot lift one. |
| Editing a draft before it goes | Changing a reply before it goes out: which fields are editable, why the sending mailbox is not, and what your edit does to the record. |
| Email attachments | What Connect records about a document attached to an email, what it deliberately does not keep, and what a reply looks like when the enclosure was the message. |
| Email for the Owner and for a customer | Email runs on one implementation behind two doors. What the plan changes for a customer, what is identical, and where the asymmetry has actually gone wrong before. |
| Following up by email | Creating a dated commitment to write again, what is drafted when it falls due, and the reference rule that stops the same follow-up existing twice. |
| From a provider message to a canonical one | The journey from a provider's own message record to the canonical thread, message and contact the engine reads — and why the engine is not allowed near the provider table. |
| Giving guidance on a thread | The one-line direction you give on a thread: where it is stored, how far it reaches, and when it should be a standing instruction instead. |
| Gmail history sync | How a Gmail mailbox resumes by history ID instead of re-reading, what a gap in that history means, and what has to happen when the provider can no longer answer. |
| Gmail OAuth scopes and why each is asked for | The permissions a Google mailbox connection asks for, the capability behind each one, and precisely what stops working when one is withheld or withdrawn. |
| Held drafts | What happens to a reply Connect has written but is not allowed to send yet: where it waits, what the recipient sees, and how it is released. |
| How a reply is drafted | What goes into a reply before a word of it is written: the thread, grounded knowledge, memory at four tiers, the rules — and the answers Connect declines to give. |
| IMAP UID and cursor lifecycle | The UID cursor on an IMAP mailbox: what it records, the only outcome that advances it, and the two server-side events that can invalidate it entirely. |
| Large batches and bursts | What happens when hundreds of messages arrive at once: the two different orderings involved, why screen cost stays flat, and what a burst can genuinely distort. |
| Mailbox health | Why a mailbox that authenticates can still be failing, what the health verdicts on the row are derived from, and which action each situation actually calls for. |
| Mailbox roles | Why a mailbox carries a role, what the role changes about which address a reply leaves from and how it is written, and how to choose one deliberately. |
| Mailbox signatures | Where a signature is stored, which messages it is applied to, how it interacts with a reply you have edited, and what this documentation can and cannot say about footers. |
| Mailboxes | What one mailbox row owns, how several run at once in a workspace, what changes when you add another, and the defect that made a connected mailbox invisible. |
| Outreach email versus a customer reply | Outreach and a reply to a customer are two paths with different gates. What each must pass, why they are never merged, and what that costs in volume. |
| Proof that a message was sent | Why Connect will not call a message sent without the provider's own acknowledgement, what the third state means, and how a message in doubt is resolved. |
| Regenerating a draft | When writing a reply again is the right move rather than editing it, what a fresh pass picks up, and what regenerating costs you. |
| Remote images in email | Why an image in a received message loads through Connect's proxy instead of your browser, and what that changes for the sender and for you. |
| Rendering HTML email safely | How Connect renders an HTML message: what the sanitiser removes, why remote images never load from the sender, and what you read instead. |
| Resolving a sender to a person | How an email address becomes an identity, an identity a person and a person part of a company — and what happens the first time a stranger writes to you. |
| Sending email | The path an outbound message takes through Connect's single send boundary, the checks along it, and the narrow meaning the word sent may carry. |
| Spam complaints | What a spam complaint changes in Connect: escalation instead of a reply, a suppressed address at the provider, and a reputation state that can stop outreach. |
| Syncing an inbox | How a mailbox is read: what the first pass does that no later pass repeats, what each pass fetches, and what bounds a single pass rather than letting it run away. |
| The daily email allowance | What the daily email allowance counts, what happens when it is spent, and why the read cursor deliberately stays where it is on a refusal. |
| The primary mailbox | What falls back to a workspace's default sending mailbox, how that default is expressed and changed, and what to do when a workspace appears to have none. |
| The suppression list | The one list checked before any outreach: what puts an address on it, what it blocks, who may take an entry off, and how it differs from blocking a person. |
| Triage and priority | How a thread gets a priority, why threads.priority changes what the engine does next, and which triage marks belong to you alone. |
| Unsubscribe | How Connect honours an unsubscribe: the request is acted on rather than answered, a do-not-contact entry is written, and every send path checks it. |
| Writing back to Gmail | After Connect answers a Gmail thread it labels it and marks it read in the customer's own mailbox — and a failure to do so never blocks the reply. |
Questions#
Does Connect need a new email address of its own?
No. It works the mailboxes your business already has. Replies go out from the connected address over that mailbox's own provider, so the identity your customers see is the one they already write to.
Can Connect read one mailbox and send from another?
A reply is sent from the mailbox the thread belongs to. That is decided by the mailbox's role and by the thread's own history rather than chosen per message, so a conversation that started on one address stays on it.
What happens to email that arrives while the daily allowance is spent?
It is held, not dropped, and the read cursor deliberately does not advance. Needs You shows the refusal. The mail is still on the provider and is picked up on the next pass once the allowance resets.