JMAP for mail apps and developers
Mailocity speaks JMAP (RFC 8620 and RFC 8621) at /jmap/api, found from /.well-known/jmap. Mail apps and scripts sign in with HTTP Basic (the password, or an app password once two-step sign-in is on) or with a mailbox token (Authorization: Bearer cs_mt_... with the jmap scope).
Keeping in sync
Every type has its own state, and the server keeps a log of what changed, whoever changed it: new mail, IMAP clients, webmail, filters, imports.
Mailbox/changes,Email/changes,Thread/changes,Identity/changes,EmailSubmission/changes,SieveScript/changes,AddressBook/changesandContactCard/changessay exactly what was created, updated and destroyed since a state. PassmaxChangesto take them a page at a time (hasMoreChangessays more are waiting).Email/queryChanges,Mailbox/queryChanges,ContactCard/queryChanges,SieveScript/queryChangesandEmailSubmission/queryChangesbring a query's results up to date.removedlists everything that may have moved, andaddedputs back the ones still in the results at their place; applying both gives the current results.- The log keeps 30 days. A client further behind gets
cannotCalculateChangesand fetches everything again.
Hearing about changes
Changes are announced the moment they are saved, by every server process (Postgres notifications), in three ways:
- Event stream (
eventSourceUrl): aStateChangewith the states of the types that changed, includingEmailDeliveryfor new mail. Passtypes=Email,Mailboxto hear about only those. - WebSocket (RFC 8887): connect to the
urlin theurn:ietf:params:jmap:websocketcapability with thejmapsubprotocol and the same sign-in. SendRequestobjects (with anidto match theResponse), andWebSocketPushEnablewithdataTypesto getStateChangepushes; each carries apushStatethat a reconnecting client passes back to hear what it missed. Webmail sessions pass their CSRF token as?csrf=, and connections from other sites are refused. - Push subscriptions (RFC 8620 section 7.2), for apps that are not running:
PushSubscription/setcreates one with adeviceClientId, an httpsurl(usually a Web Push endpoint) and optionallykeys(p256dh,auth). The server posts aPushVerificationwith a code; settingverificationCodeto it turns the pushes on. With keys, every push is encrypted (RFC 8291); every push is signed with the server's VAPID key, which the session shows asapplicationServerKeyinurn:ietf:params:jmap:webpush-vapid(RFC 9749) for use with the browser'spushManager.subscribe. Subscriptions last up to 7 days (setexpiresagain to keep one), an account has up to 25 and may make 30 an hour,urlandkeysare never shown back, and one the push service says is gone is removed.
Searching mail
Email/query takes every filter in RFC 8621 section 4.4.1: inMailbox, inMailboxOtherThan, before, after, minSize, maxSize, hasKeyword, notKeyword, allInThreadHaveKeyword, someInThreadHaveKeyword, noneInThreadHaveKeyword, hasAttachment, text, from, to, cc, bcc, subject, body and header, joined with AND, OR and NOT operators (up to 10 deep, 50 conditions each).
- Address and subject filters match any part of the header, ignoring case.
bodyandtextmatch words in the message's text and attachment names, the way webmail search does (see search).headeris["Name"](the message has that header) or["Name", "text"](its value contains the text).
It sorts by receivedAt, sentAt, size, from, to, subject, hasKeyword, allInThreadHaveKeyword and someInThreadHaveKeyword, and takes position (negative counts from the end), anchor with anchorOffset, limit (at most 500 a page) and collapseThreads. A query reaches the first 100,000 results.
Mailbox/query filters by name, role, hasAnyRole, isSubscribed and parentId, and sorts by name and sortOrder. EmailSubmission/query filters by identityIds, emailIds, threadIds, undoStatus, before and after.
SearchSnippet/get takes the same filter and a list of emails, and returns each one's subject and a piece of its text with the matching words in <mark>, as HTML (everything else escaped), or null where nothing matched.
Reading messages
Email/get returns any header in any form RFC 8621 defines: header:X-Mailer:asText, header:Cc:asAddresses, header:List-Post:asURLs, header:Received:all and so on (asRaw, asText, asAddresses, asGroupedAddresses, asMessageIds, asDate, asURLs). headers lists every field as it was sent, and bodyStructure is the whole MIME tree. bodyProperties chooses what each part shows, including its headers and header: forms.
Email/parse reads a message that is only a blob: an upload, or a message attached to another one (its part's blobId). The parts of an attached message get blob ids of their own (<blob>-3-1 is part 1 of the message in part 3), which download and Blob/get read.
Blobs
With urn:ietf:params:jmap:blob (RFC 9404):
Blob/uploadmakes a blob from up to 64 pieces: text (data:asText), base64 (data:asBase64), or a range of another blob (blobIdwithoffsetandlength). A blob made earlier in the same request is named#creationId.Blob/getreads a blob or part of one (offset,length, at most 16 MB at a time) as text or base64, with itssizeanddigest:shaordigest:sha-256.Blob/lookupsays which emails, mailboxes and threads use a blob.
Uploads nothing uses are deleted after a day. Email/copy and Blob/copy copy between your account and mailboxes shared with you; within one account use Email/set or Blob/upload.
Quotas
With urn:ietf:params:jmap:quota (RFC 9425), Quota/get shows the account's limits with what is used: storage (bytes of mail), aliases, send-hour and send-day (recipients). Each has a warnLimit at 90%. Quota/changes, Quota/query (by name, scope, resourceType and type; sorted by name or used) and Quota/queryChanges work as for other types.
Read receipts
With urn:ietf:params:jmap:mdn (RFC 9007):
MDN/sendanswers a message that asks for a receipt (it has aDisposition-Notification-Toheader), from one of your identities. The message gets the$mdnsentkeyword, so a second receipt is refused withmdnAlreadySent;onSuccessUpdateEmailcan change it further. WithincludeOriginalMessage, the original's headers go along.MDN/parsereads a receipt you received: who it is from, the disposition, andforEmailId, the message of yours it answers.
Receipts are never sent by themselves: only when the app (or you) asks.
Signed mail
S/MIME signatures are checked (RFC 9219); see Signed mail.
Shared mailboxes and address books
Mailboxes shared with you are more accounts in the session; see Sharing. Principal/get, /query and /changes list your team, and ShareNotification/get, /query, /changes and /set (destroy) tell you about shares. AddressBook/set makes, renames and removes address books (onDestroyRemoveContents, onSuccessSetIsDefault), and ContactCard/copy copies cards between accounts.
After an upgrade
Mail stored before the Cc, Bcc, attachment and header filters existed is read again in the background after the server starts. Until that is done, those filters do not find older messages; nothing else changes, and clients are not told the messages changed.
Also supported
Tasks (JMAP for Tasks; see Tasks), Sieve scripts (RFC 9661), vacation replies, contacts (RFC 9610), sending with delayed send, blob upload and download, and result references. See mail filters, vacation replies, contacts and webmail.
Signing with S/MIME
EmailSubmission/set takes smimeSign: true (a Zappocity extension) to sign the message with the sender's S/MIME certificate and key kept in webmail settings; without them it fails with smimeSignFailed. It cannot be combined with sending later. See S/MIME.