You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ma...@apache.org on 2016/01/26 08:45:38 UTC

svn commit: r1726745 [4/4] - in /james/project/trunk: ./ client-guide/ home/ server-guide/ software/ spec/

Added: james/project/trunk/spec/message.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/message.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/message.mdwn (added)
+++ james/project/trunk/spec/message.mdwn Tue Jan 26 07:45:37 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:"joeb@example.com"},
+        {name:"", email:"john@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: "joe@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/spec/messagelist.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/messagelist.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/messagelist.mdwn (added)
+++ james/project/trunk/spec/messagelist.mdwn Tue Jan 26 07:45:37 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/spec/push.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/push.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/push.mdwn (added)
+++ james/project/trunk/spec/push.mdwn Tue Jan 26 07:45:37 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/spec/searchsnippet.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/searchsnippet.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/searchsnippet.mdwn (added)
+++ james/project/trunk/spec/searchsnippet.mdwn Tue Jan 26 07:45:37 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.

Added: james/project/trunk/spec/thread.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/thread.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/thread.mdwn (added)
+++ james/project/trunk/spec/thread.mdwn Tue Jan 26 07:45:37 2016
@@ -0,0 +1,120 @@
+## 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](server.html).
+
+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:
+
+- **id**: `String`
+  The id of the thread. This property is immutable.
+- **messageIds**: `String[]`
+  The ids of the messages in the thread, sorted such that:
+  - Any message with `isDraft == true` and an *inReplyToMessageId* property that corresponds to another message in the thread comes immediately after that message in the sort order.
+  - Other than that, everything is sorted in date order (the same as the *date* property on the Message object), oldest first.
+
+### getThreads
+
+Threads can only be fetched explicitly by id. To fetch threads, make a call to *getThreads*. 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 threads to fetch.
+- **fetchMessages**: `Boolean|null`
+  If true, after outputting a *threads* response, an implicit call will be made to *getMessages* with a list of all message ids in the returned threads 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.
+
+The response to *getThreads* is called *threads*. 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 threads change (that is, new messages arrive, or messages are deleted, as these are the only two events that change thread membership). It can be passed to *getThreadUpdates* to efficiently get the list of changes from the previous state.
+- **list**: `Thread[]`
+  An array of Thread objects for the requested thread ids. This may not be in the same order as the ids were in the request.
+- **notFound**: `String[]|null`
+  An array of thread ids requested which could not be found, or `null` if all ids were found.
+
+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.
+
+`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 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" ]
+
+
+### 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:
+
+- **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 *threads* response. The server will return the changes made since this state.
+- **maxChanges**: `Number|null`
+  The maximum number of Thread ids 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 *threadUpdates* response, an implicit call will be made to *getThreads* with the *changed* property of the response as the *ids* argument, and *fetchMessages* equal to `false`.
+
+The response to *getThreadUpdates* is called *threadUpdates*. 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 *getThreadUpdates* again with the *newState* returned to get further updates. If `false`, *newState* is the current server state.
+- **changed**: `String[]`
+  An array of thread ids where the list of messages within the thread has
+  changed between the old state and the new state, and the thread currently has at least one message in it.
+- **removed**: `String[]`
+  An array of thread ids where the list of messages within the thread has changed since the old state, and there are now no messages in the thread.
+
+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 *getThreadUpdates* 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 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, and optionally the *changed* response as well.
+
+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.
+
+`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 Thread cache. The error object MUST also include a `newState: String` property with the current state for the type.

Added: james/project/trunk/spec/upload.mdwn
URL: http://svn.apache.org/viewvc/james/project/trunk/spec/upload.mdwn?rev=1726745&view=auto
==============================================================================
--- james/project/trunk/spec/upload.mdwn (added)
+++ james/project/trunk/spec/upload.mdwn Tue Jan 26 07:45:37 2016
@@ -0,0 +1,50 @@
+## File Uploads
+
+There is a single endpoint which handles all file uploads, regardless of what they are to be used for. To upload a file, submit a POST request to the file upload endpoint (see the authentication section for information on how to obtain this URL). The Content-Type MUST be correctly set for the type of the file being uploaded. The request MUST be authenticated as per any HTTP request. The request MAY include an "X-JMAP-AccountId" header, with the value being the account to use for the request. Otherwise, the default account will be used.
+
+The server will respond with one of the following HTTP response codes:
+
+#### `201`: File uploaded successfully
+
+The content of the response is a single JSON object with the following properties:
+
+- **accountId**: `String`
+  The id of the account used for the call.
+- **blobId**: `String`,
+  The id representing the binary data uploaded. The data for this id is immutable. The id *only* refers to the binary data, not any metadata.
+- **type**: `String`
+  The content type of the file.
+- **size**: `Number`
+  The size of the file in bytes.
+- **expires**: `Date`
+  The date the file will be deleted from temp storage if not referenced by another object, e.g. used in a draft.
+
+Once the file has been used, for example attached to a draft message, the file will no longer expire, and is instead guaranteed to exist while at least one other object references it. Once no other object references it, the server MAY immediately delete the file at any time. It MUST NOT delete the file during the method call which removed the last reference, so that if there is a create and a delete within the same call which both reference the file, this always works.
+
+If uploading a file would take the user over quota, the server SHOULD delete previously uploaded (but unused) files before their expiry time. This means a client does not have to explicitly delete unused temporary files (indeed, there is no way for it to do so).
+
+If identical binary content is uploaded, the same *blobId* SHOULD be returned.
+
+#### `400`: Bad request
+
+The request was malformed (this includes the case where an `X-JMAP-AccountId` header is sent with a value that does not exist). The client SHOULD NOT retry the same request.
+
+#### `401`: Unauthorized
+
+The `Authorization` header was missing or did not contain a valid token. Reauthenticate and then retry the request. There is no content in the response.
+
+#### `404`: Not Found
+
+The upload endpoint has moved. See the Authentication section of the spec for how to rediscover the current URL to use. There is no content in the response.
+
+#### `413`:  Request Entity Too Large
+
+The file is larger than the maximum size the server is willing to accept for a single file. The client SHOULD NOT retry uploading the same file. There is no content in the response. The client may discover the maximum size the server is prepared to accept by inspecting the capabilities property of the Account object.
+
+#### `415`: Unsupported Media Type
+
+The server MAY choose to not allow certain content types to be uploaded, such as executable files. This error response is returned if an unacceptable type is uploaded. The client SHOULD NOT retry uploading the same file. There is no content in the response.
+
+#### `503`: Service Unavailable
+
+The server is currently down. The client should try again later with exponential backoff. There is no content in the response.



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org