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:"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/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-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to