JMAP N. Jenkins
Internet-Draft FastMail
Intended status: Standards Track October 19, 2016
Expires: April 22, 2017

JMAP for Mail
draft-jenkins-jmapmail-00

Abstract

This document specifies a data model for synchronising email data with a server using JMAP.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at http://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on April 22, 2017.

Copyright Notice

Copyright (c) 2016 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.


Table of Contents

1. Introduction

JMAP is a generic protocol for synchronising data, such as mail, calendars or contacts, between a client and a server. It is optimised for mobile and web environments, and aims to provide a consistent interface to different data types.

This specification defines a data model for synchronising mail between a client and a server using JMAP.

1.1. Notational Conventions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119].

The underlying format used for this specification is JSON. Consequently, the terms "object" and "array" as well as the four primitive types (strings, numbers, booleans, and null) are to be interpreted as described in Section 1 of [RFC7159].

Some examples in this document contain "partial" JSON documents used for illustrative purposes. In these examples, three periods "..." are used to indicate a portion of the document that has been removed for compactness.

Types signatures are given for all JSON objects in this document. The following conventions are used:

1.2. Terminology

The same terminology is used in this document as in the core JMAP specification.

1.3. Addition to the capabilities object

The capabilities object is returned as part of the standard JMAP authentication response; see the JMAP spec. Servers supporting this specification MUST add a property called {TODO: URI for this spec} to the capabilities object. The value of this property is an object which SHOULD contain the following information on server capabilities:

2. Mailboxes

A mailbox represents a named set of emails. This is the primary mechanism for organising messages within an account. It is analogous to a folder in IMAP or a label in other systems. A mailbox may perform a certain role in the system; see below for more details.

For compatibility with IMAP, a message MUST belong to one or more mailboxes. The message id does not change if the message changes mailboxes.

A Mailbox object has the following properties:

The Trash mailbox (that is a mailbox with role == "trash") MUST be treated specially:

The result of this is that messages in the Trash are treated as though they are in a separate thread for the purposes of mailbox counts. It is expected that clients will hide messages in the Trash when viewing a thread in another mailbox and vice versa. This allows you to delete a single message to the Trash out of a thread.

For example, suppose you have an account where the entire contents is a single conversation with 2 messages: an unread message in the Trash and a read message in the Inbox. The unreadThreads count would be 1 for the Trash and 0 for the Inbox.

Destroying a mailbox MUST NOT delete any messages still contained within it. It only removes them from the mailbox. Since messages MUST always be in at least one mailbox, if the last mailbox they are in is deleted the messages must be added to the mailbox with role == "inbox". If no Inbox exists, the messages must be moved to any other mailbox; this is server dependent.

2.1. getMailboxes

Mailboxes can either be fetched explicitly by id, or all of them at once. To fetch mailboxes, make a call to getMailboxes. It takes the following arguments:

The response to getMailboxes is called mailboxes. It has the following arguments:

The following errors may be returned instead of the mailboxes response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

requestTooLarge: Returned if the number of ids requested by the client exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

2.2. getMailboxUpdates

The getMailboxUpdates call allows a client to efficiently update the state of its cached mailboxes to match the new state on the server. It takes the following arguments:

The response to getMailboxUpdates is called mailboxUpdates. It has the following arguments:

If a mailbox has been modified AND deleted since the oldState, the server should just return the id in the removed array, but MAY return it in the changed array as well. If a mailbox has been created AND deleted since the oldState, the server SHOULD remove the mailbox id from the response entirely, but MAY include it in the removed array.

The following errors may be returned instead of the mailboxUpdates response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

cannotCalculateChanges: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old. The client MUST invalidate its Mailbox cache.

2.3. setMailboxes

Mailboxes can be created, updated and destroyed using the setMailboxes method. The method takes the following arguments:

If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method.

2.3.1. Ordering of changes

Each create, update or destroy is considered an atomic unit. The server MAY commit some of the changes but not others, however MUST NOT only commit part of an update to a single record (e.g. update the name field but not the parentId field, if both are supplied in the update object).

The final state MUST be valid after the setMailboxes is finished, however the server MAY have to transition through invalid intermediate states (not exposed to the client) while processing the individual create/update/destroy requests. For example, a single method call could rename Mailbox A => B, and simultaneously rename Mailbox B => A. The final state is valid, so this is allowed, however if processed sequentially there will be an internal state where temporarily both mailboxes have the same name.

A Mailbox may reference another Mailbox object as a parent. When a Mailbox is created or modified, it may reference another Mailbox being created in the same API request by using the creation id prefixed with a #. The order of the method calls in the request by the client MUST be such that the mailbox being referenced is created in either the same or an earlier method call. If within the same method call, the server MUST process the parent create first, as if this fails the create/update that references it will also fail.

Creation ids sent by the client SHOULD be unique within the single API request for a particular data type. If a creation id is reused, the server MUST map the creation id to the most recently created item with that id.

2.3.2. Creating mailboxes

The properties of the Mailbox object submitted for creation MUST conform to the following conditions:

If any of the properties are invalid, the server MUST reject the create with an invalidProperties error. The Error object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object MAY also contain a description property of type String with a user-friendly description of the problems.

There may be a maximum number of mailboxes allowed on the server. If this is reached, any attempt at creation will be rejected with a maxQuotaReached error.

2.3.3. Updating mailboxes

If the id given does not correspond to a Mailbox in the given account, the update MUST be rejected with a notFound error.

All properties being updated must be of the correct type, not immutable or server-set-only, and the new value must obey all conditions of the property. In particular, note the following conditions:

If any of the properties are invalid, the server MUST reject the update with an invalidProperties error. The Error object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object MAY also contain a description property of type String with a user-friendly description of the problems.

2.3.4. Destroying mailboxes

If the id given does not correspond to a Mailbox in the given account, the destruction MUST be rejected with a notFound error.

If the mailbox has mayDeleteMailbox == false, the destruction MUST be rejected with a forbidden error.

A mailbox MAY NOT be destroyed if it still has any child mailboxes. Attempting to do so will result in the destruction being rejected with a mailboxHasChild error.

Destroying a mailbox MUST NOT delete any messages still contained within it. It only removes them from the mailbox. Since messages MUST always be in at least one mailbox, if the last mailbox they are in is deleted the messages must be added to the mailbox with role == "inbox". If no Inbox exists, the messages must be moved to any other mailbox; this is server dependent.

There MUST always be at least one mailbox. It is expected that the server will enforce this by setting mayDeleteMailbox == false on at least the Inbox, if not all system mailboxes. However, if this is not the case, an attempt to destroy the last mailbox MUST still be rejected with a mailboxRequired error.

2.3.5. Response

The response to setMailboxes is called mailboxesSet. It has the following arguments:

The following errors may be returned instead of the mailboxesSet response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

accountReadOnly: Returned if the account has isReadOnly == true.

requestTooLarge: Returned if the total number of objects to create, update or destroy exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

stateMismatch: Returned if an ifInState argument was supplied and it does not match the current state.

Example request:

[ "setMailboxes", {
  "ifInState": "ms4123",
  "update": {
    "f3": {
      "name": "The new name"
    }
  },
  "destroy": [ "f5" ]
}, "#0" ]

3. MessageLists

A MessageList is a sorted query on the set of messages in a user's account. Since it can be very long, the client must specify what section of the list to return. The client can optionally also fetch the threads and/or messages for this part of the list.

The same message may appear in multiple messages lists. For example, it may belong to multiple mailboxes, and of course it can appear in searches. Since messages have an immutable id, a client can easily tell if it already has a message cached and only fetch the ones it needs.

When the state changes on the server, a delta update can be requested to efficiently update the client's cache of this list to the new state. If the server doesn't support this, the client still only needs to fetch the message list again, not the messages themselves.

3.1. getMessageList

To fetch a message list, make a call to getMessageList. It takes the following arguments:

3.1.1. Filtering

A FilterOperator object has the following properties:

A FilterCondition object has the following properties:

If zero properties are specified on the FilterCondition, the condition MUST always evaluate to true. If multiple properties are specified, ALL must apply for the condition to be true (it is equivalent to splitting the object into one-property conditions and making them all the child of an AND filter operator).

The exact semantics for matching String fields is deliberately not defined to allow for flexibility in indexing implementation, subject to the following:

3.1.2. Sorting

The sort argument lists the properties to compare between two messages to determine which comes first in the sort. If two messages have an identical value for the first property, the next property will be considered and so on. If all properties are the same (this includes the case where an empty array or null is given as the argument), the sort order is server-dependent, but MUST be stable between calls to getMessageList.

Following the property name there MUST be a space and then either the string asc or desc to specify ascending or descending sort for that property.

The following properties MUST be supported for sorting:

The following properties SHOULD be supported for sorting:

The server MAY support sorting based on other properties as well. A client can discover which properties are supported by inspecting the server's capabilities object (see section 1).

The method of comparison depends on the type of the property:

3.1.3. Thread collapsing

When collapseThreads == true, then after filtering and sorting the message list, the list is further winnowed by removing any messages for a thread id that has already been seen (when passing through the list sequentially). A thread will therefore only appear once in the threadIds list of the result, at the position of the first message in the list that belongs to the thread.

3.1.4. Windowing

If a position offset is supplied, then this is the 0-based index of the first result to return in the list of messages after filtering, sorting and collapsing threads. If the index is greater than or equal to the total number of messages in the list, then there are no results to return, but this DOES NOT generate an error. If position is null (or, equivalently, omitted) this MUST be interpreted as position: 0.

Alternatively, a message id, called the anchor may be given. In this case, after filtering, sorting and collapsing threads, the anchor is searched for in the message list. If found, the anchor offset is then subtracted from this index. If the resulting index is now negative, it is clamped to 0. This index is now used exactly as though it were supplied as the position argument. If the anchor is not found, the call is rejected with an anchorNotFound error.

If an anchor is specified, any position argument supplied by the client MUST be ignored. If anchorOffset is null, it defaults to 0. If no anchor is supplied, any anchor offset argument MUST be ignored.

3.1.5. Response

The response to a call to getMessageList is called messageList. It has the following arguments:

The following errors may be returned instead of the messageList response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

unsupportedSort: Returned if the sort includes a property the server does not support sorting on.

cannotDoFilter: Returned if the server is unable to process the given filter for any reason.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

anchorNotFound: Returned if an anchor argument was supplied, but it cannot be found in the message list.

3.2. getMessageListUpdates

The getMessageListUpdates call allows a client to efficiently update the state of any cached message list to match the new state on the server. It takes the following arguments:

The response to getMessageListUpdates is called messageListUpdates It has the following arguments:

A RemovedItem object has the following properties:

An AddedItem object has the following properties:

The result of this should be that if the client has a cached sparse array of message ids in the list in the old state:

messageIds = [ "id1", "id2", null, null, "id3", "id4", null, null, null ]

then if it splices out all messages in the removed array:

removed = [{ messageId: "id2", … }];
messageIds => [ "id1", null, null, "id3", "id4", null, null, null ]

and splices in (in order) all of the messages in the added array:

added = [{ messageId: "id5", index: 0, … }];
messageIds => [ "id5", "id1", null, null, "id3", "id4", null, null, null ]

then the message list will now be in the new state.

The following errors may be returned instead of the messageListUpdates response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

tooManyChanges: Returned if there are more changes the the client's maxChanges argument. Each item in the removed or added array is considered as one change. The client may retry with a higher max changes or invalidate its cache of the message list.

cannotCalculateChanges: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old. The client MUST invalidate its cache of the message list.

4. Threads

Replies are grouped together with the original message to form a thread. In JMAP, a thread is simply a flat list of messages, ordered by date. Every message MUST belong to a thread, even if it is the only message in the thread.

The JMAP spec does not require the server to use any particular algorithm for determining whether two messages belong to the same thread, however there is a recommended algorithm in the implementation guide.

If messages are delivered out of order for some reason, a user may receive two messages in the same thread but without headers that associate them with each other. The arrival of a third message in the thread may provide the missing references to join them all together into a single thread. Since the threadId of a message is immutable, if the server wishes to merge the threads, it MUST handle this by deleting and reinserting (with a new message id) the messages that change threadId.

A Thread object has the following properties:

4.1. getThreads

Threads can only be fetched explicitly by id. To fetch threads, make a call to getThreads. It takes the following arguments:

The response to getThreads is called threads. It has the following arguments:

The following errors may be returned instead of the threads response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

requestTooLarge: Returned if the number of ids requested by the client exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

Example of a successful request:

[ "getThreads", {
  "ids": ["f123u4", "f41u44"],
  "fetchMessages": false,
  "fetchMessageProperties": null
}, "#1" ]

and response:

[ "threads", {
  "state": "f6a7e214",
  "list": [
    {
      "id": "f123u4",
      "messageIds": [ "eaa623", "f782cbb"]
    },
    {
      "id": "f41u44",
      "messageIds": [ "82cf7bb" ]
    }
  ],
  "notFound": null
}, "#1" ]

4.2. getThreadUpdates

When messages are created or deleted, new threads may be created, or the set of messages belonging to an existing thread may change. If a call to getThreads returns with a different state string in the response to a previous call, the state of the threads has changed on the server and the client needs to work out which part of its cache is now invalid.

The getThreadUpdates call allows a client to efficiently update the state of any cached threads to match the new state on the server. It takes the following arguments:

The response to getThreadUpdates is called threadUpdates. It has the following arguments:

If a maxChanges is supplied, or set automatically by the server, the server MUST ensure the number of ids returned across changed and removed does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call getThreadUpdates until it is fully up to date. If it is unable to calculat an intermediate state, it MUST return a cannotCalculateChanges error response instead.

If a thread has been modified AND deleted since the oldState, the server SHOULD just return the id in the removed response, but MAY return it in the changed response as well. If a thread has been created AND deleted since the oldState, the server SHOULD remove the thread id from the response entirely, but MAY include it in the removed response.

The following errors may be returned instead of the threadUpdates response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

cannotCalculateChanges: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Thread cache.

5. Messages

Just like in IMAP, a message is immutable except for the boolean isXXX status properties and the set of mailboxes it is in. This allows for more efficient caching of messages, and gives easier backwards compatibility for servers implementing an IMAP interface to the same data.

JMAP completely hides the complexities of MIME. All special encodings of either headers or the body, such as base64, or RFC 2047 encoding of non-ASCII characters, MUST be fully decoded into standard UTF-8.

A Message object has the following properties:

An Emailer object has the following properties:

Group information and comments from the RFC 5322 header MUST be discarded when converting into an Emailer object.

Example array of Emailer objects:

[
    {name:"Joe Bloggs", email:"joeb@example.com"},
    {name:"", email:"john@example.com"},
    {name:"John Smith", email: "john@"}
]

An Attachment object has the following properties:

5.1. getMessages

Messages can only be fetched explicitly by id. To fetch messages, make a call to getMessages. It takes the following arguments:

The id property is always returned, regardless of whether it is in the list of requested properties. The possible values for properties can be found above in the description of the Message object. In addition to this, the client may request the following special values:

The response to getMessages is called messages. It has the following arguments:

The following errors may be returned instead of the messages response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

requestTooLarge: Returned if the number of ids requested by the client exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

Example request:

["getMessages", {
  "ids": [ "f123u456", "f123u457" ],
  "properties": [ "threadId", "mailboxIds", "from", "subject", "date" ]
}, "#1"]

and response:

["messages", {
  "state": "41234123231",
  "list": [
    {
      messageId: "f123u457",
      threadId: "ef1314a",
      mailboxIds: [ "f123" ],
      from: [{name: "Joe Bloggs", email: "joe@bloggs.com"}],
      subject: "Dinner on Thursday?",
      date: "2013-10-13T14:12:00Z"
    }
  ],
  notFound: [ "f123u456" ]
}, "#1"]

5.2. getMessageUpdates

If a call to getMessages returns with a different state string in the response to a previous call, the state of the messages has changed on the server. For example, a new message may have been delivered, or an existing message may have changed mailboxes.

The getMessageUpdates call allows a client to efficiently update the state of any cached messages to match the new state on the server. It takes the following arguments:

The response to getMessageUpdates is called messageUpdates. It has the following arguments:

If a maxChanges is supplied, or set automatically by the server, the server MUST ensure the number of ids returned across changed and removed does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call getMessageUpdates until it is fully up to date. If it is unable to calculat an intermediate state, it MUST return a cannotCalculateChanges error response instead.

If a message has been modified AND deleted since the oldState, the server SHOULD just return the id in the removed response, but MAY return it in the changed response as well. If a message has been created AND deleted since the oldState, the server SHOULD remove the message id from the response entirely, but MAY include it in the removed response, and (if in the removed response) MAY included it in the changed response as well.

The following errors may be returned instead of the messageUpdates response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

cannotCalculateChanges: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Message cache.

5.3. setMessages

The setMessages method encompasses:

It takes the following arguments:

Each create, update or destroy is considered an atomic unit. It is permissible for the server to commit some of the changes but not others, however it is not permissible to only commit part of an update to a single record (e.g. update the isFlagged field but not the mailboxIds field, if both are supplied in the update object for a message).

If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method.

If an id given cannot be found, the update or destroy MUST be rejected with a notFound set error.

5.3.1. Saving a draft

Creating messages via the setMessages method is only for creating draft messages and sending them. For delivering/importing a complete [RFC5322] message, use the importMessages method.

The properties of the Message object submitted for creation MUST conform to the following conditions:

If any of the files specified in attachments cannot be found, the creation MUST be rejected with an invalidProperties error. An extra property SHOULD be included in the error object called attachmentsNotFound, of type String[], which SHOULD be an array of the blobId of every attachment that could not be found on the server. - attachedMessages: This MUST NOT be included.

All optional properties default to null unless otherwise stated. Where included, properties MUST conform to the type given in the Message object definition.

If any of the properties are invalid, the server MUST reject the create with an invalidProperties error. The Error object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object MAY also contain a description property of type String with a user-friendly description of the problems.

Other than making sure it conforms to the correct type, the server MUST NOT attempt to validate from/to/cc/bcc when saved as a draft. This is to ensure messages can be saved at any point. Validation occurs when the user tries to send a message.

If a draft cannot be saved due to the user reaching their maximum mail storage quota, the creation MUST be rejected with a maxQuotaReached error.

5.3.2. Updating messages

Messages are mainly immutable, so to update a draft the client must create a new message and delete the old one. This ensures that if the draft is also being edited elsewhere, the two will split into two different drafts to avoid data loss.

Only the following properties may be modified:

Note, a mailbox id may be a creation id (see setFoos for a description of how this works).

If any of the properties in the update are invalid (immutable and different to the current server value, wrong type, invalid value for the property – like a mailbox id for non-existent mailbox), the server MUST reject the update with an invalidProperties error. The Error object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object MAY also contain a description property of type String with a user-friendly description of the problems.

If the id given does not correspond to a Message in the given account, reject the update with a notFound error.

To delete a message to trash, simply change the mailboxIds property so it is now in the mailbox with role == "trash". If the mailbox has the property mustBeOnlyMailbox == true, it must be removed from all other mailboxes. Otherwise, leave it in those mailboxes so that it will be restored to its previous state if undeleted.

5.3.3. Sending messages

To send a message, either create a new message directly into the mailbox with role == "outbox" or move an existing draft into this mailbox. At this point the server will check that it has everything it needs for a valid message. In particular, that it has a valid "From" address (and the user has permission to use this From address), it has at least one address to send to, and all addresses in To/Cc/Bcc are valid email addresses. If it cannot send, it will reject the creation/update with an invalidProperties error. The Error object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object SHOULD also contain a description property of type String with a user-friendly description of the problems to present to the user.

If the message is accepted, the server SHOULD asynchronously schedule the message to be sent after this method call is complete (note, this MAY occur before the next method in the same API request or after the whole API request is complete). This means that the newState string in the response represents a state where the message is still in the outbox.

When the message is sent, the server MUST delete the message from the outbox and SHOULD create a new copy of the sent message (with a new id) in the sent mailbox, unless the user has indicated another preference. If inReplyToMessageId was set, the server SHOULD mark this message as isAnswered: true at this point, if found. The server is responsible for either reporting an error (normally a "bounce" email), or ensuring delivery of the message to the next hop.

5.3.4. Cancelling a send

A message may be moved out of the outbox and back to the drafts mailbox using the standard update message mechanism, if it has not yet been sent at the time the method is called. This MUST cancel the queued send. If the message has already been sent then it will have been deleted from the outbox, so the update will fail with a standard notFound error.

5.3.5. Destroying messages

If the id given does not correspond to a Message in the given account, the server MUST reject the destruction with a notFound error.

Destroying a message removes it from all mailboxes to which it belonged.

5.3.6. Response

The response to setMessages is called messagesSet. It has the following arguments:

The following errors may be returned instead of the messagesSet response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

accountReadOnly: Returned if the account has isReadOnly == true.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

stateMismatch: Returned if an ifInState argument was supplied and it does not match the current state.

5.4. importMessages

The importMessages method adds [RFC5322] messages to a user's set of messages. The messages must first be uploaded as a file using the standard upload mechanism. It takes the following arguments:

An MessageImport object has the following properties:

If isDraft == true, the mailboxes MUST include the drafts or outbox mailbox. Adding to the outbox will send the message, as described in the setMessages section (it will NOT automatically mark any other message as isAnswered).

The response to importMessages is called messagesImported. It has the following arguments:

The following errors may be returned instead of the messageImported response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

accountReadOnly: Returned if the account has isReadOnly == true.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

notFound: Returned if the URL given in the file argument does not correspond to an internal file.

invalidMailboxes: Returned if one of the mailbox ids cannot be found, or an invalid combination of mailbox ids is specified.

maxQuotaReached: Returned if the user has reached their mail quota so the message cannot be imported.

5.5. copyMessages

The only way to move messages between two different accounts is to copy them using the copyMessages method, then once the copy has succeeded, delete the original. It takes the following arguments:

A MessageCopy object has the following properties:

The "from" account may be the same as the "to" account to copy messages within an account.

The response to copyMessages is called messagesCopied. It has the following arguments:

The SetError may be one of the following types:

notFound: Returned if the messageId given can't be found.

invalidMailboxes: Returned if one of the mailbox ids cannot be found, or an invalid combination of mailbox ids is specified.

maxQuotaReached: Returned if the user has reached their mail quota so the message cannot be copied.

The following errors may be returned instead of the messagesCopied response:

fromAccountNotFound: Returned if a fromAccountId was explicitly included with the request, but it does not correspond to a valid account.

toAccountNotFound: Returned if a toAccountId was explicitly included with the request, but it does not correspond to a valid account.

fromAccountNoMail: Returned if the fromAccountId given corresponds to a valid account, but does not contain any mail data.

toAccountNoMail: Returned if the toAccountId given corresponds to a valid account, but does not contain any mail data.

accountReadOnly: Returned if the "to" account has isReadOnly == true.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

5.6. reportMessages

Messages can be reported as spam or non-spam to help train the user's spam filter. This MUST NOT affect the state of the Message objects (it DOES NOT move a message into or out of the Spam mailbox).

To report messages, make a call to reportMessages. It takes the following arguments:

The response to reportMessages is called messagesReported. It has the following arguments:

The following errors may be returned instead of the messagesReported response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

accountReadOnly: Returned if the account has isReadOnly == true.

requestTooLarge: Returned if the total number of objects to create, update or destroy exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

6. Identities

A Identity object stores information about an email address (or domain) the user may send from. It has the following properties:

Multiple identities with the same email address MAY exist, to allow for different settings the user wants to pick between (for example with different names/signatures).

6.1. getIdentities

Identities can either be fetched explicitly by id, or all of them at once. To fetch identities, make a call to getIdentities. It takes the following arguments:

The response to getIdentities is called identities. It has the following arguments:

The following errors may be returned instead of the identities response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

6.2. getIdentityUpdates

The getIdentityUpdates call allows a client to efficiently update the state of its cached identities to match the new state on the server. It takes the following arguments:

The response to getIdentityUpdates is called identityUpdates. It has the following arguments:

If a maxChanges is supplied, or set automatically by the server, the server must try to limit the number of ids across changed and removed to the number given. If there are more changes than this between the client's state and the current server state, the update returned MUST take the client to an intermediate state, from which the client can continue to call getIdentityUpdates until it is fully up to date. The server MAY return more ids than the maxChanges total if this is required for it to be able to produce an update to an intermediate state, but it SHOULD try to keep it close to the maximum requested.

If an identity has been modified AND deleted since the oldState, the server should just return the id in the removed array, but MAY return it in the changed array as well. If an identity has been created AND deleted since the oldState, the server SHOULD remove the identity id from the response entirely, but MAY include it in the removed array.

The following errors may be returned instead of the identityUpdates response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

cannotCalculateChanges: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Identity cache.

6.3. setIdentities

Modifying the state of Identity objects on the server is done via the setIdentities method. This encompasses creating, updating and destroying Identity records.

The setIdentities method takes the following arguments:

Each create, update or destroy is considered an atomic unit. It is permissible for the server to commit some of the changes but not others, however it is not permissible to only commit part of an update to a single identity.

If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method.

A create MAY be rejected with one of the following errors:

If the identity has mayDeleteIdentity == false, any attempt to destroy it MUST be rejected with a forbidden error.

If an id given cannot be found, the update or destroy MUST be rejected with a notFound set error.

The response to setIdentities is called identitiesSet. It has the following arguments:

A SetError object has the following properties:

The following errors may be returned instead of the identitiesSet response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

accountReadOnly: Returned if the account has MailCapabilities with isReadOnly == true.

requestTooLarge: Returned if the total number of objects to create, update or destroy exceeds the maximum number the server is willing to process in a single method call.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

stateMismatch: Returned if an ifInState argument was supplied and it does not match the current state.

7. SearchSnippets

When doing a search on a String property, the client may wish to show the relevant section of the body that matches the search as a preview instead of the beginning of the message, and to highlight any matching terms in both this and the subject of the message. Search snippets represent this data.

A SearchSnippet object has the following properties:

It is server-defined what is a relevant section of the body for preview. If the server is unable to determine search snippets, it MUST return null for both the subject and preview properties.

Note, unlike most data types, a SearchSnippet DOES NOT have a property called id.

7.1. getSearchSnippets

To fetch search snippets, make a call to getSearchSnippets. It takes the following arguments:

The response to getSearchSnippets is called searchSnippets. It has the following arguments:

Since snippets are only based on immutable properties, there is no state string or update mechanism needed.

The following errors may be returned instead of the searchSnippets response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

requestTooLarge: Returned if the number of messageIds requested by the client exceeds the maximum number the server is willing to process in a single method call.

cannotDoFilter: Returned if the server is unable to process the given filter for any reason.

invalidArguments: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A description property MAY be present on the response object to help debug with an explanation of what the problem was.

8. Vacation Response

The VacationResponse object represents the state of vacation-response related settings for an account. It has the following properties:

8.1. getVacationResponse

There MUST only be exactly one VacationResponse object in an account. It MUST have the id "singleton".

To fetch the vacation response object, make a call to getVacationResponse. It takes the following argument:

The response to getVacationResponse is called vacationResponse. It has the following arguments:

The following errors may be returned instead of the vacationResponse response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

8.2. setVacationResponse

Sets properties on the vacation response object. It takes the following arguments:

If any of the properties in the update are invalid (immutable and different to the current server value, wrong type), the server MUST reject the update with a SetError of type invalidProperties. The SetError object SHOULD contain a property called properties of type String[] that lists all the properties that were invalid. The object MAY also contain a description property of type String with a user-friendly description of the problems.

The response is called vacationResponseSet. It has the following arguments:

A SetError object has the following properties:

The following errors may be returned instead of the vacationResponseSet response:

accountNotFound: Returned if an accountId was explicitly included with the request, but it does not correspond to a valid account.

accountNotSupportedByMethod: Returned if the accountId given corresponds to a valid account, but the account does not support this data type.

invalidArguments: Returned if one of the arguments is of the wrong type, or otherwise invalid (including using an id other than "singleton"). A description property MAY be present on the response object to help debug with an explanation of what the problem was.

9. Normative References

[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997.
[RFC5322] Resnick, P., "Internet Message Format", RFC 5322, DOI 10.17487/RFC5322, October 2008.
[RFC7159] Bray, T., "The JavaScript Object Notation (JSON) Data Interchange Format", RFC 7159, DOI 10.17487/RFC7159, March 2014.

Author's Address

Neil Jenkins FastMail Level 1, 91 William St Melbourne, VIC 3000 Australia EMail: neilj@fastmail.com URI: https://www.fastmail.com