Added: james/project/trunk/server/protocols/jmap/doc/spec/mailbox.mdwn URL: http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/doc/spec/mailbox.mdwn?rev=1726757&view=auto ============================================================================== --- james/project/trunk/server/protocols/jmap/doc/spec/mailbox.mdwn (added) +++ james/project/trunk/server/protocols/jmap/doc/spec/mailbox.mdwn Tue Jan 26 10:04:59 2016 @@ -0,0 +1,270 @@ +## 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. + +A **Mailbox** object has the following properties: + +- **id**: `String` + The id of the mailbox. This property is immutable. +- **name**: `String` + User-visible name for the mailbox, e.g. "Inbox". This may be any UTF-8 string of at least 1 character in length and maximum 256 bytes in size. Mailboxes MAY have the same name as a sibling Mailbox. +- **parentId**: `String|null` + The mailbox id for the parent of this mailbox, or `null` if this mailbox is at the top level. Mailboxes form acyclic graphs (forests) directed by the child-to-parent relationship. There MUST NOT be a loop. +- **role**: `String|null` + Identifies system mailboxes. This property is immutable. + + The following values SHOULD be used for the relevant mailboxes: + + - `inbox` â the mailbox to which new mail is delivered by default, unless diverted by a rule or spam filter etc. + - `archive` â messages the user does not need right now, but does not wish to delete. + - `drafts` â messages the user is currently writing and are not yet sent. + - `outbox` â messages the user has finished writing and wishes to send (see the `setMessages` method description for more information). A mailbox with this role MUST be present if the user is allowed to send mail through an account. If not present, the user MAY NOT send mail with that account. + - `sent` â messages the user has sent. + - `trash` â messages the user has deleted. + - `spam` â messages considered spam by the server. + - `templates` â drafts which should be used as templates (i.e. used as the basis for creating new drafts). + + No two mailboxes may have the same role. Mailboxes without a known purpose MUST have a role of `null`. + + An account is not required to have mailboxes with any of the above roles. A client MAY create new mailboxes with a role property to help them keep track of a use-case not covered by the above list. To avoid potential conflict with any special behaviour a server might apply to mailboxes with certain roles in the future, any roles not in the above list created by the client must begin with `"x-"`. The client MAY attempt to create mailboxes with the standard roles if not already present, but the server MAY reject these. +- **sortOrder**: `Number` + Defines the sort order of mailboxes when presented in the UI, so it is + consistent between devices. The number MUST be an integer in the range + 0 <= sortOrder < 2^31. + + A mailbox with a lower order should be displayed before a mailbox with + a higher order (but the same parent) in any mailbox listing in the + client's UI. Mailboxes with equal order should be sorted in + alphabetical order by name. The sorting should take into locale-specific + character order convention. +- **mustBeOnlyMailbox**: `Boolean` + If true, messages in this mailbox may not also be in any other mailbox. +- **mayReadItems**: `Boolean` + If true, may use this mailbox as part of a filter in a *getMessageList* call. + If a submailbox is shared but not the parent mailbox, this may be `false`. +- **mayAddItems**: `Boolean` + The user may add messages to this mailbox (by either creating a new message or modifying an existing one). +- **mayRemoveItems**: `Boolean` + The user may remove messages from this mailbox (by either changing the mailboxes of a message or deleting it). +- **mayCreateChild**: `Boolean` + The user may create a mailbox with this mailbox as its parent. +- **mayRename**: `Boolean` + The user may rename the mailbox or make it a child of another mailbox. +- **mayDelete**: `Boolean` + The user may delete the mailbox itself. +- **totalMessages**: `Number` + The number of messages in this mailbox. +- **unreadMessages**: `Number` + The number of messages in this mailbox where the *isUnread* property of the message is `true` and the *isDraft* property is `false`. +- **totalThreads**: `Number` + The number of threads where at least one message in the thread is in this mailbox (but see below for special case handling of Trash). +- **unreadThreads**: `Number` + The number of threads where at least one message in the thread has `isUnread == true` and `isDraft == false` AND at least one message in the thread is in this mailbox (but see below for special case handling of Trash). Note, the unread message does not need to be the one in this mailbox. + +The Trash mailbox (that is a mailbox with `role == "trash"`) MUST be treated specially: + +* Messages in the Trash are ignored when calculating the `unreadThreads` and `totalThreads` count of other mailboxes. +* Messages not in the Trash are ignored when calculating the `unreadThreads` and `totalThreads` count for the Trash folder. + +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. + +### 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: + +- **accountId**: `String|null` + The Account to fetch the mailboxes for. If `null`, the primary account is used. +- **ids**: `String[]|null` + The ids of the mailboxes to fetch. If `null`, all mailboxes in the account are returned. +- **properties**: `String[]|null` + The properties of each mailbox to fetch. If `null`, all properties are returned. The id of the mailbox will **always** be returned, even if not explicitly requested. + +The response to *getMailboxes* is called *mailboxes*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **state**: `String` + A string representing the state on the server for **all** mailboxes. If the state of a mailbox changes, or a new mailbox is created, or a mailbox is destroyed, this string will change. It is used to get delta updates. +- **list**: `Mailbox[]` + An array of the Mailbox objects requested. This will be the **empty array** if the *ids* argument was the empty array, or contained only ids for mailboxes that could not be found. +- **notFound**: `String[]|null` + This array contains the ids passed to the method for mailboxes that do not exist, or `null` if all requested ids were found. It will always be `null` if the *ids* argument in the call was `null`. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. + +### 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: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, the primary account will be used. +- **sinceState**: `String` + The current state of the client. This is the string that was returned as the *state* argument in the *mailboxes* response. The server will return the changes made since this state. +- **fetchRecords**: `Boolean|null` + If `true`, after outputting a *mailboxUpdates* response, an implicit call will be made to *getMailboxes* with the *changed* property of the response as the *ids* argument, and the *fetchRecordProperties* argument as the *properties* argument. If `false` or `null`, no implicit call will be made. +- **fetchRecordProperties**: `String[]|null` + If `null`, all Mailbox properties will be fetched unless *onlyCountsChanged* in the *mailboxUpdates* response is `true`, in which case only the 4 counts properties will be returned (*totalMessages*, *unreadMessages*, *totalThreads* and *unreadThreads*). If not `null`, this value will be passed through to the *getMailboxes* call regardless of the *onlyCountsChanged* value in the *mailboxUpdates* response. + +The response to *getMailboxUpdates* is called *mailboxUpdates*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **oldState**: `String` + This is the *sinceState* argument echoed back; the state from which the server is returning changes. +- **newState**: `String` + This is the state the client will be in after applying the set of changes to the old state. +- **changed**: `String[]` + An array of Mailbox ids where a property of the mailbox has changed between the old state and the new state, or the mailbox has been created, and the mailbox has not been destroyed. +- **removed**: `String[]` + An array of Mailbox ids for mailboxes which have been destroyed since the old state. +- **onlyCountsChanged**: `Boolean` + Indicates that only the folder counts (unread/total messages/threads) have changed since the old state. The client can then use this to optimise its data transfer and only fetch the counts. If the server is unable to tell if only counts have changed, it should just always return `false`. + +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, and optionally the *changed* array as well. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. The error object MUST also include a `newState: String` property with the current state for the type. + +### setMailboxes + +Mailboxes can be created, updated and destroyed using the *setMailboxes* method. The method takes the following arguments: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, defaults to the primary account. +- **ifInState**: `String|null` + This is a state string as returned by the *getMailboxes* method. If supplied, the string must match the current state, otherwise the method will be aborted and a `stateMismatch` error returned. +- **create**: `String[Mailbox]|null` + A map of *creation id* (an arbitrary string set by the client) to Mailbox objects. If `null`, no objects will be created. +- **update**: `String[Mailbox]|null` + A map of id to a an object containing the properties to update for that Mailbox. If `null`, no objects will be updated. +- **destroy**: `String[]|null` + A list of ids for Mailboxes to permanently delete. If `null`, no objects will be deleted. + +Each create, update or destroy is considered an atomic unit. The server MAY commit some of the changes but not others, however MAY 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). + +If a create, update or destroy is rejected, the appropriate error should 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. + +#### Creating mailboxes + +The properties of the Mailbox object submitted for creation MUST conform to the following conditions: + +- The *id* property MUST NOT be present. +- The *parentId* property MUST be either `null` or be a valid id for a mailbox for which the `mayCreateChild` property is `true`. +- The *role* property MUST be either `null`, a valid role as listed in the Mailbox object specification, or prefixed by `"x-"`. +- The *mustBeOnlyMailbox* property MUST NOT be present. This is server dependent and will be set by the server. +- The *mayXXX* properties MUST NOT be present. Restrictions may only be set by the server for system mailboxes, or when sharing mailboxes with other users (setting sharing is not defined yet in this spec). +- The *totalMessages*, *unreadMessages*, *totalThreads* and *unreadThreads* properties MUST NOT be present. + +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. + +#### 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: + +- The *name* property MUST be valid UTF-8, between 1 and 256 bytes in size. +- The *parentId* property MUST be either `null` or be a valid id for *another* mailbox that is **not a descendant** of this mailbox, and for which the `mayCreateChild` property is `true`. +- These properties are immutable or may only be set by the server: + - id + - role + - mustBeOnlyMailbox + - mayReadMessageList + - mayAddMessages + - mayRemoveMessages + - mayCreateChild + - mayRenameMailbox + - mayDeleteMailbox + - totalMessages + - unreadMessages + - totalThreads + - unreadThreads + +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. + +#### 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. + +#### Ordering of changes + +Each individual create, update or destroy MUST take the server from one valid state to another valid state, so the changes can be processed linearly without need to undo or backtrack. However the order of the changes may affect the validity of the change. The server MUST process the changes in a manner which is indistinguishable from an order following these rules: + +1. Creates comes before any other changes. +2. Creates with a parentId that is not a *creation id* reference come before those that reference another newly created mailbox. +3. Creation of a mailbox X comes before creation of a mailbox Y if Y will be a descendent of X. +4. Updates come before destroying mailboxes. +5. Update/Destroy X comes before update/destroy Y if X is a descendent of Y. + +#### Response + +The response to *setMailboxes* is called *mailboxesSet*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **oldState**: `String|null` + The state string that would have been returned by `getMailboxes` before making the requested changes, or `null` if the server doesn't know what the previous state string was. +- **newState**: `String` + The state string that will now be returned by `getMailboxes`. +- **created**: `String[Mailbox]` + A map of the creation id to an object containing the **id** and **mustBeOnlyMailbox** properties for each successfully created Mailbox. +- **updated**: `String[]` + A list of ids for Mailboxes that were successfully updated. +- **destroyed**: `String[]` + A list of ids for Mailboxes that were successfully destroyed. +- **notCreated**: `String[SetError]` + A map of creation id to a SetError object for each Mailbox that failed to be created. The possible errors are defined above. +- **notUpdated**: `String[SetError]` + A map of Mailbox id to a SetError object for each Mailbox that failed to be updated. The possible errors are defined above. +- **notDestroyed**: `String[SetError]` + A map of Mailbox id to a SetError object for each Mailbox that failed to be destroyed. The possible errors are defined above. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. + +Example request: + + [ "setMailboxes", { + "ifInState": "ms4123", + "update": { + "f3": { + "name": "The new name" + } + }, + "destroy": [ "f5" ] + }, "#0" ] +
Added: james/project/trunk/server/protocols/jmap/doc/spec/message.mdwn URL: http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/doc/spec/message.mdwn?rev=1726757&view=auto ============================================================================== --- james/project/trunk/server/protocols/jmap/doc/spec/message.mdwn (added) +++ james/project/trunk/server/protocols/jmap/doc/spec/message.mdwn Tue Jan 26 10:04:59 2016 @@ -0,0 +1,494 @@ +## 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](https://tools.ietf.org/html/rfc4648), or [RFC2047](http://tools.ietf.org/html/rfc2047) encoding of non-ASCII characters, MUST be fully decoded into standard UTF-8. + +A **Message** object has the following properties: + +- **id**: `String` + The id of the message. +- **blobId**: `String` + The id representing the raw RFC2822 message. This may be used to download + the original message or to attach it directly to another message etc. +- **threadId**: `String` + The id of the thread to which this message belongs. +- **mailboxIds**: `String[]` (Mutable) + The ids of the mailboxes the message is in. A message MUST belong to one or more mailboxes at all times (until it is deleted). +- **inReplyToMessageId**: `String|null` + The id of the Message this message is a reply to. This is primarily for drafts, but the server MAY support this for received messages as well by looking up the RFC2822 Message-Id referenced in the `In-Reply-To` header and searching for this message in the user's mail. +- **isUnread**: `Boolean` (Mutable) + Has the message not yet been read? This corresponds to the **opposite** of the `\Seen` system flag in IMAP. +- **isFlagged**: `Boolean` (Mutable) + Is the message flagged? This corresponds to the `\Flagged` system flag in IMAP. +- **isAnswered**: `Boolean` (Mutable) + Has the message been replied to? This corresponds to the `\Answered` system flag in IMAP. +- **isDraft**: `Boolean` (Mutable by the server only) + Is the message a draft? This corresponds to the `\Draft` system flag in IMAP. +- **hasAttachment**: `Boolean` + Does the message have any attachments? +- **headers**: `String[String]` + A map of header name to (decoded) header value for all headers in the message. For headers that occur multiple times (e.g. `Received`), the values are concatenated with a single new line (`\n`) character in between each one. +- **from**: `Emailer|null` + An Emailer object (see below) containing the name/email from the parsed `From` header of the email. If the email doesn't have a `From` header, this is `null`. +- **to**: `Emailer[]|null` + An array of name/email objects (see below) representing the parsed `To` header of the email, in the same order as they appear in the header. If the email doesn't have a `To` header, this is `null`. If the header exists but does not have any content, the response is an array of zero length. +- **cc**: `Emailer[]|null` + An array of name/email objects (see below) representing the parsed `Cc` header of the email, in the same order as they appear in the header. If the email doesn't have a `Cc` header, this is `null`. If the header exists but does not have any content, the response is an array of zero length. +- **bcc**: `Emailer[]|null` + An array of name/email objects (see below) representing the parsed `Bcc` header of the email. If the email doesn't have a `Bcc` header (which will be true for most emails outside of the Sent mailbox), this is `null`. If the header exists but does not have any content, the response is an array of zero length. +- **replyTo**: `Emailer|null` + An Emailer object (see below) containing the name/email from the parsed `Reply-To` header of the email. If the email doesn't have a `Reply-To` header, this is `null`. +- **subject**: `String` + The subject of the message. +- **date**: `Date` + The date the message was sent (or saved, if the message is a draft). +- **size**: `Number` + The size in bytes of the whole message as counted by the server towards the user's quota. +- **preview**: `String` + Up to 256 characters of the beginning of a plain text version of the message body. This is intended to be shown as a preview line on a mailbox listing, and the server may choose to skip quoted sections or salutations to return a more useful preview. +- **textBody**: `String|null` + The plain text body part for the message. If there is only an HTML version of the body, a plain text version will be generated from this. +- **htmlBody**: `String|null` + The HTML body part for the message if present. If there is only a plain text version of the body, an HTML version will be generated from this. Any scripting content, or references to external plugins, MUST be stripped from the HTML by the server. +- **attachments**: `Attachment[]|null` + An array of attachment objects (see below) detailing all the attachments to the message. +- **attachedMessages**: `String[Message]|null` + An object mapping attachment id (as found in the `attachments` property) to a **Message** object with the following properties, for each RFC2822 message attached to this one: + - headers + - from + - to + - cc + - bcc + - replyTo + - subject + - date + - textBody + - htmlBody + - attachments + - attachedMessages + +An **Emailer** object has the following properties: + +- **name**: `String` + The name of the sender/recipient. If a name cannot be extracted for an email, this property should be the empty string. +- **email**: `String` + The email address of the sender/recipient. This MUST be of the form `"<mailbox>@<host>"` If a `host` or even `mailbox` cannot be extracted for an email, the empty string should be used for this part (so the result will always still contain an `"@"`). + +Example Emailer object: + + [ + {name:"Joe Bloggs", email:"j...@example.com"}, + {name:"", email:"j...@example.com"}, + {name:"John Smith", email: "john@"} + ] + +An **Attachment** object has the following properties: + +- **blobId**: `String` + The id of the binary data. +- **type**: `String` + The content-type of the attachment. +- **name**: `String` + The full file name, e.g. "myworddocument.doc" +- **size**: `Number` + The size, in bytes, of the attachment when fully decoded (i.e. the number of bytes in the file the user would download). +- **cid**: `String|null` + The id used within the message body to reference this attachment. This is only unique when paired with the message id, and has no meaning without reference to that. +- **isInline**: `Boolean` + True if the attachment is referenced by a `cid:` link from within the HTML body of the message. +- **width**: `Number|null` + The width (in px) of the image, if the attachment is an image. +- **height**: `Number|null` + The height (in px) of the image, if the attachment is an image. + +### getMessages + +Messages can only be fetched explicitly by id. To fetch messages, make a call to `getMessages`. It takes the following arguments: + +- **accountId**: `String|null` + The id of the account to use for this call. If not given, defaults to the primary account. +- **ids**: `String[]` + An array of ids for the messages to fetch. +- **properties**: `String[]|null` + A list of properties to fetch for each message. If `null`, all properties will be fetched. + +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: + +- **body**: If `"body"` is included in the list of requested properties, it will be interpreted by the server as a request for `"htmlBody"` if the message has an HTML part, or `"textBody"` otherwise. +- **headers.property**: Instead of requesting all the headers (by requesting the `"headers"` property, the client may specify the particular headers it wants using the `headers.property-name` syntax, e.g. `"headers.X-Spam-Score", "headers.X-Spam-Hits"`). The server will return a *headers* property but with just the requested headers in the object rather than all headers. If `"headers"` is requested, the server MUST ignore the individual header requests and just return all headers. If a requested header is not present in the message, it MUST not be present in the *headers* object. Header names are case-insensitive. + +The response to *getMessages* is called *messages*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **state**: `String` + A string encoding the current state on the server. This string will change + if any messages change (that is, a new message arrives, a change is made to one of the mutable properties, or a message is deleted). It can be passed to *getMessageUpdates* to efficiently get the list of changes from the previous state. +- **list**: `Message[]` + An array of Message objects for the requested message ids. This may not be in the same order as the ids were in the request. +- **notFound**: `String[]|null` + An array of message ids requested which could not be found, or `null` if all + ids were found. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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: "j...@bloggs.com"}], + subject: "Dinner on Thursday?", + date: "2013-10-13T14:12:00Z" + } + ], + notFound: [ "f123u456" ] + }, "#1"] + + +### 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: + +- **accountId**: `String|null` + The id of the account to use for this call. If not given, defaults to the primary account. +- **sinceState**: `String` + The current state of the client. This is the string that was returned as the *state* argument in the *messages* response. The server will return the changes made since this state. +- **maxChanges**: `Number|null` + The maximum number of changed messages to return in the response. The server MAY choose to clamp this value to a particular maximum or set a maximum if none is given by the client. If supplied by the client, the value MUST be a positive integer greater than 0. If a value outside of this range is given, the server MUST reject the call with an `invalidArguments` error. +- **fetchRecords**: `Boolean|null` + If true, after outputting a *messageUpdates* response, an implicit call will be made to *getMessages* with a list of all message ids in the *changed* argument of the response as the *ids* argument, and the *fetchRecordProperties* argument as the *properties* argument. +- **fetchRecordProperties**: `String[]|null` + The list of properties to fetch on any fetched messages. See *getMessages* for a full description. + +The response to *getMessageUpdates* is called *messageUpdates*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **oldState**: `String` + This is the *sinceState* argument echoed back; the state from which the server is returning changes. +- **newState**: `String` + This is the state the client will be in after applying the set of changes to the old state. +- **hasMoreUpdates**: `Boolean` + If `true`, the client may call *getMessageUpdates* again with the *newState* returned to get further updates. If `false`, *newState* is the current server state. +- **changed**: `String[]` + An array of message ids for messages that have either been created or had their state change, and are not currently deleted. +- **removed**: `String[]` + An array of message ids for messages that have been deleted since the oldState. + +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 *getMessageUpdates* 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 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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. The error object MUST also include a `newState: String` property with the current state for the type. + +### setMessages + +The *setMessages* method encompasses: + +- Creating a draft message +- Sending a message +- Changing the flags of a message (unread/flagged status) +- Adding/removing a message to/from mailboxes (moving a message) +- Deleting messages + +It takes the following arguments: + +- **accountId**: `String|null` + The id of the account to use for this call. If not given, defaults to the primary account. +- **ifInState**: `String|null` + This is a state string as returned by the *getMessages* method. If supplied, the string must match the current state, otherwise the method will be aborted and a `stateMismatch` error returned. +- **create**: `String[Message]|null` + A map of *creation id* (an arbitrary string set by the client) to Message objects (see below for a detailed description). +- **update**: `String[Message]|null` + A map of id to a an object containing the properties to update for that Message. +- **destroy**: `String[]|null` + A list of ids for Message objects to permanently delete. + +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 should 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. + +#### Saving a draft + +Creating messages via the *setMessages* method is only for creating draft messages and sending them. For delivering/importing a complete RFC2822 message, use the `importMessages` method. + +The properties of the Message object submitted for creation MUST conform to the following conditions: + +- **id**: This property MUST NOT be included. It is set by the server upon creation. +- **blobId**: This property MUST NOT be included. It is set by the server upon creation. +- **threadId**: This property MUST NOT be included. It is set by the server upon creation. +- **mailboxIds**: This property MUST be included. The value MUST include the id of either the mailbox with `role == "drafts"` (to save a draft) or the mailbox with `role == "outbox"` (to send the message). If this mailbox does not have `mustBeOnlyMailbox == true`, others may be included too. +- **inReplyToMessageId**: Optional. If included, the server will look up this message and if found set appropriate `References` and `In-Reply-To` headers. These will override any such headers supplied in the *headers* property. If not found, the creation MUST be rejected with an `inReplyToNotFound` error. +- **isUnread**: Optional, defaults to `false`. If included this MUST be `false`. +- **isFlagged**: Optional, defaults to `false`. +- **isAnswered**: Optional, defaults to `false`. If included this MUST be `false`. +- **isDraft**: Optional, defaults to `true`. If included this MUST be `true`. +- **hasAttachment**: This property MUST NOT be included. It is set by the server upon creation based on the attachments property. +- **headers**: Optional. The keys MUST only contain the characters A-Z, a-z, 0-9 and hyphens. +- **from**: Optional. Overrides a "From" in the *headers*. +- **to**: Optional. Overrides a "To" in the *headers*. +- **cc**: Optional. Overrides a "Cc" in the *headers*. +- **bcc**: Optional. Overrides a "Bcc" in the *headers*. +- **replyTo**: Optional. Overrides a "Reply-To" in the *headers*. +- **subject**: Optional. Defaults to the empty string (`""`). +- **date**: Optional. If included, the server SHOULD wait until this time to send the message (once moved to the outbox folder). Until it is sent, the send may be cancelled by moving the message back out of the outbox folder. If the date is in the past, the message must be sent immediately. A client may find out if the server supports delayed sending by querying the capabilities property of the Account object. +- **size**: This MUST NOT be included. It is set by the server upon creation. +- **preview**: This MUST NOT be included. It is set by the server upon creation. +- **textBody**: Optional. If not supplied and an htmlBody is, the server SHOULD generate a text version for the message from this. +- **htmlBody**: Optional. If this contains internal links (cid:) the cid value should be the attachment id. +- **attachments**: Optional. An array of Attachment objects detailing all the attachments to the message. To add an attachment, the file must first be uploaded using the standard upload mechanism; this will give the client a URL that may be used to identify the file. The `id` property may be assigned by the client, and is solely used for matching up with `cid:<id>` links inside the `htmlBody`. The server MAY (and probably will) change the ids upon sending. + + If one of the attachments is not 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 ids of any attachments 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. + +#### 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: + +- **mailboxIds**: The server MUST reject any attempt to add a message with `isDraft == false` to the outbox. The server MAY reject attempts to add a draft message to a mailbox that does not have a role of `drafts`, `outbox` or `templates`. +- **isFlagged** +- **isUnread** +- **isAnswered** + +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. + +#### 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, 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. + +#### 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. + +#### 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. + +#### Response + +The response to *setMessages* is called *messagesSet*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **oldState**: `String|null` + The state string that would have been returned by *getMessages* before making the requested changes, or `null` if the server doesn't know what the previous state string was. +- **newState**: `String` + The state string that will now be returned by *getMessages*. +- **created**: `String[Message]` + A map of the creation id to an object containing the *id*, *blobId*, *threadId*, and *size* properties for each successfully created Message. +- **updated**: `String[]` + A list of Message ids for Messages that were successfully updated. +- **destroyed**: `String[]` + A list of Message ids for Messages that were successfully destroyed. +- **notCreated**: `String[SetError]` + A map of creation id to a SetError object for each Message that failed to be created. The possible errors are defined above. +- **notUpdated**: `String[SetError]` + A map of Message id to a SetError object for each Message that failed to be updated. The possible errors are defined above. +- **notDestroyed**: `String[SetError]` + A map of Message id to a SetError object for each Message that failed to be destroyed. The possible errors are defined above. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. + +### importMessages + +The *importMessages* method adds RFC2822 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: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, defaults to the primary account. +- **messages**: `String[MessageImport]` + A map of creation id (client specified) to MessageImport objects + +An **ImportMessage** object has the following properties: + +- **file**: `String` + The URL of the uploaded file (see the file upload section). +- **mailboxIds** `String[]` + The ids of the mailbox(es) to assign this message to. +- **isUnread**: `Boolean` +- **isFlagged**: `Boolean` +- **isAnswered**: `Boolean` +- **isDraft**: `Boolean` + +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: + +- **accountId**: `String` + The id of the account used for this call. +- **created**: `String[Message]` + A map of the creation id to an object containing the *id*, *blobId*, *threadId* and *size* properties for each successfully imported Message. +- **notCreated**: `String[SetError]` + A map of creation id to a SetError object for each Message that failed to be created. The possible errors are defined above. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. + +### 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: + +- **fromAccountId**: `String|null` + The id of the account to copy messages from. If `null`, defaults to the primary account. +- **toAccountId**: `String|null` + The id of the account to copy messages to. If `null`, defaults to the primary account. +- **messages**: `String[MessageCopy]` + A map of *creation id* to a MessageCopy object. + +A **MessageCopy** object has the following properties: + +- **messageId**: `String` + The id of the message to be copied in the "from" account. +- **mailboxIds**: `String[]` + The ids of the mailboxes (in the "to" account) to add the copied message to. +- **isUnread**: `Boolean` + The *isUnread* property for the copy. +- **isFlagged**: `Boolean` + The *isFlagged* property for the copy. +- **isAnswered**: `Boolean` + The *isAnswered* property for the copy. +- **isDraft**: `Boolean` + The *isDraft* property for the copy. + +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: + +- **fromAccountId**: `String` + The id of the account messages were copied from. +- **toAccountId**: `String` + The id of the account messages were copied to. +- **created**: `String[Message]|null` + A map of the creation id to an object containing the *id*, *blobId*, *threadId* and *size* properties for each successfully copied Message. +- **notCreated**: `String[SetError]|null` + A map of creation id to a SetError object for each Message that failed to be copied, `null` if none. + +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. + +### 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: + +- **accountId**: `String|null` + The id of the account to use for this call. If not given, defaults to the primary account. +- **messageIds**: `String[]` + The list of ids of messages to report. +- **asSpam**: `Boolean` + If `true`, learn these messages as spam. If `false`, learn as non-spam. + + +The response to *reportMessages* is called *messagesReported*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for this call. +- **asSpam**: `Boolean` + Echoed back from the call +- **reported**: `String[]` + The ids of each message successfully reported. +- **notFound**: `String` + The ids of each message not found. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. Added: james/project/trunk/server/protocols/jmap/doc/spec/messagelist.mdwn URL: http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/doc/spec/messagelist.mdwn?rev=1726757&view=auto ============================================================================== --- james/project/trunk/server/protocols/jmap/doc/spec/messagelist.mdwn (added) +++ james/project/trunk/server/protocols/jmap/doc/spec/messagelist.mdwn Tue Jan 26 10:04:59 2016 @@ -0,0 +1,272 @@ +## 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. + +### getMessageList + +To fetch a message list, make a call to *getMessageList*. It takes the following arguments: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, the primary account will be used. +- **filter**: `FilterCondition|FilterOperator|null` + Determines the set of messages returned in the results. See the "Filtering" section below for allowed values and semantics. +- **sort**: `String[]|null` + A list of Message property names to sort by. See the "Sorting" section below for allowed values and semantics. +- **collapseThreads**: `Boolean|null` + If true, each thread will only be returned once in the resulting list, at the position of the first message in the list (given the filter and sort order) belonging to the thread. If `false` or `null`, threads may be returned multiple times. +- **position**: `Number|null` + The 0-based index of the first result in the list to return. If a negative value is given, the call MUST be rejected with an `invalidArguments` error. If `null`, 0 is used. +- **anchor**: `String|null` + A Message id. The index of this message id will be used in combination with the `anchorOffset` argument to determine the index of the first result to return (see the "Windowing" section below for more details). +- **anchorOffset**: `Number|null` + The index of the anchor message relative to the index of the first result to return. This MAY be negative. For example, `-1` means the first message after the anchor message should be the first result in the results returned (see the "Windowing" section below for more details). +- **limit**: `Number|null` + The maximum number of results to return. If `null`, no limit is presumed. The server MAY choose to enforce a maximum `limit` argument. In this case, if a greater value is given, the limit should be clamped to the maximum; since the total number of results in the list is returned, the client should not be relying on how many results are returned to determine if it has reached the end of the list. If a negative value is given, the call MUST be rejected with an `invalidArguments` error. +- **fetchThreads**: `Boolean|null` + If `true`, after outputting a *messageList* response, an implicit call will be made to *getThreads* with the *threadIds* array in the response as the *ids* argument, and the *fetchMessages* and *fetchMessageProperties* arguments passed straight through from the call to *getMessageList*. If `false` or `null`, no implicit call will be made. +- **fetchMessages**: `Boolean|null` + If `true` and `fetchThreads == false`, then after outputting a *messageList* response, an implicit call will be made to *getMessages* with the `messageIds` array in the response as the *ids* argument, and the *fetchMessageProperties* argument as the *properties* argument. If `false` or `null`, no implicit call will be made. +- **fetchMessageProperties**: `String[]|null` + The list of properties to fetch on any fetched messages. See *getMessages* for a full description. +- **fetchSearchSnippets**: `Boolean|null` + If `true`, then after outputting a *messageList* and making any other implicit calls, an implicit call will be made to *getSearchSnippets*. The *messageIds* array from the response will be used as the *messageIds* argument, and the *filter* argument will be passed straight through. If `false` or `null`, no implicit call will be made. + +#### Filtering + +A **FilterOperator** object has the following properties: + +- **operator**: `String` + This MUST be one of the following strings: "AND"/"OR"/"NOT": + - **AND**: all of the conditions must match for the filter to match. + - **OR**: at least one of the conditions must match for the filter to match. + - **NOT**: none of the conditions must match for the filter to match. +- **conditions**: `(FilterCondition|FilterOperator)[]` + The conditions to evaluate against each message. + +A **FilterCondition** object has the following properties: + +- **inMailboxes**: `String[]|null` + A list of mailbox ids. A message must be in ALL of these mailboxes to match the condition. +- **notInMailboxes**: `String[]|null` + A list of mailbox ids. A message must NOT be in ANY of these mailboxes to match the condition. +- **before**: `Date|null` + The date of the message (as returned on the Message object) must be before this date to match the condition. +- **after**: `Date|null` + The date of the message (as returned on the Message object) must be on or after this date to match the condition. +- **minSize**: `Number|null` + The size of the message in bytes (as returned on the Message object) must be equal to or greater than this number to match the condition. +- **maxSize**: `Number|null` + The size of the message in bytes (as returned on the Message object) must be less than this number to match the condition. +- **threadIsFlagged**: `Boolean|null` + If `true`, the condition is matched if the `isFlagged` property of *any* message in the same thread as the message being examined is `true`. If `false`, the `isFlagged` property of *every* message in the same thread as the message being examined must be `false` to match the condition. +- **threadIsUnread**: `Boolean|null` + If `true`, the condition is matched if the `isUnread` property of *any* message in the same thread as the message being examined is `true`. If `false`, the `isUnread` property of *every* message in the same thread as the message being examined must be `false` to match the condition. +- **isFlagged**: `Boolean|null` + The `isFlagged` property of the message must be identical to the value given to match the condition. +- **isUnread**: `Boolean|null` + The `isUnread` property of the message must be identical to the value given to match the condition. +- **isAnswered**: `Boolean|null` + The `isAnswered` property of the message must be identical to the value given to match the condition. +- **isDraft**: `Boolean|null` + The `isDraft` property of the message must be identical to the value given to match the condition. +- **hasAttachment**: `Boolean|null` + The `hasAttachment` property of the message must be identical to the value given to match the condition. +- **text**: `String|null` + Looks for the text in the *from*, *to*, *cc*, *bcc*, *subject*, *textBody* or *htmlBody* properties of the message. +- **from**: `String|null` + Looks for the text in the *from* property of the message. +- **to**: `String|null` + Looks for the text in the *to* property of the message. +- **cc**: `String|null` + Looks for the text in the *cc* property of the message. +- **bcc**: `String|null` + Looks for the text in the *bcc* property of the message. +- **subject**: `String|null` + Looks for the text in the *subject* property of the message. +- **body**: `String|null` + Looks for the text in the *textBody* or *htmlBody* property of the message. +- **header**: `String[]|null` + The array MUST contain either one or two elements. The first element is the name of the header to match against. The second (optional) element is the text to look for in the header. If not supplied, the message matches simply if it *has* a header of the given name. + +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: + +- Text SHOULD be matched in a case-insensitive manner. +- Text contained in either (but matched) single or double quotes SHOULD be treated as a **phrase search**, that is a match is required for that exact sequence of words, excluding the surrounding quotation marks. Use `\"`, `\'` and `\\` to match a literal `"`, `'` and `\` respectively in a phrase. +- Outside of a phrase, white-space SHOULD be treated as dividing separate tokens that may be searched for separately in the message, but MUST all be present for the message to match the filter. +- Tokens MAY be matched on a whole-word basis using stemming (so for example a text search for `bus` would match "buses" but not "business"). +- When searching inside the *htmlBody* property, HTML tags and attributes SHOULD be ignored. + +#### 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`. + +Optionally, following the property name there can be a space and then either the string `asc` or `desc` to specify ascending or descending sort for that property. If not specified, it MUST default to **descending**. + +The following properties MUST be supported for sorting: + +- **id** - The id as returned in the Message object. +- **date** - The date as returned in the Message object. + +The following properties SHOULD be supported for sorting: + +- **size** - The size as returned in the Message object. +- **from** â This is taken to be either the "name" part of the Emailer object, or if none then the "email" part of the Emailer object (see the definition of the from property in the Message object). If still none, consider the value to be the empty string. +- **to** - This is taken to be either the "name" part of the **first** Emailer object, or if none then the "email" part of the **first** Emailer object (see the definition of the to property in the Message object). If still none, consider the value to be the empty string. +- **subject** - This is taken to be the subject of the Message with any ignoring any leading "Fwd:"s or "Re:"s (case-insensitive match). +- **threadIsFlagged** - This value MUST be considered `true` for the message if **any** of the messages in the same thread (regardless of mailbox) have `isFlagged: true`. +- **threadIsUnread** - This value MUST be considered `true` for the message if **any** of the messages in the same thread (regardless of mailbox) have `isUnread: true`. +- **isFlagged** - The `isFlagged` state of the message (only). +- **isUnread** - The `isUnread` state of the message (only). + +The server MAY support sorting based on other properties as well. A client can discover which properties are supported by inspecting the *capabilities* property on the Account object. + +The method of comparison depends on the type of the property: + +- `String`: Comparison function is server-dependent. It SHOULD be case-insensitive and SHOULD take into account locale-specific conventions if known for the user. However, the server MAY choose to just sort based on unicode code point, after best-effort translation to lower-case. +- `Date`: If sorting in ascending order, the earlier date MUST come first. +- `Boolean`: If sorting in ascending order, a `false` value MUST come before a `true` value. + +#### 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. + +#### 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. + +#### Response + +The response to a call to *getMessageList* is called *messageList*. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **filter**: `FilterCondition|FilterOperator|null` + The filter of the message list. Echoed back from the call. +- **sort**: `String[]` + A list of Message property names used to sort by. Echoed back from the call. +- **collapseThreads**: `Boolean` + Echoed back from the call. +- **state**: `String` + A string encoding the current state on the server. This string will change if the results of the message list MAY have changed (for example, there has been a change to the state of the set of Messages; it does not guarantee that anything in the list has changed). It may be passed to *getMessageListUpdates* to efficiently get the set of changes from the previous state. + + Should a client receive back a response with a different state string to a previous call, it MUST either throw away the currently cached list and fetch it again (note, this does not require fetching the messages again, just the list of ids) or, if the server supports it, call *getMessageListUpdates* to get the delta difference. +- **canCalculateUpdates**: `Boolean` + This is `true` if the server supports calling `getMessageListUpdates` with these `filter`/`sort`/`collapseThreads` parameters. Note, this does not guarantee that the getMessageListUpdates call will succeed, as it may only be possible for a limited time afterwards due to server internal implementation details. +- **position**: `Number` + The 0-based index of the first result in the `threadIds` array within the complete list. +- **total**: `Number` + The total number of messages in the message list (given the *filter* and *collapseThreads* arguments). +- **threadIds**: `String[]` + The list of Thread ids for each message in the list after filtering, sorting and collapsing threads, starting at the index given by the *position* argument of this response, and continuing until it hits the end of the list or reaches the `limit` number of ids. +- **messageIds**: `String[]` + The list of Message ids for each message in the list after filtering, sorting and collapsing threads, starting at the index given by the *position* argument of this response, and continuing until it hits the end of the list or reaches the `limit` number of ids. + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`unsupportedSort`: Returned if the *sort* includes a property the server does not support sorting on. + +`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. + +### 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: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, the primary account will be used. +- **filter**: `FilterCondition|FilterOperator|null` + The filter argument that was used with *getMessageList*. +- **sort**: `String[]|null` + The sort argument that was used with *getMessageList*. +- **collapseThreads**: `Boolean|null` + The *collapseThreads* argument that was used with *getMessageList*. +- **sinceState**: `String` + The current state of the client. This is the string that was returned as the *state* argument in the *messageList* response. The server will return the changes made since this state. +- **uptoMessageId**: `String|null` + The message id of the last message in the list that the client knows about. In the common case of the client only having the first X ids cached, this allows the server to ignore changes further down the list the client doesn't care about. +- **maxChanges**: `Number|null` + The maximum number of changes to return in the response. See below for a more detailed description. + +The response to *getMessageListUpdates* is called *messageListUpdates* It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **filter**: `FilterCondition|FilterOperator|null` + The filter of the message list. Echoed back from the call. +- **sort**: `String[]|null` + A list of Message property names used to sort by. Echoed back from the call. +- **collapseThreads**: `Boolean` + Echoed back from the call. +- **oldState**: `String` + This is the `sinceState` argument echoed back; the state from which the server is returning changes. +- **newState**: `String` + This is the state the client will be in after applying the set of changes to the old state. +- **uptoMessageId**: `String|null` + Echoed back from the call. +- **total**: `Number` + The total number of messages in the message list (given the filter and collapseThreads arguments). +- **removed**: `RemovedItem[]` + The *messageId* and *threadId* for every message that was in the list in the old state and is not in the list in the new state. If the server cannot calculate this exactly, the server MAY return extra messages in addition that MAY have been in the old list but are not in the new list. + + If an *uptoMessageId* was given AND this id was found in the list, only messages positioned before this message that were removed need be returned. + + In addition, if the sort includes the property *isUnread* or *isFlagged*, the server MUST include all messages in the current list for which this property MAY have changed. If `collapseThreads == true`, then the server MUST include all messages in the current list for which this property MAY have changed **on any of the messages in the thread**. + +- **added**: `AddedItem[]` + The messageId and threadId and index in the list (in the new state) for every message that has been added to the list since the old state AND every message in the current list that was included in the *removed* array (due to a filter or sort based upon a mutable property). The array MUST be sorted in order of index, lowest index first. + + If an *uptoMessageId* was given AND this id was found in the list, only messages positioned before this message that have been added need be returned. + +A **RemovedItem** object has the following properties: + +- **messageId**: `String` +- **threadId**: `String` + +An **AddedItem** object has the following properties: + +- **messageId**: `String` +- **threadId**: `String` +- **index**: `Number` + +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. + +`accountNoMail`: Returned if the *accountId* given corresponds to a valid account, but does not contain any mail data. + +`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. Added: james/project/trunk/server/protocols/jmap/doc/spec/push.mdwn URL: http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/doc/spec/push.mdwn?rev=1726757&view=auto ============================================================================== --- james/project/trunk/server/protocols/jmap/doc/spec/push.mdwn (added) +++ james/project/trunk/server/protocols/jmap/doc/spec/push.mdwn Tue Jan 26 10:04:59 2016 @@ -0,0 +1,62 @@ +## Push + +Any modern email client should be able to update instantly whenever the data on the server is changed by another client or message delivery. Push notifications in JMAP occur out-of-band (i.e. not over the same connection as API exchanges) so that they can make use of efficient native push mechanisms on different platforms. + +The general model for push is simple and does not send any sensitive data over the push channel, making it suitable for use with less trusted 3rd party intermediaries. The format allows multiple changes to be coalesced into a single push update, and the frequency of pushes to be rate limited by the server. It doesn't matter if some push events are dropped before they reach the client; it will still get all changes next time it syncs. + +When something changes on the server, the server pushes a small JSON object to the client with the following property: + +- **changed**: `String[ChangedStates]` + A map of *account id* to an object encoding the state of data types which have changed for that account since the last push event, for each of the accounts to which the user has access and for which something has changed. + +A **ChangedStates** object is a map of the type name (e.g. "Mailbox" or "Message") to the current state token for that type (i.e. the "state" property that would currently be returned by a call to "getMailboxes" or "getMessages", as appropriate). The types in JMAP are "Mailbox", "Thread", "Message", "ContactGroup", "Contact", "Calendar", "CalendarEvent". + +Upon receiving this data, the client can compare the new state strings with its current values to see whether it has the current data for these types. The actual changes can then be efficiently fetched in a single standard API request (using the *getFooUpdates* type methods). + +### Event Source + +There are two mechanisms by which the client can receive the push events. The first is directly via a `text/event-stream` resource, as described in +<http://www.w3.org/TR/eventsource/>. This is essentially a long running HTTP request down which the server can push data. When a change occurs, the server MUST push an event called **state** to any connected clients. + +The server MAY also set a new `Last-Event-Id` that encodes the entire server state visible to the user. When a new connection is made to the event-source endpoint, the server can then work out whether the client has missed some changes which it should send immediately. + +The server MUST also send an event called **ping** with an empty object as the data if a maximum of 5 minutes has elapsed since the previous event. This MUST NOT set a new `Last-Event-Id`. A client may detect the absence of these to determine that the HTTP connection has been dropped somewhere along the route and so it needs to re-establish the connection. + +Refer to the Authentication section of this spec for details on how to get the URL for the event-source endpoint. The request must be authenticated using an `Authorization` header like any HTTP request. + +A client MAY hold open multiple connections to the event-source, although it SHOULD try to use a single connection for efficiency. + +### setPushCallback + +The second push mechanism is to register a callback URL to which the JMAP server will make an HTTPS POST request whenever the event occurs. The request MUST have a content type of `application/json` and contain the same UTF-8 JSON encoded object as described above as the body. + +The JMAP server MUST also set the following headers in the POST request: +- `X-JMAP-EventType: state` +- `X-JMAP-User: ${username}` where `${username}` is the username of the authenticated user for which the push event occurred. + +The JMAP server MUST follow any redirects. If the final response code from the server is `2xx`, the callback is considered a success. If the response code is `503` (Service Unavailable), the JMAP server MAY try again later (but may also just drop the event). If the response code is `429` (Too Many Requests) the JMAP server SHOULD attempt to reduce the frequency of pushes to that URL. Any other response code SHOULD be considered a **permanent failure** and the callback should be deregistered (not tried again even for future events unless explicitly re-registered by the client). + +The URL set by the client MUST use the HTTPS protocol and SHOULD encode within it a unique token that can be verified by the server to know that the request comes from the JMAP server the authenticated client connected to. + +The callback is tied to the access token used to create it. Should the access token expire or be revoked, the callback MUST be removed by the JMAP server. The client MUST re-register the callback after reauthenticating to resume callbacks. + +Each session may only have a single callback URL registered. To set it, make a call to *setPushCallback*. It takes the following argument: + +- **callback**: `String|null` + The (HTTPS) URL the JMAP server should POST events to. This will replace any previously set URL. Set to `null` to just remove any previously set callback URL. + +The response to *setPushCallback* is called *pushCallbackSet*. It has the following argument: + +- **callback**: `String|null` + Echoed back from the call. + +The following error may be returned instead of the *mailboxesSet* response: + +`invalidUrl`: Returned if the URL does not begin with `https://`, or is otherwise syntactically invalid or does not resolve. + +### getPushCallback + +To check the currently set callback URL (if any), make a call to *getPushCallback*. It does not take any arguments. The response to *getPushCallback* is called `pushCallback`. It has a single argument: + +- **callback**: `String|null` + The URL the JMAP server is currently posting push events to, or `null` if none. Added: james/project/trunk/server/protocols/jmap/doc/spec/searchsnippet.mdwn URL: http://svn.apache.org/viewvc/james/project/trunk/server/protocols/jmap/doc/spec/searchsnippet.mdwn?rev=1726757&view=auto ============================================================================== --- james/project/trunk/server/protocols/jmap/doc/spec/searchsnippet.mdwn (added) +++ james/project/trunk/server/protocols/jmap/doc/spec/searchsnippet.mdwn Tue Jan 26 10:04:59 2016 @@ -0,0 +1,41 @@ +## 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: + +- **messageId**: `String` + The message id the snippet applies to. +- **subject**: `String|null` + If text from the filter matches the subject, this is the subject of the message HTML-escaped, with matching words/phrases wrapped in `<mark></mark>` tags. If it does not match, this is `null`. +- **preview**: `String|null` + If text from the filter matches the plain-text or HTML body, this is the relevant section of the body (converted to plain text if originally HTML), HTML-escaped, with matching words/phrases wrapped in `<mark></mark>` tags, up to 256 characters long. If it does not match, this is `null`. + +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 just return `null` for both the *subject* and *preview* properties. + +Note, unlike most data types, a SearchSnippet DOES NOT have a property called `id`. + +### getSearchSnippets + +To fetch search snippets, make a call to `getSearchSnippets`. It takes the following arguments: + +- **accountId**: `String|null` + The id of the account to use for this call. If `null`, defaults to the primary account. +- **messageIds**: `String[]` + The list of ids of messages to fetch the snippets for. +- **filter**: `FilterCondition|FilterOperator|null` + The same filter as passed to getMessageList; see the description of this method for details. + +The response to `getSearchSnippets` is called `searchSnippets`. It has the following arguments: + +- **accountId**: `String` + The id of the account used for the call. +- **filter**: `FilterOperator` + Echoed back from the call. +- **list**: `SearchSnippet[]` + An array of SearchSnippets objects for the requested message ids. This may not be in the same order as the ids that were in the request. +- **notFound**: `String[]|null` + An array of message ids requested which could not be found, or `null` if all + ids were found. + +Since snippets are only based on immutable properties, there is no state string or update mechanism needed. --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org