From 1a112f6266dbd277d77553da93760a86d25cb823 Mon Sep 17 00:00:00 2001 From: Neil Jenkins Date: Tue, 19 Sep 2017 11:21:34 +1000 Subject: [PATCH] Mail: Remove duplicative text The JMAP Mail spec should just be: 1. The data type definitions 2. Which standard method types can be used with each data type (e.g. Threads are immutable so there is no setThreads method). 3. Any changes to the standard method for that particular type (extra arguments etc.) This makes the spec *much* shorter, more understandable and easier to implement. This commit does the get/getUpdates/set methods. The getList/getListUpdates methods will receive similar treatment in a future commit. This commit causes the following minor semantic (rather than editorial) changes: * A new "singleton" SetError is defined for setVacationResponse, as technically you can pass it created/destroy arguments as per a standard setFoos. * Following standard getFoos pattern, getThreads now takes an optional "properties" argument. --- spec/mail/identity.mdown | 144 ++----------------- spec/mail/mailbox.mdown | 232 +++++------------------------- spec/mail/message.mdown | 226 +++++------------------------ spec/mail/messagesubmission.mdown | 168 +++------------------- spec/mail/searchsnippet.mdown | 2 + spec/mail/thread.mdown | 78 ++-------- spec/mail/vacationresponse.mdown | 55 ++----- 7 files changed, 120 insertions(+), 785 deletions(-) diff --git a/spec/mail/identity.mdown b/spec/mail/identity.mdown index 76fea2dc..ba1efa2c 100644 --- a/spec/mail/identity.mdown +++ b/spec/mail/identity.mdown @@ -3,7 +3,7 @@ A **Identity** object stores information about an email address (or domain) the user may send from. It has the following properties: - **id**: `String` - The id of the identity. This property is immutable. + The id of the identity. This property is immutable and may only be set by the server. - **name**: `String` The "From" *name* the client SHOULD use when creating a new message from this identity. - **email**: `String` @@ -21,145 +21,27 @@ A **Identity** object stores information about an email address (or domain) the Multiple identities with the same email address MAY exist, to allow for different settings the user wants to pick between (for example with different names/signatures). -## getIdentities - -Identities can either be fetched explicitly by id, or all of them at once. To fetch identities, make a call to `getIdentities`. It takes the following arguments: - -- **accountId**: `String|null` - The Account to fetch the identities for. If `null`, the primary account is used. -- **ids**: `String[]|null` - The ids of the identities to fetch. If `null`, all identities in the account are be fetched. - -The response to *getIdentities* is called *identities*. 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 identities change (that is, a new identity is created, a change is made to an existing identity, or an identity is deleted). It can be passed to *getIdentityUpdates* to efficiently get the list of changes from the previous state. -- **list**: `Identity[]` - An array of the Identity objects requested. This will be the **empty array** if the *ids* argument was the empty array, or contained only ids for identities that could not be found. -- **notFound**: `String[]|null` - This array contains the ids passed to the method for identities that do not exist, or `null` if all requested ids were found. It MUST be `null` if the *ids* argument in the call was `null`. +The following JMAP methods are supported: -The following errors may be returned instead of the *identities* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +## getIdentities -`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. +Standard *getFoos* method. The *ids* argument may be `null` to fetch all at once. ## getIdentityUpdates -The *getIdentityUpdates* call allows a client to efficiently update the state of its cached identities to match the new state on the server. It takes the following arguments: - -- **accountId**: `String|null` - The id of the account to use for this call. If `null`, the primary account will be used. -- **sinceState**: `String` - The current state of the client. This is the string that was returned as the *state* argument in the *identities* response. The server will return the changes made since this state. -- **maxChanges**: `Number|null` - The maximum number of Identity 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`, immediately after outputting an *identityUpdates* response, an implicit call will be made to *getidentities* with the *changed* property of the response as the *ids* argument, and the *fetchRecordProperties* argument as the *properties* argument. If `false` or `null`, no implicit call is made. - -The response to *getIdentityUpdates* is called *identityUpdates*. 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 *getIdentityUpdates* again with the *newState* returned to get further updates. If `false`, *newState* is the current server state. -- **changed**: `String[]` - An array of Identity ids where a property of the identity has changed between the old state and the new state, or the identity has been created, and the identity has not been destroyed. -- **removed**: `String[]` - An array of Identity ids for identities which have been destroyed since the old state. - -If a *maxChanges* is supplied, or set automatically by the server, the server must try to limit the number of ids across *changed* and *removed* to the number given. If there are more changes than this between the client's state and the current server state, the update returned MUST take the client to an intermediate state, from which the client can continue to call *getIdentityUpdates* until it is fully up to date. The server MAY return more ids than the *maxChanges* total if this is required for it to be able to produce an update to an intermediate state, but it SHOULD try to keep it close to the maximum requested. - -If an identity has been modified AND deleted since the oldState, the server should just return the id in the *removed* array, but MAY return it in the *changed* array as well. If an identity has been created AND deleted since the oldState, the server SHOULD remove the identity id from the response entirely, but MAY include it in the *removed* array. - -The following errors may be returned instead of the `identityUpdates` response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A `description` property MAY be present on the response object to help debug with an explanation of what the problem was. - -`cannotCalculateChanges`: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Identity cache. +Standard *getFooUpdates* method. ## setIdentities -Modifying the state of Identity objects on the server is done via the *setIdentities* method. This encompasses creating, updating and destroying Identity records. - -The *setIdentities* method takes the following arguments: - -- **accountId**: `String|null` - The id of the account to use for this call. If `null`, the primary account will be used. -- **ifInState**: `String|null` - This is a state string as returned by the *getIdentities* method. If supplied, the string must match the current state, otherwise the method MUST be aborted and a `stateMismatch` error returned. If `null`, any changes will be applied to the current state. -- **create**: `String[Identity]|null` - A map of *creation id* (an arbitrary string set by the client) to Identity objects (containing all properties except the id). -- **update**: `String[Identity]|null` - A map of id to Identity objects. The object may omit any property; only properties that have changed need be included. -- **destroy**: `String[]|null` - A list of ids for Identity objects to permanently delete. - -Each creation, modification or destruction of an object is considered an atomic unit. It is permissible for the server to commit changes to some objects but not others, however it is not permissible to only commit part of an update to a single identity. - -If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method. - -A **create** MAY be rejected with one of the following errors: - -- `maxQuotaReached`: Returned if the user has reached a server-defined limit on the number of identities. -- `emailNotPermitted`: Returned if the user tries to create an identity with an email address the user does not allow them to send from. - -If the identity has `mayDeleteIdentity == false`, any attempt to destroy it MUST be rejected with a `forbidden` error. - -If an id given cannot be found, the update or destroy MUST be rejected with a `notFound` set error. - -The response to *setIdentities* is called *identitiesSet*. It has the following arguments: - -- **accountId**: `String` - The id of the account used for the call. -- **oldState**: `String|null` - The state string that would have been returned by *getIdentities* 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 *getIdentities*. -- **created**: `String[Identity]` - A map of the creation id to an object containing the **id** property for all successfully created identities. -- **updated**: `String[Identity|null]` - The *keys* in this map are the ids of all identities that were successfully updated. If the server made any other changes to the record beyond those explicitly requested by the client, the *value* for the corresponding id in the map is an object containing the updated value of each property the **server changed**. Otherwise (if no properties changed on the server other than those explicitly updated by the client), the value is `null`. -- **destroyed**: `String[]` - A list of ids for identities that were successfully destroyed. -- **notCreated**: `String[SetError]` - A map of creation id to a SetError object for each identity that failed to be created. The possible errors are defined in the description of the method for specific data types. -- **notUpdated**: `String[SetError]` - A map of Identity id to a SetError object for each identity that failed to be updated. The possible errors are defined in the description of the method for specific data types. -- **notDestroyed**: `String[SetError]` - A map of Identity id to a SetError object for each identity that failed to be destroyed. The possible errors are defined in the description of the method for specific data types. - -A **SetError** object has the following properties: - -- **type**: `String` - The type of error. -- **description**: `String|null` - A description of the error to display to the user. - -The following errors may be returned instead of the *identitiesSet* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +Standard *setFoos* method. The following extra *SetError* types are defined: -`accountReadOnly`: Returned if the account has MailCapabilities with `isReadOnly == true`. +For **create**: -`requestTooLarge`: Returned if the total number of objects to create, update or destroy exceeds the maximum number the server is willing to process in a single method call. +- `maxQuotaReached`: The user has reached a server-defined limit on the number + of identities. +- `emailNotPermitted`: The user is not alloed to send from the address given as + the *email* property of the identity. -`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. +For **destroy**: -`stateMismatch`: Returned if an *ifInState* argument was supplied and it does not match the current state. +- `forbidden`: Returned if the identity's *mayDelete* value is `false`. diff --git a/spec/mail/mailbox.mdown b/spec/mail/mailbox.mdown index 95b40a28..4e85dec0 100644 --- a/spec/mail/mailbox.mdown +++ b/spec/mail/mailbox.mdown @@ -7,7 +7,7 @@ For compatibility with IMAP, a message MUST belong to one or more mailboxes. The A **Mailbox** object has the following properties: - **id**: `String` - The id of the mailbox. This property is immutable. + The id of the mailbox. This property is immutable and may only be set by the server. - **name**: `String` User-visible name for the mailbox, e.g. "Inbox". This may be any UTF-8 string ([@!RFC3629]) of at least 1 character in length and maximum 256 bytes in size. Servers SHOULD forbid sibling Mailboxes with the same name. - **parentId**: `String|null` @@ -36,25 +36,25 @@ A **Mailbox** object has the following properties: A mailbox with a lower order should be displayed before a mailbox with a higher order (that has the same parent) in any mailbox listing in the client's UI. Mailboxes with equal order SHOULD be sorted in alphabetical order by name. The sorting SHOULD take into account locale-specific character order convention. - **mayReadItems**: `Boolean` If true, may use this mailbox as part of a filter in a *getMessageList* call. - If a submailbox is shared but not the parent mailbox, this may be `false`. This property is server-set. + If a submailbox is shared but not the parent mailbox, this may be `false`. This property may only be set by the server. - **mayAddItems**: `Boolean` - The user may add messages to this mailbox (by either creating a new message or moving an existing one). This property is server-set. + The user may add messages to this mailbox (by either creating a new message or moving an existing one). This property may only be set by the server. - **mayRemoveItems**: `Boolean` - The user may remove messages from this mailbox (by either changing the mailboxes of a message or deleting it). This property is server-set. + The user may remove messages from this mailbox (by either changing the mailboxes of a message or deleting it). This property may only be set by the server. - **mayCreateChild**: `Boolean` - The user may create a mailbox with this mailbox as its parent. This property is server-set. + The user may create a mailbox with this mailbox as its parent. This property may only be set by the server. - **mayRename**: `Boolean` - The user may rename the mailbox or make it a child of another mailbox. This property is server-set. + The user may rename the mailbox or make it a child of another mailbox. This property may only be set by the server. - **mayDelete**: `Boolean` - The user may delete the mailbox itself. This property is server-set. + The user may delete the mailbox itself. This property may only be set by the server. - **totalMessages**: `Number` - The number of messages in this mailbox. + The number of messages in this mailbox. This property may only be set by the server. - **unreadMessages**: `Number` - The number of messages in this mailbox that have neither the `$Seen` keyword nor the `$Draft` keyword. + The number of messages in this mailbox that have neither the `$Seen` keyword nor the `$Draft` keyword. This property may only be set by the server. - **totalThreads**: `Number` - The number of threads where at least one message in the thread is in this mailbox. + The number of threads where at least one message in the thread is in this mailbox. This property may only be set by the server. - **unreadThreads**: `Number` - The number of threads where at least one message in the thread has neither the `$Seen` keyword nor the `$Draft` keyword AND at least one message in the thread is in this mailbox (but see below for special case handling of Trash). Note, the unread message does not need to be the one in this mailbox. + The number of threads where at least one message in the thread has neither the `$Seen` keyword nor the `$Draft` keyword AND at least one message in the thread is in this mailbox (but see below for special case handling of Trash). Note, the unread message does not need to be the one in this mailbox. This property may only be set by the server. The Trash mailbox (that is a mailbox with `role == "trash"`) MUST be treated specially for the purpose of unread counts: @@ -67,207 +67,43 @@ So for example, suppose you have an account where the entire contents is a singl For IMAP compatibility, a message in both the Trash and another mailbox SHOULD be treated by the client as existing in both places (i.e. when emptying the trash, the client SHOULD just remove the Trash mailbox and leave it in the other mailbox). -## getMailboxes - -Mailboxes can either be fetched explicitly by id, or all of them at once. To fetch mailboxes, make a call to `getMailboxes`. It takes the following arguments: - -- **accountId**: `String|null` - The Account to fetch the mailboxes for. If `null`, the primary account is used. -- **ids**: `String[]|null` - The ids of the mailboxes to fetch. If `null`, all mailboxes in the account are returned. -- **properties**: `String[]|null` - The properties of each mailbox to fetch. If `null`, all properties are returned. The id of the mailbox will **always** be returned, even if not explicitly requested. - -The response to *getMailboxes* is called *mailboxes*. It has the following arguments: - -- **accountId**: `String` - The id of the account used for the call. -- **state**: `String` - A string representing the state on the server for **all** mailboxes. If the state of a mailbox changes, or a new mailbox is created, or a mailbox is destroyed, this string will change. It is used to get delta updates. -- **list**: `Mailbox[]` - An array of the Mailbox objects requested. This will be the **empty array** if the *ids* argument was the empty array, or contained only ids for mailboxes that could not be found. -- **notFound**: `String[]|null` - This array contains the ids passed to the method for mailboxes that do not exist, or `null` if all requested ids were found. It MUST be `null` if the *ids* argument in the call was `null`. - -The following errors may be returned instead of the *mailboxes* response: +The following JMAP methods are supported: -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`requestTooLarge`: Returned if the number of *ids* requested by the client exceeds the maximum number the server is willing to process in a single method call. +## getMailboxes -`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. +Standard *getFoos* method. The *ids* argument may be `null` to fetch all at once. ## getMailboxUpdates -The *getMailboxUpdates* call allows a client to efficiently update the state of its cached mailboxes to match the new state on the server. It takes the following arguments: - -- **accountId**: `String|null` - The id of the account to use for this call. If `null`, the primary account will be used. -- **sinceState**: `String` - The current state of the client. This is the string that was returned as the *state* argument in the *mailboxes* response. The server will return the changes made since this state. -- **maxChanges**: `Number|null` - The maximum number of changed mailboxes 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`, immediately after outputting a *mailboxUpdates* response, an implicit call will be made to *getMailboxes* with the *changed* property of the response as the *ids* argument, and the *fetchRecordProperties* argument as the *properties* argument. If `false` or `null`, no implicit call will be made. -- **fetchRecordProperties**: `String[]|null` - If `null`, all Mailbox properties will be fetched unless *onlyCountsChanged* in the *mailboxUpdates* response is `true`, in which case only the 4 counts properties will be returned (*totalMessages*, *unreadMessages*, *totalThreads* and *unreadThreads*). If not `null`, this value will be passed through to the *getMailboxes* call regardless of the *onlyCountsChanged* value in the *mailboxUpdates* response. - -The response to *getMailboxUpdates* is called *mailboxUpdates*. It has the following arguments: - -- **accountId**: `String` - The id of the account used for the call. -- **oldState**: `String` - This is the *sinceState* argument echoed back; the state from which the server is returning changes. -- **newState**: `String` - This is the state the client will be in after applying the set of changes to the old state. -- **hasMoreUpdates**: `Boolean` - If `true`, the client may call *getMailboxUpdates* again with the *newState* returned to get further updates. If `false`, *newState* is the current server state. -- **changed**: `String[]` - An array of Mailbox ids where a property of the mailbox has changed between the old state and the new state, or the mailbox has been created, and the mailbox has not been destroyed. -- **removed**: `String[]` - An array of Mailbox ids for mailboxes which have been destroyed since the old state. -- **onlyCountsChanged**: `Boolean` - Indicates that only the mailbox counts (unread/total messages/threads) have changed since the old state. The client can then use this to optimise its data transfer and only fetch the counts. If the server is unable to tell if only counts have changed, it should just always return `false`. +Standard *getFooUpdates* method, with the following changes: -If a *maxChanges* is supplied, or set automatically by the server, the server MUST ensure the number of ids returned across *changed* and *removed* does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call *getMailboxUpdates* until it is fully up to date. If it is unable to calculate an intermediate state, it MUST return a `cannotCalculateChanges` error response instead. +1. The *mailboxUpdates* response has an extra argument: -If a mailbox 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 mailbox has been created AND deleted since the oldState, the server SHOULD remove the mailbox id from the response entirely, but MAY include it in the *removed* response, and (if in the *removed* response) MAY included it in the *changed* response as well. + - **onlyCountsChanged**: `Boolean` + Indicates that only the mailbox counts (unread/total messages/threads) have changed since the old state. The client can then use this to optimise its data transfer and only fetch the counts. If the server is unable to tell if only counts have changed, it MUST return `false`. -The following errors may be returned instead of the `mailboxUpdates` response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. - -`cannotCalculateChanges`: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Mailbox cache. +2. If *onlyCountsChanged* is `true`, and the client passed `null` as the + *fetchRecordProperties* argument, the implicit call to *getMailboxes* MUST + use `["totalMessages", "unreadMessages", "totalThreads", "unreadThreads"]` + as the *properties* argument. ## setMailboxes -Mailboxes can be created, updated and destroyed using the *setMailboxes* method. The method takes the following arguments: - -- **accountId**: `String|null` - The id of the account to use for this call. If `null`, defaults to the primary account. -- **ifInState**: `String|null` - This is a state string as returned by the *getMailboxes* method. If supplied, the string must match the current state, otherwise the method will be aborted and a `stateMismatch` error returned. If `null`, any changes will be applied to the current state. -- **create**: `String[Mailbox]|null` - A map of *creation id* (an arbitrary string set by the client) to Mailbox objects. If `null`, no objects will be created. -- **update**: `String[Mailbox]|null` - A map of mailbox id to objects containing the properties to update for that Mailbox. If `null`, no objects will be updated. -- **destroy**: `String[]|null` - A list of ids for Mailboxes to permanently delete. If `null`, no objects will be deleted. - -If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method. - -### Ordering of changes - -Each creation, modification or destruction of an object is considered an atomic unit. It is permissible for the server to commit changes to some objects but not others, however MUST NOT only commit part of an update to a single record (e.g. update the *name* field but not the *parentId* field, if both are supplied in the update object). - -The final state MUST be valid after the setMailboxes is finished, however the server may have to transition through invalid intermediate states (not exposed to the client) while processing the individual create/update/destroy requests. For example, a single method call could rename Mailbox A => B, and simultaneously rename Mailbox B => A. The final state is valid, so this is allowed, however if processed sequentially there will be an internal state where temporarily both mailboxes have the same name. - -A Mailbox may reference another Mailbox object as a parent. When a Mailbox is created or modified, it may reference another Mailbox being created *in the same API request* by using the creation id prefixed with a `#`. The order of the method calls in the request by the client MUST be such that the mailbox being referenced is created in **either the same or an earlier method call**. If within the same method call, the server MUST process the parent create first, as if this fails the create/update that references it will also fail. - -Creation ids sent by the client SHOULD be unique within the single API request for a particular data type. If a creation id is reused, the server MUST map the creation id to the most recently created item with that id. - -### Creating mailboxes - -The properties of the Mailbox object submitted for creation MUST conform to the following conditions: - -- The *id* property MUST NOT be present. -- The *parentId* property MUST be either `null` or be a valid id for a mailbox for which the `mayCreateChild` property is `true`. -- The *role* property MUST be either `null`, a valid role as listed in the Mailbox object specification, or prefixed by `"x-"`. -- The *mayXXX* properties MUST NOT be present. Restrictions may only be set by the server for system mailboxes, or when sharing mailboxes with other users (setting sharing is not defined yet in this spec). -- The *totalMessages*, *unreadMessages*, *totalThreads* and *unreadThreads* properties MUST NOT be present. - -If any of the properties are invalid, the server MUST reject the create with an `invalidProperties` error. The Error object SHOULD contain a property called *properties* of type `String[]` that lists **all** the properties that were invalid. The object MAY also contain a *description* property of type `String` with a user-friendly description of the problems. - -There may be a maximum number of mailboxes allowed on the server. If this is reached, any attempt at creation will be rejected with a `maxQuotaReached` error. - -### Updating mailboxes - -If the *id* given does not correspond to a Mailbox in the given account, the update MUST be rejected with a `notFound` error. - -All properties being updated must be of the correct type, not immutable or server-set-only, and the new value must obey all conditions of the property. In particular, note the following conditions: - -- The *name* property MUST be a valid UTF-8 string of at least 1 character in length and maximum 256 bytes in size. -- The *parentId* property MUST be either `null` or be a valid id for *another* mailbox that is **not a descendant** of this mailbox, and for which the `mayCreateChild` property is `true`. -- These properties are immutable or may only be set by the server: - - id - - role - - mayReadItems - - mayAddItems - - mayRemoveItems - - mayCreateChild - - mayRename - - mayDelete - - totalMessages - - unreadMessages - - totalThreads - - unreadThreads - -If any of the properties are invalid, the server MUST reject the update with an `invalidProperties` error. The Error object SHOULD contain a property called *properties* of type `String[]` that lists **all** the properties that were invalid. The object MAY also contain a *description* property of type `String` with a user-friendly description of the problems. - -If the the update would violate a mayXXX property, the update MUST be rejected with a `forbidden` error. - -### Destroying mailboxes - -If the *id* given does not correspond to a Mailbox in the given account, the destruction MUST be rejected with a `notFound` error. - -If the mailbox has `mayDelete == false`, the destruction MUST be rejected with a `forbidden` error. - -A mailbox MUST NOT be destroyed if it still has any child mailboxes. Attempts to do so MUST be rejected with a `mailboxHasChild` error. - -A mailbox MUST NOT be destroyed if it has any messages assigned to it. Attempts to do so MUST be rejected with a `mailboxHasMessage` error. - -### Response - -The response to *setMailboxes* is called *mailboxesSet*. It has the following arguments: - -- **accountId**: `String` - The id of the account used for the call. -- **oldState**: `String|null` - The state string that would have been returned by `getMailboxes` before making the requested changes, or `null` if the server doesn't know what the previous state string was. -- **newState**: `String` - The state string that will now be returned by `getMailboxes`. -- **created**: `String[Mailbox]` - A map of the creation id to an object containing all server-set properties for each successfully created Mailbox: *id*, *mustBeOnlyMailbox*, all *mayXXX* properties, *totalMessages*, *unreadMessages*, *totalThreads* and *unreadThreads*. -- **updated**: `String[Mailbox|null]` - The *keys* in this map are the ids of all mailboxes that were successfully updated. If the server made any other changes to the record beyond those explicitly requested by the client, the *value* for the corresponding id in the map is an object containing the updated value of each property the **server changed**. Otherwise (if no properties changed on the server other than those explicitly updated by the client), the value is `null`. -- **destroyed**: `String[]` - A list of ids for Mailboxes that were successfully destroyed. -- **notCreated**: `String[SetError]` - A map of creation id to a SetError object for each Mailbox that failed to be created. The possible errors are defined above. -- **notUpdated**: `String[SetError]` - A map of Mailbox id to a SetError object for each Mailbox that failed to be updated. The possible errors are defined above. -- **notDestroyed**: `String[SetError]` - A map of Mailbox id to a SetError object for each Mailbox that failed to be destroyed. The possible errors are defined above. - -The following errors may be returned instead of the *mailboxesSet* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`accountReadOnly`: Returned if the account has `isReadOnly == true`. +Standard *setFoos* method. The following extra *SetError* types are defined: -`requestTooLarge`: Returned if the total number of objects to create, update or destroy exceeds the maximum number the server is willing to process in a single method call. +For **create**: -`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. +- `maxQuotaReached`: The user has reached a server-defined limit on the number + of mailboxes. -`stateMismatch`: Returned if an `ifInState` argument was supplied and it does not match the current state. +For **update**: -Example request: +- `forbidden`: The update would violate a mayXXX property. - [ "setMailboxes", { - "ifInState": "ms4123", - "update": { - "f3": { - "name": "The new name" - } - }, - "destroy": [ "f5" ] - }, "#0" ] +For **destroy**: +- `forbidden`: The update would violate a mayXXX property. +- `mailboxHasChild`: The mailbox still has at least one child mailbox. The + client MUST remove these before it can delete the parent mailbox. +- `mailboxHasMessage`: The mailbox has at least one message assigned to it. The + client MUST remove these before it can delete the mailbox. diff --git a/spec/mail/message.mdown b/spec/mail/message.mdown index f2afd612..01ddbcce 100644 --- a/spec/mail/message.mdown +++ b/spec/mail/message.mdown @@ -1,18 +1,18 @@ # 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. +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 textual body parts, such as [base64](https://tools.ietf.org/html/rfc4648), or [RFC 2047](http://tools.ietf.org/html/rfc2047) encoding of non-ASCII characters, MUST be fully decoded into UTF-8. -A **Message** object has the following properties: +A **Message** object has the following properties. All are **immutable** unless otherwise stated: - **id**: `String` - The id of the message. + The id of the message. This property may only be set by the server. - **blobId**: `String` The id representing the raw [@!RFC5322] message. This may be used to download - the original message or to attach it directly to another message etc. + the original message or to attach it directly to another message etc. This property may only be set by the server. - **threadId**: `String` - The id of the thread to which this message belongs. + The id of the thread to which this message belongs. This property may only be set by the server. - **mailboxIds**: `String[Boolean]` (Mutable) The set of mailbox ids this message is in. A message MUST belong to one or more mailboxes at all times (until it is deleted). The set is represented as an object, with each key being a *Mailbox id*. The value for each key in the object MUST be `true`. - **keywords**: `String[Boolean]` (Mutable) @@ -36,7 +36,7 @@ A **Message** object has the following properties: - `$Junk`: The message is definitely spam. Clients SHOULD set this flag when users report spam to help train automated spam-detection systems. - `$NotJunk`: The message is definitely not spam. Clients SHOULD set this flag when users indicate a message is legitimate, to help train automated spam-detection systems. - **hasAttachment**: `Boolean` - Does the message have any attachments? + Does the message have any attachments? This property may only be set by the server, based on the *attachments* property. - **headers**: `String[String]` A map of lower-cased 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. - **sender**: `Emailer|null` @@ -52,13 +52,13 @@ A **Message** object has the following properties: - **replyTo**: `Emailer[]|null` An array of name/email objects (see below) representing the parsed `Reply-To` header of the email, in the same order as they appear in the header. If the email doesn't have a `Reply-To` header, this is `null`. If the header exists but does not have any content, the response is an array of zero length. - **subject**: `String` - The subject of the message. + The subject of the message. If none, defaults to the empty string, not `null`. - **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. + The size in bytes of the whole message as counted by the server towards the user's quota. This property may only be set by the server. - **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. + 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. This property may only be set by the server. - **textBody**: `String` The plain text body part for the message. If there is only an HTML version of the body, a plain text version MUST be generated from this; the exact method of conversion in this case is not defined and is server-specific. If there is neither a `text/plain` nor a `text/html` body part, this MUST be the empty string. - **htmlBody**: `String|null` @@ -81,6 +81,8 @@ A **Message** object has the following properties: - attachments - attachedMessages + This property may only be set by the server, based on the *attachments* property. + An **Emailer** object has the following properties: - **name**: `String` @@ -117,46 +119,20 @@ An **Attachment** object has the following properties: - **height**: `Number|null` (optional, server MAY omit if not supported) 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: +To add an attachment, the file must first be uploaded using the standard upload mechanism; this will give the client a blobId that may be used to identify the file. The `cid` property may be assigned by the client, and is solely used for matching up with `cid:` links inside the `htmlBody`. -- **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 following JMAP methods are supported: -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. +## getMessages -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +Standard *getFoos* method, except the client may use the following pseudo values in the *properties* argument: -`requestTooLarge`: Returned if the number of *ids* requested by the client exceeds the maximum number the server is willing to process in a single method call. +- **body**: If `"body"` is included in the list of requested properties, it MUST 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 MUST 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. -`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 -Example request: +Request: ["getMessages", { "ids": [ "f123u456", "f123u457" ], @@ -184,172 +160,40 @@ and response: ## 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, immediately 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 ensure the number of ids returned across *changed* and *removed* does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call *getMessageUpdates* until it is fully up to date. If it is unable to calculate an intermediate state, it MUST return a `cannotCalculateChanges` error response instead. - -If a message has been modified AND deleted since the oldState, the server SHOULD just return the id in the *removed* response, but MAY return it in the changed response as well. If a message has been created AND deleted since the oldState, the server SHOULD remove the message id from the response entirely, but MAY include it in the *removed* response, and (if in the *removed* response) MAY included it in the *changed* response as well. - -The following errors may be returned instead of the *messageUpdates* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. - -`cannotCalculateChanges`: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Message cache. +Standard *getFooUpdates* method. ## setMessages -The *setMessages* method encompasses: +Standard *setFoos* method. The *setMessages* method encompasses: - Creating a draft 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. If `null`, any changes will be applied to the current state. -- **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 message id to objects containing the properties to update for that Message. -- **destroy**: `String[]|null` - A list of ids for Message objects to permanently delete. - -Each creation, modification or destruction of an object is considered an atomic unit. It is permissible for the server to commit changes to some objects but not others, however it is not permissible to only commit part of an update to a single record (e.g. update the *keywords* field but not the *mailboxIds* field, if both are supplied in the update object for a message). +When creating a message, the client MAY omit the *headers* property. If included, it specifies extra headers to add in addition to any based off the parsed properties (like from/to/subject), or that the server chooses to set. The keys MUST only contain the characters a-z (lower-case only), 0-9 and hyphens. If a header is included that conflicts with one of the other properties on the Message object (e.g. *from*, *date*), the value in the *headers* object MUST be ignored. If not included, the server MUST generate and set a `Message-Id` header in conformance with [@!RFC5322] section 3.6.4. -If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method. +The *date* property MAY also be omitted when creating a message; the server MUST default it to the current time. -If an id given cannot be found, the update or destroy MUST be rejected with a `notFound` set error. +Other than making sure it conforms to the correct type, the server MUST NOT attempt to validate *from*/*to*/*cc*/*bcc* when creating a message. This is to ensure draft messages can be saved at any point. -### Creating a message (for example saving a draft) +Destroying a message removes it from all mailboxes to which it belonged. To just delete a message to trash, simply change the `mailboxIds` property so it is now in the mailbox with `role == "trash"`, and remove all other mailbox ids. -The properties of the Message object submitted for creation MUST conform to the following conditions: +When emptying the trash, clients SHOULD NOT destroy messages which are also in a mailbox other than trash. For those messages, they SHOULD just remove the Trash mailbox from the message. -- **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, and contain at least one mailbox. -- **keywords**: This property MUST be included. If the message is a draft, it SHOULD include the `$Draft` and `$Seen` keywords. -- **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 - (lower-case only), 0-9 and hyphens. If a header is included that conflicts with one of the other properties on the Message object (e.g. *from*, *date*), the value in the *headers* object MUST be ignored. -- **from**: Optional. -- **to**: Optional. -- **cc**: Optional. -- **bcc**: Optional. -- **replyTo**: Optional. -- **subject**: Optional. Defaults to the empty string (`""`). -- **date**: Optional. -- **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 the HTML body. -- **htmlBody**: Optional. -- **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 blobId that may be used to identify the file. The `cid` property may be assigned by the client, and is solely used for matching up with `cid:` links inside the `htmlBody`. +The following extra *SetError* types are defined: - If any of the files specified in *attachments* cannot be found, the creation MUST be rejected with an `invalidProperties` error. An extra property SHOULD be included in the error object called `attachmentsNotFound`, of type `String[]`, which SHOULD be an array of the *blobId* of every attachment that could not be found on the server. -- **attachedMessages**: This MUST NOT be included. +For **create**: -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. This is to ensure draft messages can be saved at any point. - -If a message cannot be created 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 *mailboxIds* and *keywords* properties may be modified, and they are subject to the following constraints: - -- **keywords**: The server MAY have a maximum number of keywords it supports; if the change would exceed this, it MUST be rejected with a `tooManyKeywords` error. - -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"`, and remove all other mailbox ids. - -### 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. - -When emptying the trash, clients SHOULD NOT destroy messages which are also in a mailbox other than trash. For those messages, they should just remove the Trash mailbox from the message. - -### 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[Message|null]` - The *keys* in this map are the ids of all messages that were successfully updated. If the server made any other changes to the record beyond those explicitly requested by the client, the *value* for the corresponding id in the map is an object containing the updated value of each property the **server changed**. Otherwise (if no properties changed on the server other than those explicitly updated by the client), the value is `null`. -- **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. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`accountReadOnly`: Returned if the account has `isReadOnly == true`. +- `attachmentNotFound`: At least one blob id given in an attachment doesn't + exist. An extra *notFound* property of type `String[]` MUST be included in the error object containing every *blobId* referenced in *attachments* that could not be found on the server. +- `maxQuotaReached`: The user has reached a server-defined limit on their + message storage quota. -`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. +For **update**: -`stateMismatch`: Returned if an *ifInState* argument was supplied and it does not match the current state. +- `tooManyKeywords`: The change to the message's keywords would exceed a + server-defined maximum. ## importMessages diff --git a/spec/mail/messagesubmission.mdown b/spec/mail/messagesubmission.mdown index ee011ec6..c5e85248 100644 --- a/spec/mail/messagesubmission.mdown +++ b/spec/mail/messagesubmission.mdown @@ -58,7 +58,7 @@ The MessageSubmission object represents the submission of a message for delivery - `canceled`: The message submission was canceled and will not be delivered to any recipient. - On systems that do not support unsending, the value of this property will always be `final`. On systems that do support canceling submission, it will start as `pending`, and MAY transition to `final` when the server knows it definitely cannot recall the message, but MAY just remain `pending`. If in pending state, a client can attempt to cancel the submission by setting this property to `canceled`; if the update succeeds, the submission was successfully canceled. + On systems that do not support unsending, the value of this property will always be `final`. On systems that do support canceling submission, it will start as `pending`, and MAY transition to `final` when the server knows it definitely cannot recall the message, but MAY just remain `pending`. If in pending state, a client can attempt to cancel the submission by setting this property to `canceled`; if the update succeeds, the submission was successfully canceled and the message has not been delivered to any of the original recipients. - **deliveryStatus**: `String[DeliveryStatus]|null` This represents the delivery status for each of the message recipients, if known. This property is server-set and MAY not be supported by all servers, in which case it will remain `null`. Servers that support it SHOULD update the MessageSubmission object each time the status of any of the recipients changes, even if some recipients are still being retried. @@ -135,117 +135,36 @@ JMAP servers MAY choose not to expose DSN and MDN responses as Message objects i For efficiency, a server MAY destroy MessageSubmission objects a certain amount of time after the message is successfully sent or it has finished retrying sending the message. For very basic SMTP proxies, this MAY be immediately after creation, as it has no way to assign a real id and return the information again if fetched later. -## getMessageSubmissions - -MessageSubmissions can only be fetched explicitly by id. To fetch them, make a call to `getMessageSubmissions`. 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 MessageSubmission objects to fetch. -- **properties**: `String[]|null` - A list of properties to fetch for each MessageSubmission object. 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 MessageSubmission object. - -The response to *getMessageSubmissions* is called *messageSubmissions*. 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 MessageSubmissions change (that is, a new one is created, a change is made to one of the mutable properties by the server). It can be passed to *getMessageSubmissionUpdates* to efficiently get the list of changes from the previous state. -- **list**: `MessageSubmission[]` - An array of MessageSubmission objects for the requested ids. This MAY be in a different order to the *ids* request argument. -- **notFound**: `String[]|null` - An array of ids requested which could not be found, or `null` if all ids were - found. +The following JMAP methods are supported: -The following errors may be returned instead of the *messageSubmissions* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`requestTooLarge`: Returned if the number of *ids* requested by the client exceeds the maximum number the server is willing to process in a single method call. +## getMessageSubmissions -`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. +Standard *getFoos* method. ## getMessageSubmissionUpdates -If a call to *getMessageSubmissions* returns with a different *state* string in the response to a previous call, the state of the set of MessageSubmissions has changed on the server. - -The *getMessageSubmissionUpdates* call allows a client to efficiently update 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 *messageSubmissions* response. The server will return the changes made since this state. -- **maxChanges**: `Number|null` - The maximum number of changed objects 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, immediately after outputting a *messageSubmissionUpdates* response, an implicit call will be made to *getMessageSubmissions* with a list of all MessageSubmission 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 MessageSubmission objects. See *getMessageSubmissions* for a full description. - -The response to *getMessageSubmissionUpdates* is called *messageSubmissionUpdates*. 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 *getMessageSubmissionUpdates* again with the *newState* returned to get further updates. If `false`, *newState* is the current server state. -- **changed**: `String[]` - An array of ids for objects that have either been created or had their state change, and are not currently deleted. -- **removed**: `String[]` - An array of ids for objects that have been deleted since the oldState. - -If a *maxChanges* is supplied, or set automatically by the server, the server MUST ensure the number of ids returned across *changed* and *removed* does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call *getMessageSubmissionUpdates* until it is fully up to date. If it is unable to calculate an intermediate state, it MUST return a `cannotCalculateChanges` error response instead. - -If an object 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 an object has been created AND deleted since the oldState, the server SHOULD remove the object 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 *messageSubmissionUpdates* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. - -`cannotCalculateChanges`: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its cache for this object type. +Standard *getFooUpdates* method. ## setMessageSubmissions -This method is used to send messages, or to recall a sent message (if possible). It takes the following arguments +Standard *setFoos* method, with the following two extra 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 *getMessageSubmissions* method. If supplied, the string must match the current state, otherwise the method will be aborted and a `stateMismatch` error returned. If `null`, any changes will be applied to the current state. -- **create**: `String[MessageSubmission]|null` - A map of *creation id* (an arbitrary string set by the client) to MessageSubmission objects to create. Note, the *messageId* may be a back-reference to a message created in the same API request. - - This is how a message is sent. - -- **update**: `String[MessageSubmission]|null` - A map of MessageSubmission id to objects containing the properties to update for that MessageSubmission. This is how a message may be unsent. -- **destroy**: `String[]|null` - A list of ids for MessageSubmission objects to delete. - **onSuccessUpdateMessage**: `String[Message]|null` A map of *MessageSubmission id* to an object containing properties to update on the Message object referenced by the MessageSubmission if the create/update/destroy succeeds. (For references to MessageSubmission creations, this is equivalent to a back reference so the id will be the creation id prefixed with a `#`.) - **onSuccessDestroyMessage**: `String[]|null` A list of *MessageSubmission ids* for which the message with the corresponding messageId should be destroyed if the create/update/destroy succeeds. (For references to MessageSubmission creations, this is equivalent to a back reference so the id will be the creation id prefixed with a `#`.) -Each creation, modification or destruction of an object is considered an atomic unit. If a create, update or destroy is rejected, the appropriate error MUST be added to the notCreated/notUpdated/notDestroyed property of the response and the server MUST continue to the next create/update/destroy. It does not terminate the method. +A single implicit *setMessages* call MUST be made after all MessageSubmission create/update/destroy requests have been processed to perform any changes requested in these two arguments. The *messagesSet* response MUST be returned after the *messageSubmissionsSet* response. + +A message is sent by creating a MessageSubmission object. When processing each create, the server must check that the message is valid, and the user has sufficient authorization to send it. If the creation succeeds, the message will be sent to the recipients given in the envelope *rcptTo* parameter. The server MUST remove any *Bcc* header present on the message during delivery. The server MAY add or remove other headers from the submitted message, or make further alterations in accordance with the server's policy during delivery. -If an id given cannot be found, the update/destroy MUST be rejected with a `notFound` set error. +If the referenced message is destroyed at any point after the MessageSubmission object is created, this MUST NOT change the behaviour of the message submission (i.e. it does not cancel a future send). + +Similarly, destroying a MessageSubmission object MUST NOT affect the deliveries it represents. It purely removes the record of the message submission. The server MAY automatically destroy MessageSubmission objects after a certain time or in response to other triggers, and MAY forbid the client from manually destroying MessageSubmission objects. -### Sending a message +The following extra *SetError* types are defined: -A message is sent by creating a MessageSubmission object. When processing each create, the server must check that the message is valid, and the user has sufficient authorization to send it. The creation may be rejected with one of the following SetErrors: +For **create**: - `tooLarge` - The message size is larger than the server supports. A *maxSize* `Number` property MUST be present on the SetError specifying the maximum size @@ -265,60 +184,13 @@ A message is sent by creating a MessageSubmission object. When processing each c account. - `invalidMessage` - The message to be sent is invalid in some way. The SetError SHOULD contain a property called *properties* of type `String[]` that lists **all** the properties of the Message that were invalid. -- `invalidProperties` - The object contains properties which are of the wrong - type, or is missing a required property, or is specifying a property that may only be set by the server. - -If the creation succeeds, the message will be sent to the recipients given in the envelope *rcptTo* parameter. The server MUST remove any *Bcc* header present on the message during delivery. The server MAY add or remove other headers from the submitted message, or make further alterations in accordance with the server's policy during delivery. - -If the referenced message is destroyed at any point after the MessageSubmission object is created, this MUST NOT change the behaviour of the message submission (i.e. it does not cancel a future send). - -### Undo send - -In some systems, a message that has been queued to be sent may be unsent if it is before the *sendAt* time, or the recipients are all on the same system. The server indicates that it may be possible to cancel the submission by setting the *undoStatus* property to `pending`. - -To "unsend" a message, attempt to update the *undoStatus* of the MessageSubmission object from `pending` to `canceled`. If this succeeds, the message will definitely not be sent to any recipients. - -If the MessageSubmission exists but the message cannot be unsent, this MUST be rejected with a `cannotUnsend` error. If the message submission object doesn't exist (because the server only retains them for a certain amount of time, or it's just bogus), this MUST be rejected with a `notFound` error. - -### Destroying a MessageSubmission object - -Destroying a MessageSubmission object MUST NOT affect the deliveries it represents. It purely removes the record of the message submission. The JMAP server MAY reject attempts by a client to destroy a MessageSubmission with a `forbidden` error. The server MAY automatically destroy MessageSubmission objects after a certain time or in response to other triggers. - -### Response - -The response to *setMessageSubmissions* is called *messageSubmissionsSet*. 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 *getMessageSubmissions* 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 *getMessageSubmissions*. -- **created**: `String[MessageSubmission]` - A map of the creation id to an object containing any properties on the successfully created MessageSubmission object that were not supplied by the client, or have been changed by the server from that supplied by the client. -- **updated**: `String[MessageSubmission|null]` - The *keys* in this map are the ids of all MessageSubmission objects that were successfully updated. If the server made any other changes to the record beyond those explicitly requested by the client, the *value* for the corresponding id in the map is an object containing the updated value of each property the **server changed**. Otherwise (if no properties changed on the server other than those explicitly updated by the client), the value is `null`. -- **destroyed**: `String[]` - A list of ids for MessageSubmission objects that were successfully destroyed. -- **notCreated**: `String[SetError]` - A map of creation id to a SetError object for each MessageSubmission that failed to be created. The possible errors are defined above. -- **notUpdated**: `String[SetError]` - A map of MessageSubmission id to a SetError object for each MessageSubmission that failed to be updated. The possible errors are defined above. -- **notDestroyed**: `String[SetError]` - A map of MessageSubmission id to a SetError object for each MessageSubmission 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. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`accountReadOnly`: Returned if the account has `isReadOnly == true`. -`invalidArguments`: Returned if one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. +For **update**: -`stateMismatch`: Returned if an *ifInState* argument was supplied and it does not match the current state. +- `cannotUnsend`: The client attempted to update the *undoStatus* of a valid + MessageSubmission object from `pending` to `canceled`, but the message cannot be unsent. -### Implicit call to setMessages +For **destroy**: -If onSuccessUpdateMessage or onSuccessDestroyMessage arguments are specified, the server MUST make a single implicit call to *setMessages* after the *messageSubmissionsSet* response to create/update/destroy messages as requested for each successful MessageSubmission create/update/destroy. +- `forbidden`: The server does not allow clients to destroy MessageSubmission + objects. diff --git a/spec/mail/searchsnippet.mdown b/spec/mail/searchsnippet.mdown index cbc771f4..1e019603 100644 --- a/spec/mail/searchsnippet.mdown +++ b/spec/mail/searchsnippet.mdown @@ -17,6 +17,8 @@ It is server-defined what is a relevant section of the body for preview. If the Note, unlike most data types, a SearchSnippet DOES NOT have a property called `id`. +The following JMAP method is supported: + ## getSearchSnippets To fetch search snippets, make a call to `getSearchSnippets`. It takes the following arguments: diff --git a/spec/mail/thread.mdown b/spec/mail/thread.mdown index 1f1c59f0..b86448ea 100644 --- a/spec/mail/thread.mdown +++ b/spec/mail/thread.mdown @@ -17,42 +17,20 @@ A **Thread** object has the following properties: - Other than that, everything is sorted in *date* order (as determined by the date property on the *Message* object), oldest first. - If two messages are identical under the above two conditions, the sort is server-dependent but MUST be stable (sorting by id is recommended). +The following JMAP methods are supported: + ## getThreads -Threads can only be fetched explicitly by id. To fetch threads, make a call to *getThreads*. It takes the following arguments: +Standard *getFoos* method, with the following two extra 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. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +### Example -`requestTooLarge`: Returned if the number of *ids* requested by the client exceeds the maximum number the server is willing to process in a single method call. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. - -Example of a successful request: +Request: [ "getThreads", { "ids": ["f123u4", "f41u44"], @@ -60,9 +38,10 @@ Example of a successful request: "fetchMessageProperties": null }, "#1" ] -and response: +with response: [ "threads", { + "accountId": "acme", "state": "f6a7e214", "list": [ { @@ -80,45 +59,4 @@ and response: ## 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`, immediately 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 ensure the number of ids returned across *changed* and *removed* does not exceed this limit. If there are more changes than this between the client's state and the current server state, the update returned SHOULD generate an update to take the client to an intermediate state, from which the client can continue to call *getThreadUpdates* until it is fully up to date. If it is unable to calculate an intermediate state, it MUST return a `cannotCalculateChanges` error response instead. - -If a thread has been modified AND deleted since the oldState, the server SHOULD just return the id in the *removed* response, but MAY return it in the changed response as well. If a thread has been created AND deleted since the oldState, the server SHOULD remove the thread id from the response entirely, but MAY include it in the *removed* response. - -The following errors may be returned instead of the *threadUpdates* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. - -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. - -`invalidArguments`: Returned if the request does not include one of the required arguments, or one of the arguments is of the wrong type, or otherwise invalid. A *description* property MAY be present on the response object to help debug with an explanation of what the problem was. - -`cannotCalculateChanges`: Returned if the server cannot calculate the changes from the state string given by the client. Usually due to the client's state being too old, or the server being unable to produce an update to an intermediate state when there are too many updates. The client MUST invalidate its Thread cache. +Standard *getFooUpdates* method. diff --git a/spec/mail/vacationresponse.mdown b/spec/mail/vacationresponse.mdown index 31f9ce3e..ace0d8dd 100644 --- a/spec/mail/vacationresponse.mdown +++ b/spec/mail/vacationresponse.mdown @@ -19,58 +19,19 @@ related settings for an account. It has the following properties: - **htmlBody**: `String|null` The HTML message to send in response to messages when the vacation response is enabled. If this is `null`, when the vacation message is sent an HTML body part MAY be generated from the *textBody*, or the server MAY choose to send the response as plain-text only. -## getVacationResponse - -There MUST only be exactly one VacationResponse object in an account. It MUST have the id `"singleton"`. - -To fetch the vacation response object, make a call to `getVacationResponse`. It takes the following argument: - -- **accountId**: `String|null` - The Account to get the vacation response for. If `null`, the primary account is used. +The following JMAP methods are supported: -The response to *getVacationResponse* is called *vacationResponse*. It has the following arguments: - -- **accountId**: `String` - The id of the account used for the call. -- **list**: `VacationResponse[]` - An array containing the single VacationResponse object. - -The following errors may be returned instead of the *vacationResponse* response: +## getVacationResponse -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. +Standard *getFoos* method. -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +There MUST only be exactly one VacationResponse object in an account. It MUST have the id `"singleton"`. ## setVacationResponse -Sets properties on the vacation response object. It takes the following arguments: - -- **accountId**: `String|null` - The Account to set the vacation response for. If `null`, the primary account is used. -- **update**: `String[VacationResponse]|null` - A map of id ("singleton") to the VacationResponse object with new values for the properties you wish to change. The object may omit any property; only properties that have changed need be included. - -If any of the properties in the update are invalid (immutable and different to the current server value, wrong type), the server MUST reject the update with a SetError of type `invalidProperties`. The SetError object SHOULD contain a property called *properties* of type `String[]` that lists **all** the properties that were invalid. The object MAY also contain a *description* property of type `String` with a user-friendly description of the problems. - -The response is called *vacationResponseSet*. It has the following arguments: - -- **updated**: `String[]` -- **updated**: `String[VacationResponse|null]` - If successfully updated, this map will have a sole key of "singleton". The *value* in the map is an object containing the updated value of each property the **server changed** on the record, if any. Otherwise (if no properties changed on the server other than those explicitly updated by the client), the value is `null`. -- **notUpdated**: `String[SetError]` - A map of id ("singleton") to a SetError object if the update failed. - -A **SetError** object has the following properties: - -- **type**: `String` - The type of error. -- **description**: `String|null` - A description of the error to display to the user. - -The following errors may be returned instead of the *vacationResponseSet* response: - -`accountNotFound`: Returned if an *accountId* was explicitly included with the request, but it does not correspond to a valid account. +Standard *setFoos* method. The following extra *SetError* types are defined: -`accountNotSupportedByMethod`: Returned if the *accountId* given corresponds to a valid account, but the account does not support this data type. +For **create** or **destroy**: -`invalidArguments`: Returned if one of the arguments is of the wrong type, or otherwise invalid (including using an id other than `"singleton"`). A `description` property MAY be present on the response object to help debug with an explanation of what the problem was. +- `singleton`: This is a singleton object, so you cannot create another one or + destroy the existing one.