# Choosing a backend

Epistole sends the same message through any backend, so choosing one is a deployment decision, not a code decision.

Use **SMTP** unless something stops you. It works with every mail system. It carries the largest messages. It sends exactly the MIME Epistole built.

Use **Graph** when your tenant has turned SMTP AUTH off, or when you need a retry hint on throttling. Accept its body limit before you choose it. Epistole treats it as 4 MB, taken conservatively from Microsoft's unitless "4 MB". That figure is not yet measured against a live tenant.

Use **Gmail** when you are already authenticated against a Google account and would rather not manage an SMTP credential.

|  | SMTP | Gmail | Graph |
|----|----|----|----|
| Mail systems served | any | Google accounts | Exchange Online |
| Largest body | whole-message limit | whole-message limit | **4 MB, no path past it** |
| Largest message | server `SIZE`, 35 MB on a default Exchange Online tenant | 25 MB of attachment, 35 MB of request | 35 MB default, 1 MB to 150 MB configurable |
| Largest attachment | shares the message limit | shares the message limit | 150 MB, via upload session |
| Per-recipient refusals | visible | not expressible | not expressible |
| Retry hint | none | none documented | `Retry-After` |
| MIME you send | unchanged | unchanged, except the `Message-ID` | rebuilt by Exchange |
| Recipients per message | server policy | 500 | 500 |
| Credentials | anonymous, [Password](../reference/smtp.Password.md#epistole.smtp.Password), or [OAuth](../reference/smtp.OAuth.md#epistole.smtp.OAuth) | [ServiceAccount](../reference/gmail.ServiceAccount.md#epistole.gmail.ServiceAccount) or [AuthorizedUser](../reference/gmail.AuthorizedUser.md#epistole.gmail.AuthorizedUser) | [ClientSecret](../reference/graph.ClientSecret.md#epistole.graph.ClientSecret), [Certificate](../reference/graph.Certificate.md#epistole.graph.Certificate), or [ManagedIdentity](../reference/graph.ManagedIdentity.md#epistole.graph.ManagedIdentity) |

Gmail and Graph also take any object with [get_token](../reference/TokenCredential.md#epistole.TokenCredential.get_token), the [TokenCredential](../reference/TokenCredential.md#epistole.TokenCredential) shape `azure-identity` implements. SMTP takes one inside [OAuth](../reference/smtp.OAuth.md#epistole.smtp.OAuth), with an explicit `scope=`. Epistole pre-checks only what a vendor documents: Gmail's 35 MiB request and 500 recipients, and Graph's 150 MB attachment, 500 recipients, and custom header names that start with `x-`. SMTP gets none, because `smtplib` already negotiates `SIZE` with the server. Everywhere else, Epistole maps the service's reply onto the same error a pre-check would have raised.

The choice depends on four things.

**Check the body size.** Graph caps the entire write request at 4 MB, a figure Microsoft publishes without units. Graph has no chunked path for a message body. So a large embedded HTML report cannot be sent through Graph at all. SMTP and Gmail measure against the whole message, so a body of several MB is routine on both. If you send through Graph, attach the report as a file and keep the body small.

**Check whether your tenant allows SMTP AUTH.** On Exchange Online, security defaults and any policy that blocks basic authentication switch SMTP client submission off. The trend is toward switching it off. Graph works under all of these settings. The per-mailbox SMTP AUTH setting overrides the organization setting, so one enabled mailbox is the documented workaround.

**Weigh fidelity against features.** SMTP and Gmail take the complete RFC 5322 message Epistole builds, so the recipient receives the MIME structure you send. The Gmail API does replace its `Message-ID` with one of its own. Graph takes a flat JSON array, and Exchange serializes the MIME later. So Epistole can guarantee that your `cid:` references resolve, but not the MIME structure around them. In exchange, Graph is the only backend that returns how long to wait when it throttles you.

**Weigh the access each permission grants.** For a plain send the three are comparable: an Exchange Online SMTP OAuth grant with no claim added, the `gmail.send` scope, `Mail.Send`. SMTP through Gmail is the exception. XOAUTH2 there requires `https://mail.google.com/`, which grants full mailbox access. So the Gmail backend requests less access than SMTP does. Once a serialized Graph message exceeds 4 MB, Epistole takes the draft path. That path needs `Mail.ReadWrite`, which grants reading every message in scope. If you send through Graph, keep the whole message under 4 MB. A security reviewer will prefer the narrower grant. The 3 MB figure you may have seen is a second, internal threshold. Inside the draft path, Epistole uses it to choose between one call and an upload session for an attachment. It changes no permission.

[ConsoleBackend](../reference/ConsoleBackend.md#epistole.ConsoleBackend) and [MemoryBackend](../reference/MemoryBackend.md#epistole.MemoryBackend) are backends like any other. Swapping one in changes the constructor and nothing else. [MemoryBackend](../reference/MemoryBackend.md#epistole.MemoryBackend) records what it accepted as `backend.submissions`, so a test reads `submissions[0].message.to_`. [ConsoleBackend](../reference/ConsoleBackend.md#epistole.ConsoleBackend) prints a readable rendering rather than the raw bytes any one backend sends.

These limits were quoted on 2026-09-08, and they change over time. Re-check one before relying on it. The sources are [Graph request limits](https://learn.microsoft.com/en-us/graph/use-the-api), [Graph large attachments](https://learn.microsoft.com/en-us/graph/outlook-large-attachments), [Exchange Online limits](https://learn.microsoft.com/en-us/office365/servicedescriptions/exchange-online-service-description/exchange-online-limits), [Gmail sending limits](https://knowledge.workspace.google.com/admin/gmail/gmail-sending-limits-in-google-workspace), [Exchange rebuilds MIME](https://learn.microsoft.com/en-us/graph/outlook-things-to-know-about-send-mail), [SMTP `SIZE`, RFC 1870](https://datatracker.ietf.org/doc/html/rfc1870), and [SMTP AUTH on Exchange Online](https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission). Fuller working is in [`docs/research/send-boundary-semantics.md`](https://github.com/ozanozbeker/epistole/blob/main/docs/research/send-boundary-semantics.md) and [`docs/research/attachment-and-inline-rules.md`](https://github.com/ozanozbeker/epistole/blob/main/docs/research/attachment-and-inline-rules.md).
