Tech-invite3GPPspaceIETFspace
959493929190898887868584838281807978777675747372717069686766656463626160595857565554535251504948474645444342414039383736353433323130292827262524232221201918171615141312111009080706050403020100
in Index   Prev   Next

RFC 8621

The JSON Meta Application Protocol (JMAP) for Mail

Pages: 108
Proposed Standard
Updates:  5788
Part 3 of 5 – Pages 45 to 68
First   Prev   Next

Top   ToC   RFC8621 - Page 45   prevText

4.4. Email/query

This is a standard "/query" method as described in [RFC8620], Section 5.5 but with the following additional request arguments: o collapseThreads: "Boolean" (default: false) If true, Emails in the same Thread as a previous Email in the list (given the filter and sort order) will be removed from the list. This means only one Email at most will be included in the list for any given Thread. In quality implementations, the query "total" property is expected to be fast to calculate when the filter consists solely of a single "inMailbox" property, as it is the same as the totalEmails or totalThreads properties (depending on whether collapseThreads is true) of the associated Mailbox object.
Top   ToC   RFC8621 - Page 46

4.4.1. Filtering

A *FilterCondition* object has the following properties, any of which may be omitted: o inMailbox: "Id" A Mailbox id. An Email must be in this Mailbox to match the condition. o inMailboxOtherThan: "Id[]" A list of Mailbox ids. An Email must be in at least one Mailbox not in this list to match the condition. This is to allow messages solely in trash/spam to be easily excluded from a search. o before: "UTCDate" The "receivedAt" date-time of the Email must be before this date- time to match the condition. o after: "UTCDate" The "receivedAt" date-time of the Email must be the same or after this date-time to match the condition. o minSize: "UnsignedInt" The "size" property of the Email must be equal to or greater than this number to match the condition. o maxSize: "UnsignedInt" The "size" property of the Email must be less than this number to match the condition. o allInThreadHaveKeyword: "String" All Emails (including this one) in the same Thread as this Email must have the given keyword to match the condition. o someInThreadHaveKeyword: "String" At least one Email (possibly this one) in the same Thread as this Email must have the given keyword to match the condition.
Top   ToC   RFC8621 - Page 47
   o  noneInThreadHaveKeyword: "String"

      All Emails (including this one) in the same Thread as this Email
      must *not* have the given keyword to match the condition.

   o  hasKeyword: "String"

      This Email must have the given keyword to match the condition.

   o  notKeyword: "String"

      This Email must not have the given keyword to match the condition.

   o  hasAttachment: "Boolean"

      The "hasAttachment" property of the Email must be identical to the
      value given to match the condition.

   o  text: "String"

      Looks for the text in Emails.  The server MUST look up text in the
      From, To, Cc, Bcc, and Subject header fields of the message and
      SHOULD look inside any "text/*" or other body parts that may be
      converted to text by the server.  The server MAY extend the search
      to any additional textual property.

   o  from: "String"

      Looks for the text in the From header field of the message.

   o  to: "String"

      Looks for the text in the To header field of the message.

   o  cc: "String"

      Looks for the text in the Cc header field of the message.

   o  bcc: "String"

      Looks for the text in the Bcc header field of the message.

   o  subject: "String"

      Looks for the text in the Subject header field of the message.
Top   ToC   RFC8621 - Page 48
   o  body: "String"

      Looks for the text in one of the body parts of the message.  The
      server MAY exclude MIME body parts with content media types other
      than "text/*" and "message/*" from consideration in search
      matching.  Care should be taken to match based on the text content
      actually presented to an end user by viewers for that media type
      or otherwise identified as appropriate for search indexing.
      Matching document metadata uninteresting to an end user (e.g.,
      markup tag and attribute names) is undesirable.

   o  header: "String[]"

      The array MUST contain either one or two elements.  The first
      element is the name of the header field to match against.  The
      second (optional) element is the text to look for in the header
      field value.  If not supplied, the message matches simply if it
      has a header field of the given name.

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

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

   o  Any syntactically correct encoded sections [RFC2047] of header
      fields with a known encoding SHOULD be decoded before attempting
      to match text.

   o  When searching inside a "text/html" body part, any text considered
      markup rather than content SHOULD be ignored, including HTML tags
      and most attributes, anything inside the "<head>" tag, Cascading
      Style Sheets (CSS), and JavaScript.  Attribute content intended
      for presentation to the user such as "alt" and "title" SHOULD be
      considered in the search.

   o  Text SHOULD be matched in a case-insensitive manner.

   o  Text contained in either (but matched) single (') or double (")
      quotes SHOULD be treated as a *phrase search*; that is, a match is
      required for that exact word or sequence of words, excluding the
      surrounding quotation marks.
Top   ToC   RFC8621 - Page 49
      Within a phrase, to match one of the following characters you MUST
      escape it by prefixing it with a backslash (\):

                                    ' " \

   o  Outside of a phrase, white space SHOULD be treated as dividing
      separate tokens that may be searched for separately but MUST all
      be present for the Email to match the filter.

   o  Tokens (not part of a phrase) MAY be matched on a whole-word basis
      using stemming (for example, a text search for "bus" would match
      "buses" but not "business").

4.4.2. Sorting

The following value for the "property" field on the Comparator object MUST be supported for sorting: o "receivedAt" - The "receivedAt" date as returned in the Email object. The following values for the "property" field on the Comparator object SHOULD be supported for sorting. When specifying a "hasKeyword", "allInThreadHaveKeyword", or "someInThreadHaveKeyword" sort, the Comparator object MUST also have a "keyword" property. o "size" - The "size" as returned in the Email object. o "from" - This is taken to be either the "name" property or if null/empty, the "email" property of the *first* EmailAddress object in the Email's "from" property. If still none, consider the value to be the empty string. o "to" - This is taken to be either the "name" property or if null/ empty, the "email" property of the *first* EmailAddress object in the Email's "to" property. If still none, consider the value to be the empty string. o "subject" - This is taken to be the base subject of the message, as defined in Section 2.1 of [RFC5256]. o "sentAt" - The "sentAt" property on the Email object. o "hasKeyword" - This value MUST be considered true if the Email has the keyword given as an additional "keyword" property on the Comparator object, or false otherwise.
Top   ToC   RFC8621 - Page 50
   o  "allInThreadHaveKeyword" - This value MUST be considered true for
      the Email if *all* of the Emails in the same Thread have the
      keyword given as an additional "keyword" property on the
      Comparator object.

   o  "someInThreadHaveKeyword" - This value MUST be considered true for
      the Email if *any* of the Emails in the same Thread have the
      keyword given as an additional "keyword" property on the
      Comparator object.

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

   Example sort:

                 [{
                   "property": "someInThreadHaveKeyword",
                   "keyword": "$flagged",
                   "isAscending": false
                 }, {
                   "property": "subject",
                   "collation": "i;ascii-casemap"
                 }, {
                   "property": "receivedAt",
                   "isAscending": false
                 }]

   This would sort Emails in flagged Threads first (the Thread is
   considered flagged if any Email within it is flagged), in subject
   order second, and then from newest first for messages with the same
   subject.  If two Emails have identical values for all three
   properties, then the order is server dependent but must be stable.

4.4.3. Thread Collapsing

When "collapseThreads" is true, then after filtering and sorting the Email list, the list is further winnowed by removing any Emails for a Thread id that has already been seen (when passing through the list sequentially). A Thread will therefore only appear *once* in the result, at the position of the first Email in the list that belongs to the Thread (given the current sort/filter).
Top   ToC   RFC8621 - Page 51

4.5. Email/queryChanges

This is a standard "/queryChanges" method as described in [RFC8620], Section 5.6 with the following additional request argument: o collapseThreads: "Boolean" (default: false) The "collapseThreads" argument that was used with "Email/query".

4.6. Email/set

This is a standard "/set" method as described in [RFC8620], Section 5.3. The "Email/set" method encompasses: o Creating a draft o Changing the keywords of an Email (e.g., unread/flagged status) o Adding/removing an Email to/from Mailboxes (moving a message) o Deleting Emails The format of the "keywords"/"mailboxIds" properties means that when updating an Email, you can either replace the entire set of keywords/ Mailboxes (by setting the full value of the property) or add/remove individual ones using the JMAP patch syntax (see [RFC8620], Section 5.3 for the specification and Section 5.7 for an example). Due to the format of the Email object, when creating an Email, there are a number of ways to specify the same information. To ensure that the message [RFC5322] to create is unambiguous, the following constraints apply to Email objects submitted for creation: o The "headers" property MUST NOT be given on either the top-level Email or an EmailBodyPart -- the client must set each header field as an individual property. o There MUST NOT be two properties that represent the same header field (e.g., "header:from" and "from") within the Email or particular EmailBodyPart. o Header fields MUST NOT be specified in parsed forms that are forbidden for that particular field. o Header fields beginning with "Content-" MUST NOT be specified on the Email object, only on EmailBodyPart objects.
Top   ToC   RFC8621 - Page 52
   o  If a "bodyStructure" property is given, there MUST NOT be
      "textBody", "htmlBody", or "attachments" properties.

   o  If given, the "bodyStructure" EmailBodyPart MUST NOT contain a
      property representing a header field that is already defined on
      the top-level Email object.

   o  If given, textBody MUST contain exactly one body part and it MUST
      be of type "text/plain".

   o  If given, htmlBody MUST contain exactly one body part and it MUST
      be of type "text/html".

   o  Within an EmailBodyPart:

      *  The client may specify a partId OR a blobId, but not both.  If
         a partId is given, this partId MUST be present in the
         "bodyValues" property.

      *  The "charset" property MUST be omitted if a partId is given
         (the part's content is included in bodyValues, and the server
         may choose any appropriate encoding).

      *  The "size" property MUST be omitted if a partId is given.  If a
         blobId is given, it may be included but is ignored by the
         server (the size is actually calculated from the blob content
         itself).

      *  A Content-Transfer-Encoding header field MUST NOT be given.

   o  Within an EmailBodyValue object, isEncodingProblem and isTruncated
      MUST be either false or omitted.

   Creation attempts that violate any of this SHOULD be rejected with an
   "invalidProperties" error; however, a server MAY choose to modify the
   Email (e.g., choose between conflicting headers, use a different
   content-encoding, etc.) to comply with its requirements instead.

   The server MAY also choose to set additional headers.  If not
   included, the server MUST generate and set a Message-ID header field
   in conformance with [RFC5322], Section 3.6.4 and a Date header field
   in conformance with Section 3.6.1.

   The final message generated may be invalid per RFC 5322.  For
   example, if it is a half-finished draft, the To header field may have
   a value that does not conform to the required syntax for this header.
   The message will be checked for strict conformance when submitted for
   sending (see the EmailSubmission object description).
Top   ToC   RFC8621 - Page 53
   Destroying an Email removes it from all Mailboxes to which it
   belonged.  To just delete an Email to trash, simply change the
   "mailboxIds" property, so it is now in the Mailbox with a "role"
   property equal to "trash", and remove all other Mailbox ids.

   When emptying the trash, clients SHOULD NOT destroy Emails that are
   also in a Mailbox other than trash.  For those Emails, they SHOULD
   just remove the trash Mailbox from the Email.

   For successfully created Email objects, the "created" response
   contains the "id", "blobId", "threadId", and "size" properties of the
   object.

   The following extra SetError types are defined:

   For "create":

   o  "blobNotFound": At least one blob id given for an EmailBodyPart
      doesn't exist.  An extra "notFound" property of type "Id[]" MUST
      be included in the SetError object containing every "blobId"
      referenced by an EmailBodyPart that could not be found on the
      server.

   For "create" and "update":

   o  "tooManyKeywords": The change to the Email's keywords would exceed
      a server-defined maximum.

   o  "tooManyMailboxes": The change to the set of Mailboxes that this
      Email is in would exceed a server-defined maximum.

4.7. Email/copy

This is a standard "/copy" method as described in [RFC8620], Section 5.4, except only the "mailboxIds", "keywords", and "receivedAt" properties may be set during the copy. This method cannot modify the message represented by the Email. The server MAY forbid two Email objects with identical message content [RFC5322], or even just with the same Message-ID [RFC5322], to coexist within an account; if the target account already has the Email, the copy will be rejected with a standard "alreadyExists" error. For successfully copied Email objects, the "created" response contains the "id", "blobId", "threadId", and "size" properties of the new object.
Top   ToC   RFC8621 - Page 54

4.8. Email/import

The "Email/import" method adds messages [RFC5322] to the set of Emails in an account. The server MUST support messages with Email Address Internationalization (EAI) headers [RFC6532]. The messages must first be uploaded as blobs using the standard upload mechanism. The method takes the following arguments: o accountId: "Id" The id of the account to use. o ifInState: "String|null" This is a state string as returned by the "Email/get" method. If supplied, the string must match the current state of the account referenced by the accountId; otherwise, the method will be aborted and a "stateMismatch" error returned. If null, any changes will be applied to the current state. o emails: "Id[EmailImport]" A map of creation id (client specified) to EmailImport objects. An *EmailImport* object has the following properties: o blobId: "Id" The id of the blob containing the raw message [RFC5322]. o mailboxIds: "Id[Boolean]" The ids of the Mailboxes to assign this Email to. At least one Mailbox MUST be given. o keywords: "String[Boolean]" (default: {}) The keywords to apply to the Email. o receivedAt: "UTCDate" (default: time of most recent Received header, or time of import on server if none) The "receivedAt" date to set on the Email. Each Email to import is considered an atomic unit that may succeed or fail individually. Importing successfully creates a new Email object from the data referenced by the blobId and applies the given Mailboxes, keywords, and receivedAt date.
Top   ToC   RFC8621 - Page 55
   The server MAY forbid two Email objects with the same exact content
   [RFC5322], or even just with the same Message-ID [RFC5322], to
   coexist within an account.  In this case, it MUST reject attempts to
   import an Email considered to be a duplicate with an "alreadyExists"
   SetError.  An "existingId" property of type "Id" MUST be included on
   the SetError object with the id of the existing Email.  If duplicates
   are allowed, the newly created Email object MUST have a separate id
   and independent mutable properties to the existing object.

   If the "blobId", "mailboxIds", or "keywords" properties are invalid
   (e.g., missing, wrong type, id not found), the server MUST reject the
   import with an "invalidProperties" SetError.

   If the Email cannot be imported because it would take the account
   over quota, the import should be rejected with an "overQuota"
   SetError.

   If the blob referenced is not a valid message [RFC5322], the server
   MAY modify the message to fix errors (such as removing NUL octets or
   fixing invalid headers).  If it does this, the "blobId" on the
   response MUST represent the new representation and therefore be
   different to the "blobId" on the EmailImport object.  Alternatively,
   the server MAY reject the import with an "invalidEmail" SetError.

   The response has the following arguments:

   o  accountId: "Id"

      The id of the account used for this call.

   o  oldState: "String|null"

      The state string that would have been returned by "Email/get" on
      this account before making the requested changes, or null if the
      server doesn't know what the previous state string was.

   o  newState: "String"

      The state string that will now be returned by "Email/get" on this
      account.

   o  created: "Id[Email]|null"

      A map of the creation id to an object containing the "id",
      "blobId", "threadId", and "size" properties for each successfully
      imported Email, or null if none.
Top   ToC   RFC8621 - Page 56
   o  notCreated: "Id[SetError]|null"

      A map of the creation id to a SetError object for each Email that
      failed to be created, or null if all successful.  The possible
      errors are defined above.

   The following additional errors may be returned instead of the
   "Email/import" response:

   "stateMismatch": An "ifInState" argument was supplied, and it does
   not match the current state.

4.9. Email/parse

This method allows you to parse blobs as messages [RFC5322] to get Email objects. The server MUST support messages with EAI headers [RFC6532]. This can be used to parse and display attached messages without having to import them as top-level Email objects in the mail store in their own right. The following metadata properties on the Email objects will be null if requested: o id o mailboxIds o keywords o receivedAt The "threadId" property of the Email MAY be present if the server can calculate which Thread the Email would be assigned to were it to be imported. Otherwise, this too is null if fetched. The "Email/parse" method takes the following arguments: o accountId: "Id" The id of the account to use. o blobIds: "Id[]" The ids of the blobs to parse.
Top   ToC   RFC8621 - Page 57
   o  properties: "String[]"

      If supplied, only the properties listed in the array are returned
      for each Email object.  If omitted, defaults to:

      [ "messageId", "inReplyTo", "references", "sender", "from", "to",
      "cc", "bcc", "replyTo", "subject", "sentAt", "hasAttachment",
      "preview", "bodyValues", "textBody", "htmlBody", "attachments" ]

   o  bodyProperties: "String[]"

      A list of properties to fetch for each EmailBodyPart returned.  If
      omitted, defaults to the same value as the "Email/get"
      "bodyProperties" default argument.

   o  fetchTextBodyValues: "Boolean" (default: false)

      If true, the "bodyValues" property includes any "text/*" part in
      the "textBody" property.

   o  fetchHTMLBodyValues: "Boolean" (default: false)

      If true, the "bodyValues" property includes any "text/*" part in
      the "htmlBody" property.

   o  fetchAllBodyValues: "Boolean" (default: false)

      If true, the "bodyValues" property includes any "text/*" part in
      the "bodyStructure" property.

   o  maxBodyValueBytes: "UnsignedInt" (default: 0)

      If greater than zero, the "value" property of any EmailBodyValue
      object returned in "bodyValues" MUST be truncated if necessary so
      it does not exceed this number of octets in size.  If 0 (the
      default), no truncation occurs.

      The server MUST ensure the truncation results in valid UTF-8 and
      does not occur mid-codepoint.  If the part is of type "text/html",
      the server SHOULD NOT truncate inside an HTML tag, e.g., in the
      middle of "<a href="https://example.com">".  There is no
      requirement for the truncated form to be a balanced tree or valid
      HTML (indeed, the original source may well be neither of these
      things).
Top   ToC   RFC8621 - Page 58
   The response has the following arguments:

   o  accountId: "Id"

      The id of the account used for the call.

   o  parsed: "Id[Email]|null"

      A map of blob id to parsed Email representation for each
      successfully parsed blob, or null if none.

   o  notParsable: "Id[]|null"

      A list of ids given that corresponded to blobs that could not be
      parsed as Emails, or null if none.

   o  notFound: "Id[]|null"

      A list of blob ids given that could not be found, or null if none.

   As specified above, parsed forms of headers may only be used on
   appropriate header fields.  Attempting to fetch a form that is
   forbidden (e.g., "header:From:asDate") MUST result in the method call
   being rejected with an "invalidArguments" error.

   Where a specific header field is requested as a property, the
   capitalization of the property name in the response MUST be identical
   to that used in the request.

4.10. Examples

A client logs in for the first time. It first fetches the set of Mailboxes. Now it will display the inbox to the user, which we will presume has Mailbox id "fb666a55". The inbox may be (very!) large, but the user's screen is only so big, so the client can just load the Threads it needs to fill the screen and then load in more only when the user scrolls. The client sends this request: [[ "Email/query",{ "accountId": "ue150411c", "filter": { "inMailbox": "fb666a55" }, "sort": [{ "isAscending": false, "property": "receivedAt" }], "collapseThreads": true,
Top   ToC   RFC8621 - Page 59
                        "position": 0,
                        "limit": 30,
                        "calculateTotal": true
                      }, "0" ],
                      [ "Email/get", {
                        "accountId": "ue150411c",
                        "#ids": {
                          "resultOf": "0",
                          "name": "Email/query",
                          "path": "/ids"
                        },
                        "properties": [
                          "threadId"
                        ]
                      }, "1" ],
                      [ "Thread/get", {
                        "accountId": "ue150411c",
                        "#ids": {
                          "resultOf": "1",
                          "name": "Email/get",
                          "path": "/list/*/threadId"
                        }
                      }, "2" ],
                      [ "Email/get", {
                        "accountId": "ue150411c",
                        "#ids": {
                          "resultOf": "2",
                          "name": "Thread/get",
                          "path": "/list/*/emailIds"
                        },
                        "properties": [
                          "threadId",
                          "mailboxIds",
                          "keywords",
                          "hasAttachment",
                          "from",
                          "subject",
                          "receivedAt",
                          "size",
                          "preview"
                        ]
                      }, "3" ]]
Top   ToC   RFC8621 - Page 60
   Let's break down the 4 method calls to see what they're doing:

   "0": This asks the server for the ids of the first 30 Email objects
   in the inbox, sorted newest first, ignoring Emails from the same
   Thread as a newer Email in the Mailbox (i.e., it is the first 30
   unique Threads).

   "1": Now we use a back-reference to fetch the Thread ids for each of
   these Email ids.

   "2": Another back-reference fetches the Thread object for each of
   these Thread ids.

   "3": Finally, we fetch the information we need to display the Mailbox
   listing (but no more!) for every Email in each of these 30 Threads.
   The client may aggregate this data for display, for example, by
   showing the Thread as "flagged" if any of the Emails in it has the
   "$flagged" keyword.

   The response from the server may look something like this:

    [[ "Email/query", {
      "accountId": "ue150411c",
      "queryState": "09aa9a075588-780599:0",
      "canCalculateChanges": true,
      "position": 0,
      "total": 115,
      "ids": [ "Ma783e5cdf5f2deffbc97930a",
        "M9bd17497e2a99cb345fc1d0a", ... ]
    }, "0" ],
    [ "Email/get", {
      "accountId": "ue150411c",
      "state": "780599",
      "list": [{
        "id": "Ma783e5cdf5f2deffbc97930a",
        "threadId": "T36703c2cfe9bd5ed"
      }, {
        "id": "M9bd17497e2a99cb345fc1d0a",
        "threadId": "T0a22ad76e9c097a1"
      }, ... ],
      "notFound": []
    }, "1" ],
    [ "Thread/get", {
      "accountId": "ue150411c",
      "state": "22a8728b",
      "list": [{
        "id": "T36703c2cfe9bd5ed",
        "emailIds": [ "Ma783e5cdf5f2deffbc97930a" ]
Top   ToC   RFC8621 - Page 61
      }, {
        "id": "T0a22ad76e9c097a1",
        "emailIds": [ "M3b568670a63e5d100f518fa5",
          "M9bd17497e2a99cb345fc1d0a" ]
      },  ... ],
      "notFound": []
    }, "2" ],
    [ "Email/get", {
      "accountId": "ue150411c",
      "state": "780599",
      "list": [{
        "id": "Ma783e5cdf5f2deffbc97930a",
        "threadId": "T36703c2cfe9bd5ed",
        "mailboxIds": {
          "fb666a55": true
        },
        "keywords": {
          "$seen": true,
          "$flagged": true
        },
        "hasAttachment": true,
        "from": [{
          "email": "jdoe@example.com",
          "name": "Jane Doe"
        }],
        "subject": "The Big Reveal",
        "receivedAt": "2018-06-27T00:20:35Z",
        "size": 175047,
        "preview": "As you may be aware, we are required to prepare a
          presentation where we wow a panel of 5 random members of the
          public, on or before 30 June each year.  We have drafted..."
      },
      ...
      ],
      "notFound": []
    }, "3" ]]
Top   ToC   RFC8621 - Page 62
   Now, on another device, the user marks the first Email as unread,
   sending this API request:

                    [[ "Email/set", {
                      "accountId": "ue150411c",
                      "update": {
                        "Ma783e5cdf5f2deffbc97930a": {
                          "keywords/$seen": null
                        }
                      }
                    }, "0" ]]

   The server applies this and sends the success response:

                   [[ "Email/set", {
                     "accountId": "ue150411c",
                     "oldState": "780605",
                     "newState": "780606",
                     "updated": {
                       "Ma783e5cdf5f2deffbc97930a": null
                     },
                     ...
                   }, "0" ]]

   The user also deletes a few Emails, and then a new message arrives.
Top   ToC   RFC8621 - Page 63
   Back on our original machine, we receive a push update that the state
   string for Email is now "780800".  As this does not match the
   client's current state, it issues a request for the changes:

               [[ "Email/changes", {
                 "accountId": "ue150411c",
                 "sinceState": "780605",
                 "maxChanges": 50
               }, "3" ],
               [ "Email/queryChanges", {
                 "accountId": "ue150411c",
                 "filter": {
                   "inMailbox": "fb666a55"
                 },
                 "sort": [{
                   "property": "receivedAt",
                   "isAscending": false
                 }],
                 "collapseThreads": true,
                 "sinceQueryState": "09aa9a075588-780599:0",
                 "upToId": "Mc2781d5e856a908d8a35a564",
                 "maxChanges": 25,
                 "calculateTotal": true
               }, "11" ]]

   The response:

            [[ "Email/changes", {
              "accountId": "ue150411c",
              "oldState": "780605",
              "newState": "780800",
              "hasMoreChanges": false,
              "created": [ "Me8de6c9f6de198239b982ea2" ],
              "updated": [ "Ma783e5cdf5f2deffbc97930a" ],
              "destroyed": [ "M9bd17497e2a99cb345fc1d0a", ... ]
            }, "3" ],
            [ "Email/queryChanges", {
              "accountId": "ue150411c",
              "oldQueryState": "09aa9a075588-780599:0",
              "newQueryState": "e35e9facf117-780615:0",
              "added": [{
                "id": "Me8de6c9f6de198239b982ea2",
                "index": 0
              }],
              "removed": [ "M9bd17497e2a99cb345fc1d0a" ],
              "total": 115
            }, "11" ]]
Top   ToC   RFC8621 - Page 64
   The client can update its local cache of the query results by
   removing "M9bd17497e2a99cb345fc1d0a" and then splicing in
   "Me8de6c9f6de198239b982ea2" at position 0.  As it does not have the
   data for this new Email, it will then fetch it (it also could have
   done this in the same request using back-references).

   It knows something has changed about "Ma783e5cdf5f2deffbc97930a", so
   it will refetch the Mailbox ids and keywords (the only mutable
   properties) for this Email too.

   The user starts composing a new Email.  The email is plaintext and
   the client knows the email in English so adds this metadata to the
   body part.  The user saves a draft while the composition is still in
   progress.  The client sends:

     [[ "Email/set", {
       "accountId": "ue150411c",
       "create": {
         "k192": {
           "mailboxIds": {
             "2ea1ca41b38e": true
           },
           "keywords": {
             "$seen": true,
             "$draft": true
           },
           "from": [{
             "name": "Joe Bloggs",
             "email": "joe@example.com"
           }],
           "subject": "World domination",
           "receivedAt": "2018-07-10T01:03:11Z",
           "sentAt": "2018-07-10T11:03:11+10:00",
           "bodyStructure": {
             "type": "text/plain",
             "partId": "bd48",
             "header:Content-Language": "en"
           },
           "bodyValues": {
             "bd48": {
               "value": "I have the most brilliant plan.  Let me tell
                 you all about it.  What we do is, we",
               "isTruncated": false
             }
           }
         }
       }
     }, "0" ]]
Top   ToC   RFC8621 - Page 65
   The server creates the message and sends the success response:

       [[ "Email/set", {
         "accountId": "ue150411c",
         "oldState": "780823",
         "newState": "780839",
         "created": {
           "k192": {
             "id": "Mf40b5f831efa7233b9eb1c7f",
             "blobId": "Gf40b5f831efa7233b9eb1c7f8f97d84eeeee64f7",
             "threadId": "Td957e72e89f516dc",
             "size": 359
           }
         },
         ...
       }, "0" ]]

   The message created on the server looks something like this:

 Message-Id: <bbce0ae9-58be-4b24-ac82-deb840d58016@sloti7d1t02>
 User-Agent: Cyrus-JMAP/3.1.6-736-gdfb8e44
 Mime-Version: 1.0
 Date: Tue, 10 Jul 2018 11:03:11 +1000
 From: "Joe Bloggs" <joe@example.com>
 Subject: World domination
 Content-Language: en
 Content-Type: text/plain

 I have the most brilliant plan.  Let me tell you all about it.  What we
 do is, we

   The user adds a recipient and converts the message to HTML so they
   can add formatting, then saves an updated draft:

 [[ "Email/set", {
   "accountId": "ue150411c",
   "create": {
     "k1546": {
       "mailboxIds": {
         "2ea1ca41b38e": true
       },
       "keywords": {
         "$seen": true,
         "$draft": true
       },
       "from": [{
         "name": "Joe Bloggs",
         "email": "joe@example.com"
Top   ToC   RFC8621 - Page 66
       }],
       "to": [{
         "name": "John",
         "email": "john@example.com"
       }],
       "subject": "World domination",
       "receivedAt": "2018-07-10T01:05:08Z",
       "sentAt": "2018-07-10T11:05:08+10:00",
       "bodyStructure": {
         "type": "multipart/alternative",
         "subParts": [{
           "partId": "a49d",
           "type": "text/html",
           "header:Content-Language": "en"
         }, {
           "partId": "bd48",
           "type": "text/plain",
           "header:Content-Language": "en"
         }]
       },
       "bodyValues": {
         "bd48": {
           "value": "I have the most brilliant plan.  Let me tell
             you all about it.  What we do is, we",
           "isTruncated": false
         },
         "a49d": {
           "value": "<!DOCTYPE html><html><head><title></title>
             <style type=\"text/css\">div{font-size:16px}</style></head>
             <body><div>I have the most <b>brilliant</b> plan.  Let me
             tell you all about it.  What we do is, we</div></body>
             </html>",
           "isTruncated": false
         }
       }
     }
   },
   "destroy": [ "Mf40b5f831efa7233b9eb1c7f" ]
 }, "0" ]]
Top   ToC   RFC8621 - Page 67
   The server creates the new draft, deletes the old one, and sends the
   success response:

       [[ "Email/set", {
         "accountId": "ue150411c",
         "oldState": "780839",
         "newState": "780842",
         "created": {
           "k1546": {
             "id": "Md45b47b4877521042cec0938",
             "blobId": "Ge8de6c9f6de198239b982ea214e0f3a704e4af74",
             "threadId": "Td957e72e89f516dc",
             "size": 11721
           }
         },
         "destroyed": [ "Mf40b5f831efa7233b9eb1c7f" ],
         ...
       }, "0" ]]

   The client moves this draft to a different account.  The only way to
   do this is via the "Email/copy" method.  It MUST set a new
   "mailboxIds" property, since the current value will not be valid
   Mailbox ids in the destination account:

                 [[ "Email/copy", {
                   "fromAccountId": "ue150411c",
                   "accountId": "u6c6c41ac",
                   "create": {
                     "k45": {
                       "id": "Md45b47b4877521042cec0938",
                       "mailboxIds": {
                         "75a4c956": true
                       }
                     }
                   },
                   "onSuccessDestroyOriginal": true
                 }, "0" ]]
Top   ToC   RFC8621 - Page 68
   The server successfully copies the Email and deletes the original.
   Due to the implicit call to "Email/set", there are two responses to
   the single method call, both with the same method call id:

       [[ "Email/copy", {
         "fromAccountId": "ue150411c",
         "accountId": "u6c6c41ac",
         "oldState": "7ee7e9263a6d",
         "newState": "5a0d2447ed26",
         "created": {
           "k45": {
             "id": "M138f9954a5cd2423daeafa55",
             "blobId": "G6b9fb047cba722c48c611e79233d057c6b0b74e8",
             "threadId": "T2f242ea424a4079a",
             "size": 11721
           }
         },
         "notCreated": null
       }, "0" ],
       [ "Email/set", {
         "accountId": "ue150411c",
         "oldState": "780842",
         "newState": "780871",
         "destroyed": [ "Md45b47b4877521042cec0938" ],
         ...
       }, "0" ]]



(page 68 continued on part 4)

Next Section