> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Mail

> The JMAP Mail surface (RFC 8621): Mailbox, Email, Thread, SearchSnippet, Identity, EmailSubmission, VacationResponse, MDN — the methods, the OxiMail-specific extensions, and the behaviours worth knowing.

Mail is the [RFC 8621](https://www.rfc-editor.org/rfc/rfc8621) surface, requested with `urn:ietf:params:jmap:mail` (plus `submission` and `vacationresponse` for sending and auto-reply). This page lists what OxiMail serves and where it goes beyond the RFC. The mechanics shared by every method — batching, back-references, `/changes`, push — are on [JMAP Core](./jmap-core).

## Objects and methods

| Object             | Methods                                                                                              | Notes                                                                                                                                                          |
| ------------------ | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Mailbox`          | `get`, `set`, `query`, `queryChanges`, `changes`                                                     | Folders with roles, hierarchy, per-folder counts, and `shareWith` grants.                                                                                      |
| `Email`            | `get`, `set`, `query`, `queryChanges`, `changes`, `import`, `parse`                                  | The message object; see the behaviours below.                                                                                                                  |
| `Thread`           | `get`, `changes` (mail capability); `query`, `queryChanges`, `set`, `archive`, `merge`, `split` (v2) | The conversation aggregate, threaded by stored Message-ID (exact string, never a hash).                                                                        |
| `SearchSnippet`    | `get`                                                                                                | Highlighted matches for search results.                                                                                                                        |
| `Identity`         | `get`, `set`, `changes`, `queryChanges`                                                              | Sender identities; aliases are sendable identities (ADR-089).                                                                                                  |
| `EmailSubmission`  | `get`, `set`, `query`, `changes`, `queryChanges`                                                     | Send requests, `onSuccessUpdateEmail`, scheduled send.                                                                                                         |
| `VacationResponse` | `get`, `set`                                                                                         | The auto-reply singleton.                                                                                                                                      |
| `MDN`              | `send`, `parse`                                                                                      | Read receipts ([RFC 9007](https://www.rfc-editor.org/rfc/rfc9007)), requested with `urn:ietf:params:jmap:mdn` — see [read receipts](#read-receipts-mdn) below. |
| `Quota`            | `get`                                                                                                | Storage quota visibility.                                                                                                                                      |
| `Preferences`      | `get`, `set`                                                                                         | Server-stored client preferences (OxiMail extension, singleton per account).                                                                                   |

OxiMail-specific method extensions on the mail surface: **`Email/send`** (one-call submission convenience), **`Email/cancel`** (cancel a scheduled/undo-window send), and **`Email/wake`** (snooze expiry). Reindexing is an admin concern, not a client one.

## The body tree

`Email/get` derives everything it says about a message's body from **one depth-first walk of the real MIME tree**. That is what makes `partId` mean the same thing in every property of the same response: the id you read in `bodyStructure` is the id you find on the attachment, and the key you look up in `bodyValues`.

| Property                | What you get                                                                                                                                                                                                                      |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bodyStructure`         | The tree itself, recursive. `partId` is `null` **if and only if** the part is a `multipart/*`; every leaf carries one. A `message/rfc822` part is a leaf, consistent with [the attachment rule](#behaviours-worth-knowing) below. |
| `textBody` / `htmlBody` | The primary text and HTML parts (first Text, first Html). The `type` announced is the part's real media type.                                                                                                                     |
| `bodyValues`            | The decoded content of **every** `text/*` leaf, charset applied — `text/calendar`, `text/csv` and `text/vcard` included, not just plain and HTML.                                                                                 |
| `attachments`           | The same selection as before, each entry now carrying `partId`, `charset` and `disposition`.                                                                                                                                      |

<Warning>
  **`bodyValues` is empty unless you ask for it.** RFC 8621 §4.2 makes the content conditional on `fetchTextBodyValues`, `fetchHTMLBodyValues` or `fetchAllBodyValues`, and OxiMail now holds that letter — the values used to be populated whether or not the arguments were present. A client that never set a `fetch*` argument and read `bodyValues` anyway will read an empty map. Set the argument. `Email/get` and `Email/parse` run the same selection, so the rule is identical on both.
</Warning>

**Per-part headers are how you read Content-Type parameters.** `type` is normatively bare — `text/calendar` and nothing more — so the parameters that carry the meaning are only reachable by asking for the headers of the part: `bodyProperties: ["partId", "type", "headers"]`. This is the RFC-blessed way to tell a genuine invitation (`method=REQUEST`) from a plain `.ics` someone attached, **without downloading the blob**.

Two deviations are deliberate and worth knowing:

* **The default projection strips per-part headers.** The default shape stays a subset of the normative §4.2 property list; ask for `headers` explicitly and you get them.
* **`partId` and `subParts` survive an explicit projection inside the tree.** A tree without its branches is not a tree, so omitting `subParts` from `bodyProperties` reads as a mistake rather than as a wish to flatten, and the server keeps them.

Two shapes carry no `bodyStructure` at all, by construction: a message the parser refused and fell back to reading flat (it never announces a structure it did not parse), and a native draft, whose canonical v2 shape is `body`/`html`.

## Behaviours worth knowing

* **`notFound` is never empty when it should not be.** Every unparseable or unknown id in a `/get` lands in `notFound` explicitly.
* **`updated` values are `null`.** Per RFC 8620 §5.3, check membership (`id in updated`), not truthiness of the value.
* **`hasAttachment` means attachments.** The flag is computed from the MIME structure at ingestion, not from text extraction.
* **An attached message is one attachment, not its contents.** `attachments` is drawn from *this* message's body structure (RFC 8621 §4.1.7), so a `message/rfc822` part **is** the attachment and its own parts are not advertised alongside it. Forwarding a mail that itself carried `contract.pdf` lists the `.eml`, not `contract.pdf` — the reader is never told they received files the sender never attached. The encapsulated parts remain reachable: parse the `.eml` blob with `Email/parse` and their blobs download normally (every part is stored, nested ones included — what the wire list answers and what is downloadable are two different questions).
* **`Thread/get` and `Thread/changes` need only the mail capability.** RFC 8621 §3 makes that pair mandatory, so those two methods carry both capabilities — `urn:ietf:params:jmap:mail` as primary, `v2:threads` as additional — and a client that declares nothing but `core` + `mail` is served. The richer thread verbs (`query`, `queryChanges`, `set`, `archive`, `merge`, `split`) stay v2 extensions. The RFC's `{id, emailIds}` shape is a strict subset of what the object serves: select it with `properties`.
* **Anchor pagination is exact.** `anchor` + `anchorOffset` + `position` in `Email/query` follow RFC 8620 §5.5 to the letter, including negative positions.
* **Drafts can be rewritten.** A body update on a `$draft` message works through the standard destroy+create cycle; the server does not reject draft edits.
* **Send-to-self delivers.** `onSuccessUpdateEmail` runs after delivery, and deduplication never happens at the message level — mailing yourself works.
* **Scheduled send is first-class.** An `EmailSubmission` with a future send time is queued and cancellable (`Email/cancel`) until it leaves; disabled accounts' scheduled sends are cancelled at the source.
* **Strictness is a feature.** Unknown `accountId` → `accountNotFound`; a capability missing from `using` → `unknownCapability`; a bad back-reference → `invalidResultReference`. The server is the reference implementation and does not have a lenient mode.

## Filtering and search

`Email/query` supports the RFC 8621 filter tree (`inMailbox`, `from`/`to`/`subject`/`text`, `hasKeyword`, `hasAttachment`, dates, sizes) with `AND`/`OR`/`NOT` combinators, and full-text conditions run against the Tantivy index — searchable the moment SMTP answered `250 OK` (accepted means searchable). `SearchSnippet/get` returns highlighted fragments for result lists.

## Keywords and flags

JMAP keywords (`$seen`, `$flagged`, `$draft`, `$answered`, `$junk`, custom) are the same state IMAP flags read and write — one store, two protocols. The spam pipeline files with `$junk`; user Labels are a separate v2 object ([JMAP v2](./jmap-v2)).

## Sending

The submission pipeline validates the `from` against the account's identities (envelope **and** header — forging a colleague's address fails with `forbiddenFrom`), stores the message encrypted, and hands it to the [delivery queue](../operator/mail-and-smtp). `onSuccessUpdateEmail`/`onSuccessDestroyEmail` run **after** delivery is accepted, in the same request's response.

## Read receipts (MDN)

Message Disposition Notifications are the RFC 9007 surface, requested with `urn:ietf:params:jmap:mdn`. Both directions are served.

**Reading the receipts you were sent.** Inbound MDNs are linked onto the sent message they answer at SMTP ingest, paired on `Original-Message-ID` and exposed as `Email.mdnBlobIds`. `MDN/parse` turns such a blob into the structured `MDN` object — `disposition`, `originalMessageId`, `finalRecipient`, `reportingUA`, `originalRecipient`, `mdnGateway`, `error`, `extensionFields` — so a client renders "your message was displayed on …" without reimplementing RFC 8098 itself. Verdicts are **per blob**, never a method-level collapse: each id lands in `parsed`, `notParsable` (it parses as mail but is not an MDN, or does not parse at all — RFC 9007 draws no finer line), or `notFound`. Empty groups are served as `null`, which is the RFC's own shape.

**Sending one.** `MDN/send` composes an RFC 8098 receipt for a received message and hands it to the outbound queue, with `disposition` and optional `subject`, `textBody`, `reportingUA`, `finalRecipient` (which must be an address this account may send as, `forbiddenFrom` otherwise), `includeOriginalMessage`, and `extensionFields`.

Two properties of that method are worth knowing before you build on it:

* **Nothing sends an MDN automatically.** Emission is an explicit user gesture, and this method *is* the gesture. The receipt goes out with a null envelope sender (RFC 8098 §3) and does not generate follow-up traffic if it fails.
* **"Already sent" is not client-erasable.** RFC 9007 anchors the anti-duplicate check on the `$mdnsent` keyword, and OxiMail holds the RFC letter — the `onSuccessUpdateEmail` patch **must** set `keywords/$mdnsent: true`, refused by name otherwise, so an RFC-literal third-party client works unmodified. But it checks a server-side audit timestamp *alongside* the keyword, written atomically with the patch. Clearing `$mdnsent` does not re-arm the send.

For the classic-protocol view of the same data, see [legacy protocols](../operator/legacy-protocols); for v2-only mail extensions (labels, snooze, saved messages), see [JMAP v2](./jmap-v2).
