RFC 9007

Handling Message Disposition Notification with the JSON Meta Application Protocol (JMAP)

๐Ÿ“… published March 2021, from ietf

Handling Message Disposition Notification with the JSON Meta Application Protocol (JMAP) ๐Ÿ’Œ

Okay bestie, this is RFC 9007 ๐Ÿ“ฌ Its number is 9007 and it's a Standards Track document, so it's an actual Internet Standard and not just a suggestion ๐Ÿ’… It was written by R. Ouazana, Ed. from Linagora and published in March 2021 ๐ŸŒธ It's listed under ISSN 2070-1721. The header doesn't list any RFC it obsoletes or updates, so it stands on its own โœจ

Abstract โœจ

This doc gives you a data model for handling Message Disposition Notifications (MDNs) (see RFC 8098) in the JSON Meta Application Protocol (JMAP) (see RFCs 8620 and 8621). MDNs are the "read receipts" we all have feelings about ๐Ÿ™ˆ

Status of This Memo

This is an Internet Standards Track document ๐Ÿฆ‹

It's a product of the Internet Engineering Task Force (IETF) and represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG). For more on Internet Standards, check out Section 2 of RFC 7841 ๐Ÿงš

For the current status of this document, any errata, and how to give feedback on it, go to https://www.rfc-editor.org/info/rfc9007 ๐Ÿ’œ

Copyright (c) 2021 IETF Trust and the persons identified as the document authors. All rights reserved ๐ŸŽ€

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as they stand on the date of publication. Please read these carefully, because they describe your rights and restrictions for this document ๐Ÿซถ Any Code Components pulled out of this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as described in the Simplified BSD License โš ๏ธ

Table of Contents

1.  Introduction
  1.1.  Notational Conventions
  1.2.  Terminology
  1.3.  Addition to the Capabilities Object
2.  MDN
  2.1.  MDN/send
  2.2.  MDN/parse
3.  Samples
  3.1.  Sending an MDN for a Received Email Message
  3.2.  Asking for an MDN When Sending an Email Message
  3.3.  Parsing a Received MDN
4.  IANA Considerations
  4.1.  JMAP Capability Registration for "mdn"
  4.2.  JMAP Error Codes Registration for "mdnAlreadySent"
5.  Security Considerations
6.  Normative References
Author's Address

1. Introduction ๐ŸŒˆ

Okay so JMAP (that's "The JSON Meta Application Protocol (JMAP)" [RFC8620]) is a generic protocol for syncing data, like mail, calendars, or contacts, between a client and a server. It's optimised for mobile and web, and it gives you one consistent interface across all the different data types ๐Ÿ’–

JMAP for Mail ("The JSON Meta Application Protocol (JMAP) for Mail" [RFC8621]) is the bit that defines a data model for syncing email with a server using JMAP. Clients use it to search, access, organise, and send messages, and it's honestly so efficient ๐Ÿ”ฅ

Message Disposition Notifications (MDNs) are defined in [RFC8098]. They're the things people call "read receipts", "acknowledgements", or "receipt notifications" ๐Ÿ‘€

A client can run into MDNs in three different ways:

  1. When an email message comes in, an MDN can be sent back to the sender. This spec defines an MDN/send method to cover that case ๐Ÿ“ค
  2. When an email message is being sent, an MDN can be requested. This must be done with a header field, as already specified by [RFC8098]. The header field is already handled by the guidance in [RFC8621] ๐Ÿ“
  3. When an MDN comes in, it could be related to a sent message that already exists. That's already covered by [RFC8621] in the EmailSubmission object. A client might want to show detailed info about a received MDN, so this spec defines an MDN/parse method for that ๐Ÿ”

1.1. Notational Conventions

Okay this part is the rulebook for the capital letter words ๐Ÿง The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, just like they do here. Lowercase versions are just regular words, no cap ๐Ÿ’…

Type signatures, examples, and property descriptions in this document follow the conventions set out in Section 1.1 of [RFC8620]. The data types defined in the core specification are also used here, so you'll see them everywhere ๐ŸŽ€

Servers MUST support all properties specified for the new data types defined in this document. No skipping, bestie ๐Ÿ‘‘

1.2. Terminology

The same terminology is used in this document as in the core JMAP specification, so you don't have to learn a whole new vocab ๐Ÿฆ‹

Here's a fun one: keywords are case insensitive in IMAP but case sensitive in JMAP. So the $mdnsent keyword MUST always be used in lowercase. Lowercase only, periodt ๐Ÿ’œ

1.3. Addition to the Capabilities Object

Capabilities get announced as part of the standard JMAP Session resource, see [RFC8620], Section 2 ๐ŸŒธ This defines a new capability called urn:ietf:params:jmap:mdn โœจ

If urn:ietf:params:jmap:mdn is present in the accountCapabilities property of an account, it means the server supports the MDN data type, parsing MDNs with the MDN/parse method, and creating and sending MDN messages with the MDN/send method. Servers that include the capability in one or more accountCapabilities properties MUST also include the property in the capabilities property. That's the rule, no exceptions ๐Ÿซถ

The value of the urn:ietf:params:jmap:mdn property is an empty object, both in the account's accountCapabilities property and in the capabilities property. Empty object, full vibes ๐Ÿงš


2. MDN ๐Ÿ“ฌ

An MDN object has the following properties. Let's go through them ๐Ÿ’–

  • forEmailId: "Id|null"

The Email id of the received message that this MDN is related to. This property MUST NOT be null for MDN/send, but it MAY be null in the response from the MDN/parse method ๐Ÿง

  • subject: "String|null"

The subject that goes in the Subject header field for this MDN ๐Ÿ’Œ

  • textBody: "String|null"

The human-readable part of the MDN, as plain text ๐Ÿ“–

  • includeOriginalMessage: "Boolean" (default: false)

If true, the content of the original message will show up in the third component of the multipart/report generated for the MDN. See [RFC8098] for the details and the security considerations ๐Ÿ”

  • reportingUA: "String|null"

The name of the Mail User Agent (MUA) creating this MDN. It's used to build the MDN report part of the MDN. Fun fact: a "null" value may have better privacy properties ๐Ÿ™ˆ

  • disposition: "Disposition"

The object holding all the different MDN disposition options ๐ŸŽ€

  • mdnGateway: "String|null" (server-set)

The name of the gateway or Message Transfer Agent (MTA) that translated a foreign (non-Internet) message disposition notification into this MDN ๐ŸŒ‰

  • originalRecipient: "String|null" (server-set)

The original recipient address, as specified by the sender of the message the MDN is being issued for ๐Ÿ“ฎ

  • finalRecipient: "String|null"

The recipient the MDN is being issued for. If it's set, it overrides the value the server would calculate from the Identity defined in the MDN/send method, unless the client explicitly sets it. Main character override ๐Ÿ‘‘

  • originalMessageId: "String|null" (server-set)

The Message-ID header field [RFC5322] (not the JMAP id) of the message the MDN is being issued for ๐Ÿชช

  • error: "String[]|null" (server-set)

Extra info in the form of text messages when the error disposition modifier shows up ๐Ÿšจ

  • extensionFields: "String[String]|null"

The object where the keys are extension-field names and the values are extension-field values (see [RFC8098], Section 3.3) ๐Ÿงฉ

A Disposition object has the following properties:

  • actionMode: "String"

This MUST be one of the following strings: "manual-action" / "automatic-action" ๐ŸŽฏ

  • sendingMode: "String"

This MUST be one of the following strings: "mdn-sent-manually" / "mdn-sent-automatically" ๐Ÿš€

  • type: "String"

This MUST be one of the following strings: "deleted" / "dispatched" / "displayed" / "processed" ๐Ÿ’…

Heads up: see [RFC8098] for the exact meaning of these fields. They're case insensitive in [RFC8098], but in this RFC they're case sensitive, so MDN/parse MUST convert them to lowercase. Don't skip this step, it's literally the whole point ๐Ÿ˜ญ

๐ŸŽ€ To be continued in part 2, bestie!! ๐ŸŽ€

2.1. MDN/send โœจ

okay so the "MDN/send" method is how you send a message in the style of [RFC5322] straight from an MDN object ๐Ÿ’Œ when you call it, the "using" property of the Request object MUST contain the capabilities "urn:ietf:params:jmap:mdn" and "urn:ietf:params:jmap:mail". the second one is needed bc of the implicit call to "Email/set" and the Identity objects (more on those below) ๐Ÿงš it takes these arguments:

  • accountId: "Id"
    • the id of the account to use ๐Ÿ 
  • identityId: "Id"
    • the id of the Identity to associate with these MDNs. the server uses this identity to define the sender of the MDNs and to set the "finalRecipient" field ๐Ÿ’…
  • send: "Id[MDN]"
    • a map of the creation id (client specified) to MDN objects ๐Ÿ“ฌ
  • onSuccessUpdateEmail: "Id[PatchObject]|null"
    • a map of the id to an object holding properties to update on the Email object referenced by the "MDN/send" if the sending succeeds. this will always be a backward reference to the creation id (see the example below in Section 3.1) ๐Ÿฆ‹

the response has these arguments:

  • accountId: "Id"
    • the id of the account used for the call ๐ŸŒธ
  • sent: "Id[MDN]|null"
    • a map of the creation id to an MDN containing any properties the client did NOT set. this includes properties the client left out, which means the server filled in a default for them โœจ this argument is null if no MDN objects were successfully sent ๐Ÿฅฒ
  • notSent: "Id[SetError]|null"
    • a map of the creation id to a SetError object for each record that failed to be sent, or null if everything went through ๐ŸŽ€

in this context, the existing SetError types defined in [RFC8620] and [RFC8621] get interpreted like this, bestie ๐Ÿ‘‘

  • notFound: the reference "forEmailId" cannot be found, or has no valid "Disposition-Notification-To" header field ๐Ÿ™ˆ
  • forbidden: "MDN/send" would violate an Access Control List (ACL) or other permissions policy ๐Ÿšซ
  • forbiddenFrom: the user is not allowed to use the given "finalRecipient" property ๐Ÿซฃ
  • overQuota: "MDN/send" would exceed a server-defined limit on the number or total size of sent MDNs. it could include limitations on sent messages ๐Ÿ“ฆ
  • tooLarge: "MDN/send" would result in an MDN that exceeds a server-defined limit for the maximum size of an MDN, or more generally, on email message ๐Ÿ˜
  • rateLimit: too many MDNs or email messages have been created recently, and a server-defined rate limit has been reached. it may work if tried again later โณ
  • invalidProperties: the record given is invalid in some way ๐Ÿ’€

and this one is NEW ๐ŸŒˆ

  • mdnAlreadySent: the message has the "$mdnsent" keyword already set ๐Ÿ˜ญ

if the "accountId" or "identityId" given cannot be found, the method call gets rejected with an "invalidArguments" error. bestie, that's not it ๐Ÿ™…โ€โ™€๏ธ

the client MUST NOT issue an "MDN/send" request if the message has the "$mdnsent" keyword set. no cap, don't do it ๐Ÿ›‘

when sending the MDN, the server is in charge of generating the "originalRecipient" and "originalMessageId" fields according to [RFC8098]. "finalRecipient" also usually gets generated by the server based on the provided identity, but if the client specifies it and it's allowed (see Section 5), the server uses the client-provided value ๐Ÿง

the client is expected to explicitly update each "Email" that had an "MDN/send" invoked on it, so the "$mdnsent" keyword gets set on these messages ๐Ÿ”– to make sure that happens, the server MUST reject an "MDN/send" that does not result in setting the keyword "$mdnsent". so, the server MUST check that the "onSuccessUpdateEmail" property of the method is correctly set to update this keyword. it's giving accountability ๐Ÿ’œ


2.2. MDN/parse ๐Ÿ”

this method lets a client parse blobs as messages in the style of [RFC5322] to get MDN objects ๐Ÿง  you can use it to parse and get detailed info about blobs referenced in the "mdnBlobIds" of the EmailSubmission object, or any email message the client expects to be an MDN ๐Ÿ’–

the "forEmailId" property can be null or missing if the "originalMessageId" property is missing, or doesn't refer to an existing message, or if the server can't efficiently calculate the related message (for example, if several messages get the same "Message-ID" header field) ๐ŸŒ€

the "MDN/parse" method takes these arguments:

  • accountId: "Id"
    • the id of the account to use ๐Ÿก
  • blobIds: "Id[]"
    • the ids of the blobs to parse ๐Ÿงฉ

the response has these arguments:

  • accountId: "Id"
    • the id of the account used for the call ๐ŸŒท
  • parsed: "Id[MDN]|null"
    • a map of the blob id to a parsed MDN representation for each successfully parsed blob, or null if none ๐ŸŽ
  • notParsable: "Id[]|null"
    • a list of ids given that corresponds to blobs that could not be parsed as MDNs, or null if none ๐Ÿซ 
  • notFound: "Id[]|null"
    • a list of blob ids given that could not be found, or null if none ๐Ÿ•ต๏ธโ€โ™€๏ธ

the following additional errors may come back instead of the "MDN/parse" response ๐Ÿฅบ

  • requestTooLarge: the number of ids the client requested is more than the maximum number the server is willing to process in a single method call ๐Ÿ‹๏ธโ€โ™€๏ธ
  • invalidArguments: if the given "accountId" cannot be found, the MDN parsing gets rejected with an "invalidArguments" error ๐Ÿ™ƒ

3. Samples ๐ŸŽ€

3.1. Sending an MDN for a Received Email Message ๐Ÿ’Œ

a client can use this request to send an MDN back to the sender ๐Ÿ“จ

   [[ "MDN/send", {
     "accountId": "ue150411c",
     "identityId": "I64588216",
     "send": {
       "k1546": {
         "forEmailId": "Md45b47b4877521042cec0938",
         "subject": "Read receipt for: World domination",
         "textBody": "This receipt shows that the email has been
             displayed on your recipient's computer.  There is no
             guarantee it has been read or understood.",
         "reportingUA": "joes-pc.cs.example.com; Foomail 97.1",
         "disposition": {
           "actionMode": "manual-action",
           "sendingMode": "mdn-sent-manually",
           "type": "displayed"
         },
         "extension": {
           "EXTENSION-EXAMPLE": "example.com"
         }
       }
     },
     "onSuccessUpdateEmail": {
       "#k1546": {
         "keywords/$mdnsent": true
       }
     }
   }, "0" ]]

if the email id matches an existing email message that doesn't have the "$mdnsent" keyword, the server can answer like this ๐ŸŒˆ

    [[ "MDN/send", {
      "accountId": "ue150411c",
      "sent": {
        "k1546": {
          "finalRecipient": "rfc822; john@example.com",
          "originalMessageId": "<199509192301.23456@example.org>"
        }
      }
    }, "0" ],
    [ "Email/set", {
      "accountId": "ue150411c",
      "oldState": "23",
      "newState": "42",
      "updated": {
        "Md45b47b4877521042cec0938": {}
      }
    }, "0" ]]

if the "$mdnsent" keyword has already been set, the server can answer with an error ๐Ÿ™…โ€โ™€๏ธ

     [[ "MDN/send", {
       "accountId": "ue150411c",
       "notSent": {
         "k1546": {
           "type": "mdnAlreadySent",
           "description" : "$mdnsent keyword is already present"
         }
       }
     }, "0" ]]

3.2. Asking for an MDN When Sending an Email Message โœ‰๏ธ

this is done with the "Email/set" "create" method of [RFC8621] ๐Ÿซถ

[[ "Email/set", {
  "accountId": "ue150411c",
  "create": {
    "k2657": {
      "mailboxIds": {
        "2ea1ca41b38e": true
      },
      "keywords": {
        "$seen": true,
        "$draft": true
      },
      "from": [{
        "name": "Joe Bloggs",
        "email": "joe@example.com"
      }],
      "to": [{
        "name": "John",
        "email": "john@example.com"
      }],
      "header:Disposition-Notification-To:asText": "joe@example.com",
      "subject": "World domination",
      ...
    }
  }
}, "0" ]]

note the specified "Disposition-Notification-To" header field, which says where to send the MDN (usually the sender of the message) ๐Ÿฆ‹

3.3. Parsing a Received MDN ๐Ÿงš

the client issues a parse request ๐Ÿ“ค

      [[ "MDN/parse", {
        "accountId": "ue150411c",
        "blobIds": [ "0f9f65ab-dc7b-4146-850f-6e4881093965" ]
      }, "0" ]]

the server responds ๐Ÿ“ฅ

  [[ "MDN/parse", {
    "accountId": "ue150411c",
    "parsed": {
      "0f9f65ab-dc7b-4146-850f-6e4881093965": {
        "forEmailId": "Md45b47b4877521042cec0938",
        "subject": "Read receipt for: World domination",
        "textBody": "This receipt shows that the email has been
            displayed on your recipient's computer.  There is no
            guarantee it has been read or understood.",
        "reportingUA": "joes-pc.cs.example.com; Foomail 97.1",
        "disposition": {
          "actionMode": "manual-action",
          "sendingMode": "mdn-sent-manually",
          "type": "displayed"
        },
        "finalRecipient": "rfc822; john@example.com",
        "originalMessageId": "<199509192301.23456@example.org>"
      }
    }
  }, "0" ]]

if a blob id isn't found, the server would respond like this ๐Ÿ˜ฌ

     [[ "MDN/parse", {
       "accountId": "ue150411c",
       "notFound": [ "0f9f65ab-dc7b-4146-850f-6e4881093965" ]
     }, "0" ]]

if the blob id has been found but isn't parsable, the server would respond like this ๐Ÿซ 

    [[ "MDN/parse", {
      "accountId": "ue150411c",
      "notParsable": [ "0f9f65ab-dc7b-4146-850f-6e4881093965" ]
    }, "0" ]]

4. IANA Considerations ๐Ÿ’…

4.1. JMAP Capability Registration for "mdn" ๐ŸŒธ

this section registers the "mdn" JMAP Capability in the "JMAP Capabilities" registry, like so ๐Ÿ“

   Capability Name:  "urn:ietf:params:jmap:mdn"
   Intended Use:  common
   Change Controller:  IETF
   Security and Privacy Considerations:  This document, Section 5.
   Reference:  This document

4.2. JMAP Error Codes Registration for "mdnAlreadySent" ๐ŸŽ€

IANA has registered one new error code in the "JMAP Error Codes" registry, as defined in [RFC8620] โœจ

   JMAP Error Code:  mdnAlreadySent
   Intended Use:  common
   Change Controller:  IETF
   Description:  The message has the "$mdnsent" keyword already set.
      The client MUST NOT try again to send an MDN for this message.
   Reference:  This document, Section 2.1

5. Security Considerations ๐Ÿ›ก๏ธ

the same considerations about MDN (see [RFC8098] and [RFC3503]) apply to this document ๐Ÿ”

to reinforce trust between the user sending an email message and the identity of that user, the server SHOULD validate, in conformance to the provided Identity, that the user is permitted to use the "finalRecipient" value, and return a "forbiddenFrom" error if not. this is a big deal, bestie, so don't skip it ๐Ÿ˜ญ๐Ÿ‘‘

6. Normative References โœจ

okay bestie, this is the reading list ๐Ÿ“š the spec leans on these docs, so you'd want them open in another tab too ๐Ÿค“


Author's Address ๐Ÿ’Œ

this is where you find the editor, Raphaรซl Ouazana, at Linagora ๐ŸŒธ

Raphaรซl Ouazana (editor)
Linagora
100 Terrasse Boieldieu - Tour Franklin
92042 Paris - La Dรฉfense CEDEX
France

Email: rouazana@linagora.com
URI:   https://www.linagora.com

and that's the whole spec, bestie ๐Ÿฆ‹โœจ go be a JMAP main character ๐Ÿ‘‘๐Ÿ’…

๐Ÿ“ฆ where this page comes from