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.
Objects and methods
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.
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
headersexplicitly and you get them. partIdandsubPartssurvive an explicit projection inside the tree. A tree without its branches is not a tree, so omittingsubPartsfrombodyPropertiesreads as a mistake rather than as a wish to flatten, and the server keeps them.
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
notFoundis never empty when it should not be. Every unparseable or unknown id in a/getlands innotFoundexplicitly.updatedvalues arenull. Per RFC 8620 §5.3, check membership (id in updated), not truthiness of the value.hasAttachmentmeans attachments. The flag is computed from the MIME structure at ingestion, not from text extraction.- An attached message is one attachment, not its contents.
attachmentsis drawn from this message’s body structure (RFC 8621 §4.1.7), so amessage/rfc822part is the attachment and its own parts are not advertised alongside it. Forwarding a mail that itself carriedcontract.pdflists the.eml, notcontract.pdf— the reader is never told they received files the sender never attached. The encapsulated parts remain reachable: parse the.emlblob withEmail/parseand 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/getandThread/changesneed only the mail capability. RFC 8621 §3 makes that pair mandatory, so those two methods carry both capabilities —urn:ietf:params:jmap:mailas primary,v2:threadsas additional — and a client that declares nothing butcore+mailis 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 withproperties.- Anchor pagination is exact.
anchor+anchorOffset+positioninEmail/queryfollow RFC 8620 §5.5 to the letter, including negative positions. - Drafts can be rewritten. A body update on a
$draftmessage works through the standard destroy+create cycle; the server does not reject draft edits. - Send-to-self delivers.
onSuccessUpdateEmailruns after delivery, and deduplication never happens at the message level — mailing yourself works. - Scheduled send is first-class. An
EmailSubmissionwith 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 fromusing→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).
Sending
The submission pipeline validates thefrom 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. 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 withurn: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
$mdnsentkeyword, and OxiMail holds the RFC letter — theonSuccessUpdateEmailpatch must setkeywords/$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$mdnsentdoes not re-arm the send.