# Connecting a Microsoft mailbox

A Microsoft mailbox connects as a third transport alongside Gmail and IMAP, and its adapter fetches mail through Microsoft Graph. Once a message crosses the bridge into canonical threads and messages, nothing downstream knows or cares that it came from Microsoft — triage, drafting, autonomy, sending, audit and memory are the same code for all three.

- **Status:** Available
- **Audience:** both
- **Channels:** email
- **In the app:** #/mailboxes
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/email/microsoft-graph/

## What the adapter is responsible for

An adapter has a narrow job: authenticate, fetch, store in `tenant_*` in the provider's own shape, and hand outbound messages to the provider when `outbound.py` asks. `providers.build()` reads `transport` and `config` and nothing else. That is the whole contract, and it is the reason a third provider was an adapter rather than a change to the engine.

Everything past the bridge is shared. The Graph adapter does not have its own triage, its own drafting or its own send path, and it cannot acquire one without that showing up as a second implementation — which is the thing this codebase is organised to prevent.

## Where it differs from Gmail

| Concern | Gmail | Microsoft |
|---|---|---|
| Authentication | Google OAuth | The Microsoft identity flow |
| Fetching | Gmail API | Microsoft Graph's mail API |
| Resuming after a gap | History IDs — [Gmail history sync](/docs/email/gmail-history-sync/) | The adapter's own position; the rule governing it is the same |
| Write-back | Read state and labels — [Writing back to Gmail](/docs/email/gmail-writeback/) | Whatever the adapter does with Microsoft's equivalents; no reply depends on it |
| Everything after the bridge | Identical | Identical |

The resume mechanism is the difference that matters operationally. Each adapter keeps its own idea of where it got to, but the governing rule is written once and is not negotiable per provider: when work is refused — most often because the daily allowance is spent — the position **does not advance**. Advancing a read position on a refusal is how a mail agent loses messages permanently, and it is a mistake that is invisible until somebody asks why a customer was never answered.

## What is identical

- The mailbox row: `role`, `signature`, `autonomy` and the health verdicts, on the same screen with the same meanings.
- Canonicalisation: `connect_core.bridge_*` produces the same `threads`, `messages` and `contacts` from a Microsoft message as from any other.
- Contact resolution: an address becomes an identity and an identity resolves to a person, regardless of which mailbox it arrived through.
- Autonomy: the same four modes at the same four scopes, with the mailbox as the *endpoint* tier.
- Sending: `outbound.py`, with **sent** reserved for a message the provider has acknowledged, and **uncertain** shown as itself rather than guessed either way.
- Compliance: suppression, unsubscribe, complaints and do-not-contact are checked in one place before any outreach, whatever the transport.

This is the payoff of the two-model design rather than a coincidence. Because the engine never reads the provider tables, the list above is not a set of features that were carefully re-implemented for Microsoft — it is the same code path, reached by a different adapter.

## Choosing between Graph and IMAP for a Microsoft mailbox

A Microsoft-hosted mailbox can often be reached either way, and the choice is worth making deliberately. Graph is an API: it authenticates through Microsoft's identity flow rather than a stored password, and it resumes through the provider's own change tracking. IMAP means a credential you type and a UID cursor you should understand — see [Connecting an IMAP and SMTP server](/docs/email/connect-imap-smtp/).

Whichever you pick, the mailbox is one transport. Switching later means reconnecting the address on the new transport, and the uniqueness check is what stops you ending up with two rows for one real inbox — the check that an empty workspace stamp once defeated.

## Questions

### Is a Microsoft mailbox a second-class provider here?

No, and the architecture is what makes that answerable rather than a claim. An adapter's whole surface is authenticate, fetch, store and hand off; every feature a reader would actually notice lives after the bridge, shared by all three.

### Does a shared or delegated Microsoft mailbox work?

What Connect needs is a grant that lets the adapter read and send for that address. Whether your tenant permits that for a shared mailbox is a Microsoft administration question, and the consent flow is where it is answered.

### Which provider should a new workspace connect first?

The one your business actually reads mail in. There is no capability advantage to choose between, and connecting a mailbox nobody uses produces a quiet mailbox and a misleading sense that nothing is working.

## Related

- [Mailboxes](https://connectbyjbrh.com/docs/email/mailboxes/)
- [Connecting an IMAP and SMTP server](https://connectbyjbrh.com/docs/email/connect-imap-smtp/)
- [From a provider message to a canonical one](https://connectbyjbrh.com/docs/email/message-canonicalisation/)
- [Syncing an inbox](https://connectbyjbrh.com/docs/email/inbox-sync/)
- [Microsoft Graph mail](https://connectbyjbrh.com/docs/technology/microsoft-graph/)
- [OAuth 2.0](https://connectbyjbrh.com/docs/technology/oauth/)

## What this page is based on

- Connect source pack — channels, §1: three providers, `providers.build()`, the allowance refusal and the cursor
- Connect source pack — the data model: mail exists twice, the engine never reads provider tables (`docs-source/sources/GENERAL.md` §3)
- Connect capability registry (`docs-source/facts.py`) — `CHANNELS['email']`
