draft-ietf-jmap-mail-11.txt   draft-ietf-jmap-mail-12.txt 
JMAP N. Jenkins JMAP N. Jenkins
Internet-Draft FastMail Internet-Draft FastMail
Updates: 5788 (if approved) C. Newman Updates: 5788 (if approved) C. Newman
Intended status: Standards Track Oracle Intended status: Standards Track Oracle
Expires: May 30, 2019 November 26, 2018 Expires: June 6, 2019 December 3, 2018
JMAP for Mail JMAP for Mail
draft-ietf-jmap-mail-11 draft-ietf-jmap-mail-12
Abstract Abstract
This document specifies a data model for synchronising email data This document specifies a data model for synchronising email data
with a server using JMAP. with a server using JMAP. Clients can use this to efficiently
search, access, organise and send messages, and get pushed
notifications for fast resynchronisation when new messages are
delivered or a change is made in another client.
Status of This Memo Status of This Memo
This Internet-Draft is submitted in full conformance with the This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79. provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet- working documents as Internet-Drafts. The list of current Internet-
Drafts is at https://datatracker.ietf.org/drafts/current/. Drafts is at https://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress." material or to cite them other than as "work in progress."
This Internet-Draft will expire on May 30, 2019. This Internet-Draft will expire on June 6, 2019.
Copyright Notice Copyright Notice
Copyright (c) 2018 IETF Trust and the persons identified as the Copyright (c) 2018 IETF Trust and the persons identified as the
document authors. All rights reserved. document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info) in effect on the date of (https://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents publication of this document. Please review these documents
skipping to change at page 2, line 47 skipping to change at page 2, line 49
4.4. Email/query . . . . . . . . . . . . . . . . . . . . . . . 38 4.4. Email/query . . . . . . . . . . . . . . . . . . . . . . . 38
4.4.1. Filtering . . . . . . . . . . . . . . . . . . . . . . 38 4.4.1. Filtering . . . . . . . . . . . . . . . . . . . . . . 38
4.4.2. Sorting . . . . . . . . . . . . . . . . . . . . . . . 40 4.4.2. Sorting . . . . . . . . . . . . . . . . . . . . . . . 40
4.4.3. Thread collapsing . . . . . . . . . . . . . . . . . . 42 4.4.3. Thread collapsing . . . . . . . . . . . . . . . . . . 42
4.5. Email/queryChanges . . . . . . . . . . . . . . . . . . . 42 4.5. Email/queryChanges . . . . . . . . . . . . . . . . . . . 42
4.6. Email/set . . . . . . . . . . . . . . . . . . . . . . . . 42 4.6. Email/set . . . . . . . . . . . . . . . . . . . . . . . . 42
4.7. Email/copy . . . . . . . . . . . . . . . . . . . . . . . 45 4.7. Email/copy . . . . . . . . . . . . . . . . . . . . . . . 45
4.8. Email/import . . . . . . . . . . . . . . . . . . . . . . 45 4.8. Email/import . . . . . . . . . . . . . . . . . . . . . . 45
4.9. Email/parse . . . . . . . . . . . . . . . . . . . . . . . 47 4.9. Email/parse . . . . . . . . . . . . . . . . . . . . . . . 47
4.10. Examples . . . . . . . . . . . . . . . . . . . . . . . . 49 4.10. Examples . . . . . . . . . . . . . . . . . . . . . . . . 49
5. Search snippets . . . . . . . . . . . . . . . . . . . . . . . 57 5. Search snippets . . . . . . . . . . . . . . . . . . . . . . . 56
5.1. SearchSnippet/get . . . . . . . . . . . . . . . . . . . . 58 5.1. SearchSnippet/get . . . . . . . . . . . . . . . . . . . . 57
5.2. Example . . . . . . . . . . . . . . . . . . . . . . . . . 59 5.2. Example . . . . . . . . . . . . . . . . . . . . . . . . . 58
6. Identities . . . . . . . . . . . . . . . . . . . . . . . . . 59 6. Identities . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.1. Identity/get . . . . . . . . . . . . . . . . . . . . . . 60 6.1. Identity/get . . . . . . . . . . . . . . . . . . . . . . 60
6.2. Identity/changes . . . . . . . . . . . . . . . . . . . . 61 6.2. Identity/changes . . . . . . . . . . . . . . . . . . . . 60
6.3. Identity/set . . . . . . . . . . . . . . . . . . . . . . 61 6.3. Identity/set . . . . . . . . . . . . . . . . . . . . . . 60
6.4. Example . . . . . . . . . . . . . . . . . . . . . . . . . 61 6.4. Example . . . . . . . . . . . . . . . . . . . . . . . . . 60
7. Email submission . . . . . . . . . . . . . . . . . . . . . . 62 7. Email submission . . . . . . . . . . . . . . . . . . . . . . 61
7.1. EmailSubmission/get . . . . . . . . . . . . . . . . . . . 67 7.1. EmailSubmission/get . . . . . . . . . . . . . . . . . . . 66
7.2. EmailSubmission/changes . . . . . . . . . . . . . . . . . 67 7.2. EmailSubmission/changes . . . . . . . . . . . . . . . . . 66
7.3. EmailSubmission/query . . . . . . . . . . . . . . . . . . 67 7.3. EmailSubmission/query . . . . . . . . . . . . . . . . . . 66
7.4. EmailSubmission/queryChanges . . . . . . . . . . . . . . 68 7.4. EmailSubmission/queryChanges . . . . . . . . . . . . . . 67
7.5. EmailSubmission/set . . . . . . . . . . . . . . . . . . . 68 7.5. EmailSubmission/set . . . . . . . . . . . . . . . . . . . 67
7.5.1. Example . . . . . . . . . . . . . . . . . . . . . . . 70 7.5.1. Example . . . . . . . . . . . . . . . . . . . . . . . 69
8. Vacation response . . . . . . . . . . . . . . . . . . . . . . 71 8. Vacation response . . . . . . . . . . . . . . . . . . . . . . 70
8.1. VacationResponse/get . . . . . . . . . . . . . . . . . . 72 8.1. VacationResponse/get . . . . . . . . . . . . . . . . . . 71
8.2. VacationResponse/set . . . . . . . . . . . . . . . . . . 72 8.2. VacationResponse/set . . . . . . . . . . . . . . . . . . 71
9. Security considerations . . . . . . . . . . . . . . . . . . . 73 9. Security considerations . . . . . . . . . . . . . . . . . . . 72
9.1. EmailBodyPart value . . . . . . . . . . . . . . . . . . . 73 9.1. EmailBodyPart value . . . . . . . . . . . . . . . . . . . 72
9.2. HTML email display . . . . . . . . . . . . . . . . . . . 73 9.2. HTML email display . . . . . . . . . . . . . . . . . . . 72
9.3. Email submission . . . . . . . . . . . . . . . . . . . . 75 9.3. Email submission . . . . . . . . . . . . . . . . . . . . 74
10. IANA considerations . . . . . . . . . . . . . . . . . . . . . 76 10. IANA considerations . . . . . . . . . . . . . . . . . . . . . 75
10.1. JMAP capability registration for "mail" . . . . . . . . 76 10.1. JMAP capability registration for "mail" . . . . . . . . 75
10.2. JMAP capability registration for "submission" . . . . . 76 10.2. JMAP capability registration for "submission" . . . . . 75
10.3. JMAP capability registration for "vacationresponse" . . 77 10.3. JMAP capability registration for "vacationresponse" . . 76
10.4. IMAP and JMAP keywords registry . . . . . . . . . . . . 77 10.4. IMAP and JMAP keywords registry . . . . . . . . . . . . 76
10.4.1. Registration of JMAP keyword '$draft' . . . . . . . 78 10.4.1. Registration of JMAP keyword '$draft' . . . . . . . 77
10.4.2. Registration of JMAP keyword '$seen' . . . . . . . . 79 10.4.2. Registration of JMAP keyword '$seen' . . . . . . . . 78
10.4.3. Registration of JMAP keyword '$flagged' . . . . . . 79 10.4.3. Registration of JMAP keyword '$flagged' . . . . . . 78
10.4.4. Registration of JMAP keyword '$answered' . . . . . . 80 10.4.4. Registration of JMAP keyword '$answered' . . . . . . 79
10.4.5. Registration of '$recent' keyword . . . . . . . . . 81 10.4.5. Registration of '$recent' keyword . . . . . . . . . 80
10.5. Registration of "inbox" role in . . . . . . . . . . . . 82 10.5. Registration of "inbox" role in . . . . . . . . . . . . 81
10.6. JMAP Error Codes registry . . . . . . . . . . . . . . . 82 10.6. JMAP Error Codes registry . . . . . . . . . . . . . . . 81
10.6.1. mailboxHasChild . . . . . . . . . . . . . . . . . . 82 10.6.1. mailboxHasChild . . . . . . . . . . . . . . . . . . 81
10.6.2. mailboxHasEmail . . . . . . . . . . . . . . . . . . 82 10.6.2. mailboxHasEmail . . . . . . . . . . . . . . . . . . 81
10.6.3. blobNotFound . . . . . . . . . . . . . . . . . . . . 82 10.6.3. blobNotFound . . . . . . . . . . . . . . . . . . . . 81
10.6.4. tooManyKeywords . . . . . . . . . . . . . . . . . . 83 10.6.4. tooManyKeywords . . . . . . . . . . . . . . . . . . 82
10.6.5. tooManyMailboxes . . . . . . . . . . . . . . . . . . 83 10.6.5. tooManyMailboxes . . . . . . . . . . . . . . . . . . 82
10.6.6. invalidEmail . . . . . . . . . . . . . . . . . . . . 83 10.6.6. invalidEmail . . . . . . . . . . . . . . . . . . . . 82
10.6.7. tooManyRecipients . . . . . . . . . . . . . . . . . 83 10.6.7. tooManyRecipients . . . . . . . . . . . . . . . . . 82
10.6.8. noRecipients . . . . . . . . . . . . . . . . . . . . 83 10.6.8. noRecipients . . . . . . . . . . . . . . . . . . . . 82
10.6.9. invalidRecipients . . . . . . . . . . . . . . . . . 84 10.6.9. invalidRecipients . . . . . . . . . . . . . . . . . 83
10.6.10. forbiddenMailFrom . . . . . . . . . . . . . . . . . 84 10.6.10. forbiddenMailFrom . . . . . . . . . . . . . . . . . 83
10.6.11. forbiddenFrom . . . . . . . . . . . . . . . . . . . 84 10.6.11. forbiddenFrom . . . . . . . . . . . . . . . . . . . 83
10.6.12. forbiddenToSend . . . . . . . . . . . . . . . . . . 84 10.6.12. forbiddenToSend . . . . . . . . . . . . . . . . . . 83
11. References . . . . . . . . . . . . . . . . . . . . . . . . . 84 11. References . . . . . . . . . . . . . . . . . . . . . . . . . 83
11.1. Normative References . . . . . . . . . . . . . . . . . . 85 11.1. Normative References . . . . . . . . . . . . . . . . . . 84
11.2. URIs . . . . . . . . . . . . . . . . . . . . . . . . . . 88 11.2. URIs . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 88 Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 87
1. Introduction 1. Introduction
JMAP (RFC XXXX) is a generic protocol for synchronising data, such as JMAP (RFC XXXX) is a generic protocol for synchronising data, such as
mail, calendars or contacts, between a client and a server. It is mail, calendars or contacts, between a client and a server. It is
optimised for mobile and web environments, and aims to provide a optimised for mobile and web environments, and aims to provide a
consistent interface to different data types. consistent interface to different data types.
This specification defines a data model for mail over JMAP. This specification defines a data model for mail over JMAP.
skipping to change at page 22, line 51 skipping to change at page 23, line 5
encoding MUST be decoded, following the same rules as for the _Text_ encoding MUST be decoded, following the same rules as for the _Text_
form. Any [RFC6532] UTF-8 values MUST be decoded. form. Any [RFC6532] UTF-8 values MUST be decoded.
Parsing SHOULD be best-effort in the face of invalid structure to Parsing SHOULD be best-effort in the face of invalid structure to
accommodate invalid messages and semi-complete drafts. EmailAddress accommodate invalid messages and semi-complete drafts. EmailAddress
objects MAY have an _email_ property that does not conform to the objects MAY have an _email_ property that does not conform to the
_addr-spec_ form (for example, may not contain an @ symbol). _addr-spec_ form (for example, may not contain an @ symbol).
For example, the following "address-list" string: For example, the following "address-list" string:
" James Smythe" <james@example.com>, Friends: jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>; " James Smythe" <james@example.com>, Friends:
jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?=
<john@example.com>;
would be parsed as: would be parsed as:
[ [
{ "name": "James Smythe", "email": "james@example.com" }, { "name": "James Smythe", "email": "james@example.com" },
{ "name": null, "email": "jane@example.com" }, { "name": null, "email": "jane@example.com" },
{ "name": "John Smith", "email": "john@example.com" } { "name": "John Smith", "email": "john@example.com" }
] ]
To prevent obviously nonsense behaviour, which can lead to To prevent obviously nonsense behaviour, which can lead to
interoperability issues, this form may only be fetched or set for the interoperability issues, this form may only be fetched or set for the
skipping to change at page 24, line 24 skipping to change at page 24, line 28
Any syntactically correct [RFC2047] encoded sections with a known Any syntactically correct [RFC2047] encoded sections with a known
encoding MUST be decoded, following the same rules as for the _Text_ encoding MUST be decoded, following the same rules as for the _Text_
form. Any [RFC6532] UTF-8 values MUST be decoded. form. Any [RFC6532] UTF-8 values MUST be decoded.
Parsing SHOULD be best-effort in the face of invalid structure to Parsing SHOULD be best-effort in the face of invalid structure to
accommodate invalid messages and semi-complete drafts. accommodate invalid messages and semi-complete drafts.
For example, the following "address-list" string: For example, the following "address-list" string:
" James Smythe" <james@example.com>, Friends: jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>; " James Smythe" <james@example.com>, Friends:
jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?=
<john@example.com>;
would be parsed as: would be parsed as:
[ [
{ "name": null, "addresses": [ { "name": null, "addresses": [
{ "name": "James Smythe", "email": "james@example.com" } { "name": "James Smythe", "email": "james@example.com" }
]}, ]},
{ "name": "Friends", "addresses": [ { "name": "Friends", "addresses": [
{ "name": null, "email": "jane@example.com" }, { "name": null, "email": "jane@example.com" },
{ "name": "John Smith", "email": "john@example.com" } { "name": "John Smith", "email": "john@example.com" }
skipping to change at page 31, line 24 skipping to change at page 31, line 33
o *hasAttachment*: "Boolean" (immutable; server-set) This is "true" o *hasAttachment*: "Boolean" (immutable; server-set) This is "true"
if there are one or more parts in the message that a client UI if there are one or more parts in the message that a client UI
should offer as downloadable. A server SHOULD set hasAttachment should offer as downloadable. A server SHOULD set hasAttachment
to "true" if the _attachments_ list contains at least one item to "true" if the _attachments_ list contains at least one item
that does not have "Content-Disposition: inline". The server MAY that does not have "Content-Disposition: inline". The server MAY
ignore parts in this list that are processed automatically in some ignore parts in this list that are processed automatically in some
way, or are referenced as embedded images in one of the "text/ way, or are referenced as embedded images in one of the "text/
html" parts of the message. The server MAY set hasAttachment html" parts of the message. The server MAY set hasAttachment
based on implementation-defined or site configurable heuristics. based on implementation-defined or site configurable heuristics.
o *preview*: "String" (immutable; server-set) Up to 255 octets of o *preview*: "String" (immutable; server-set) A plain text fragment
plain text, summarising the message body. This is intended to be of the message body. This is intended to be shown as a preview
shown as a preview line on a mailbox listing, and may be truncated line on a mailbox listing, and may be truncated when shown. The
when shown. The server may choose which part of the message to server may choose which part of the message to include in the
include in the preview; skipping quoted sections and salutations preview; skipping quoted sections and salutations and collapsing
and collapsing white-space can result in a more useful preview. white-space can result in a more useful preview. This MUST NOT be
As this is derived from the message content by the server, and the more than 256 characters in length. As this is derived from the
algorithm for doing so could change over time, fetching this for message content by the server, and the algorithm for doing so
an email a second time MAY return a different result. However, could change over time, fetching this for an email a second time
the previous value is not considered incorrect, and the change MAY return a different result. However, the previous value is not
MUST NOT cause the Email object to be considered as changed by the considered incorrect, and the change SHOULD NOT cause the Email
server. object to be considered as changed by the server.
The exact algorithm for decomposing bodyStructure into textBody, The exact algorithm for decomposing bodyStructure into textBody,
htmlBody and attachments part lists is not mandated, as this is a htmlBody and attachments part lists is not mandated, as this is a
quality-of-service implementation issue and likely to require quality-of-service implementation issue and likely to require
workarounds for malformed content discovered over time. However, the workarounds for malformed content discovered over time. However, the
following algorithm (expressed here in JavaScript) is suggested as a following algorithm (expressed here in JavaScript) is suggested as a
starting point, based on real-world experience: starting point, based on real-world experience:
function isInlineMediaType ( type ) { function isInlineMediaType ( type ) {
return type.startsWith( 'image/' ) || return type.startsWith( 'image/' ) ||
type.startsWith( 'audio/' ) || type.startsWith( 'audio/' ) ||
type.startsWith( 'video/' ); type.startsWith( 'video/' );
} }
function parseStructure ( parts, multipartType, inAlternative, function parseStructure ( parts, multipartType, inAlternative,
htmlBody, textBody, attachments ) { htmlBody, textBody, attachments ) {
// For multipartType == alternative // For multipartType == alternative
let textLength = textBody ? textBody.length : -1; let textLength = textBody ? textBody.length : -1;
let htmlLength = htmlBody ? htmlBody.length : -1; let htmlLength = htmlBody ? htmlBody.length : -1;
for ( let i = 0; i < parts.length; i += 1 ) { for ( let i = 0; i < parts.length; i += 1 ) {
let part = parts[i]; let part = parts[i];
let isMultipart = part.type.startsWith( 'multipart/' ); let isMultipart = part.type.startsWith( 'multipart/' );
// Is this a body part rather than an attachment // Is this a body part rather than an attachment
let isInline = part.disposition != "attachment" && let isInline = part.disposition != "attachment" &&
// Must be one of the allowed body types // Must be one of the allowed body types
( part.type == "text/plain" || ( part.type == "text/plain" ||
part.type == "text/html" || part.type == "text/html" ||
isInlineMediaType( part.type ) ) && isInlineMediaType( part.type ) ) &&
// If multipart/related, only the first part can be inline // If multipart/related, only the first part can be inline
// If a text part with a filename, and not the first item in the // If a text part with a filename, and not the first item
// multipart, assume it is an attachment // in the multipart, assume it is an attachment
( i === 0 || ( i === 0 ||
( multipartType != "related" && ( multipartType != "related" &&
( isInlineMediaType( part.type ) || !part.name ) ) ); ( isInlineMediaType( part.type ) || !part.name ) ) );
if ( isMultipart ) { if ( isMultipart ) {
let subMultiType = part.type.split( '/' )[1]; let subMultiType = part.type.split( '/' )[1];
parseStructure( part.subParts, subMultiType, parseStructure( part.subParts, subMultiType,
inAlternative || ( subMultiType == 'alternative' ), inAlternative || ( subMultiType == 'alternative' ),
htmlBody, textBody, attachments ); htmlBody, textBody, attachments );
} else if ( isInline ) { } else if ( isInline ) {
if ( multipartType == 'alternative' ) { if ( multipartType == 'alternative' ) {
switch ( part.type ) { switch ( part.type ) {
case 'text/plain': case 'text/plain':
textBody.push( part ); textBody.push( part );
break; break;
case 'text/html': case 'text/html':
htmlBody.push( part ); htmlBody.push( part );
break; break;
default: default:
attachments.push( part ); attachments.push( part );
break; break;
} }
continue; continue;
} else if ( inAlternative ) {
if ( part.type == 'text/plain' ) {
htmlBody = null;
}
if ( part.type == 'text/html' ) {
textBody = null;
}
}
if ( textBody ) {
textBody.push( part );
}
if ( htmlBody ) {
htmlBody.push( part );
}
if ( ( !textBody || !htmlBody ) &&
isInlineMediaType( part.type ) ) {
attachments.push( part );
}
} else {
attachments.push( part );
}
}
if ( multipartType == 'alternative' && textBody && htmlBody ) { } else if ( inAlternative ) {
// Found HTML part only if ( part.type == 'text/plain' ) {
if ( textLength == textBody.length && htmlBody = null;
htmlLength != htmlBody.length ) { }
for ( let i = htmlLength; i < htmlBody.length; i += 1 ) { if ( part.type == 'text/html' ) {
textBody.push( htmlBody[i] ); textBody = null;
} }
} }
// Found plain text part only if ( textBody ) {
if ( htmlLength == htmlBody.length && textBody.push( part );
textLength != textBody.length ) { }
for ( let i = textLength; i < textBody.length; i += 1 ) { if ( htmlBody ) {
htmlBody.push( textBody[i] ); htmlBody.push( part );
} }
} if ( ( !textBody || !htmlBody ) &&
} isInlineMediaType( part.type ) ) {
} attachments.push( part );
}
} else {
attachments.push( part );
}
}
// Usage: if ( multipartType == 'alternative' && textBody && htmlBody ) {
let htmlBody = []; // Found HTML part only
let textBody = []; if ( textLength == textBody.length &&
let attachments = []; htmlLength != htmlBody.length ) {
for ( let i = htmlLength; i < htmlBody.length; i += 1 ) {
textBody.push( htmlBody[i] );
}
}
// Found plain text part only
if ( htmlLength == htmlBody.length &&
textLength != textBody.length ) {
for ( let i = textLength; i < textBody.length; i += 1 ) {
htmlBody.push( textBody[i] );
}
}
}
}
parseStructure( [ bodyStructure ], 'mixed', false, // Usage:
htmlBody, textBody, attachments ); let htmlBody = [];
let textBody = [];
let attachments = [];
parseStructure( [ bodyStructure ], 'mixed', false,
htmlBody, textBody, attachments );
For instance, consider a message with both text and html versions For instance, consider a message with both text and html versions
that's then gone through a list software manager that attaches a that's then gone through a list software manager that attaches a
header/footer. It might have a MIME structure something like: header/footer. It might have a MIME structure something like:
multipart/mixed multipart/mixed
text/plain, content-disposition=inline - A text/plain, content-disposition=inline - A
multipart/mixed multipart/mixed
multipart/alternative multipart/alternative
multipart/mixed multipart/mixed
skipping to change at page 36, line 29 skipping to change at page 36, line 33
being rejected with an "invalidArguments" error. being rejected with an "invalidArguments" error.
Where a specific header is requested as a property, the Where a specific header is requested as a property, the
capitalization of the property name in the response MUST be identical capitalization of the property name in the response MUST be identical
to that used in the request. to that used in the request.
4.2.1. Example 4.2.1. Example
Request: Request:
[[ "Email/get", { [[ "Email/get", {
"ids": [ "f123u456", "f123u457" ], "ids": [ "f123u456", "f123u457" ],
"properties": [ "threadId", "mailboxIds", "from", "subject", "receivedAt", "header:List-POST:asURLs", "htmlBody", "bodyValues" ], "properties": [ "threadId", "mailboxIds", "from", "subject",
"bodyProperties": [ "partId", "blobId", "size", "type" ], "receivedAt", "header:List-POST:asURLs",
"fetchHTMLBodyValues": true, "htmlBody", "bodyValues" ],
"maxBodyValueBytes": 256 "bodyProperties": [ "partId", "blobId", "size", "type" ],
}, "#1" ]] "fetchHTMLBodyValues": true,
"maxBodyValueBytes": 256
}, "#1" ]]
and response: and response:
[[ "Email/get", { [[ "Email/get", {
"accountId": "abc", "accountId": "abc",
"state": "41234123231", "state": "41234123231",
"list": [ "list": [
{ {
"id": "f123u457", "id": "f123u457",
"threadId": "ef1314a", "threadId": "ef1314a",
"mailboxIds": { "f123": true }, "mailboxIds": { "f123": true },
"from": [{ "name": "Joe Bloggs", "email": "joe@example.com" }], "from": [{ "name": "Joe Bloggs", "email": "joe@example.com" }],
"subject": "Dinner on Thursday?", "subject": "Dinner on Thursday?",
"receivedAt": "2013-10-13T14:12:00Z", "receivedAt": "2013-10-13T14:12:00Z",
"header:List-POST:asURLs": [ "mailto:partytime@lists.example.com" ], "header:List-POST:asURLs": [
"htmlBody": [{ "mailto:partytime@lists.example.com"
"partId": "1", ],
"blobId": "841623871", "htmlBody": [{
"size": 283331, "partId": "1",
"type": "text/html" "blobId": "841623871",
}, { "size": 283331,
"partId": "2", "type": "text/html"
"blobId": "319437193", }, {
"size": 10343, "partId": "2",
"type": "text/plain" "blobId": "319437193",
}], "size": 10343,
"bodyValues": { "type": "text/plain"
"1": { }],
"isEncodingProblem": false, "bodyValues": {
"isTruncated": true, "1": {
"value": "<html><body><p>Hello ..." "isEncodingProblem": false,
}, "isTruncated": true,
"2": { "value": "<html><body><p>Hello ..."
"isEncodingProblem": false, },
"isTruncated": false, "2": {
"value": "-- Sent by your friendly mailing list ..." "isEncodingProblem": false,
} "isTruncated": false,
} "value": "-- Sent by your friendly mailing list ..."
} }
], }
"notFound": [ "f123u456" ] }
}, "#1" ]] ],
"notFound": [ "f123u456" ]
}, "#1" ]]
4.3. Email/changes 4.3. Email/changes
Standard "/changes" method. If generating intermediate states for a Standard "/changes" method. If generating intermediate states for a
large set of changes, it is recommended that newer changes are large set of changes, it is recommended that newer changes are
returned first, as these are generally of more interest to users. returned first, as these are generally of more interest to users.
4.4. Email/query 4.4. Email/query
Standard "/query" method, but with the following additional Standard "/query" method, but with the following additional
skipping to change at page 50, line 47 skipping to change at page 50, line 47
these thread ids. these thread ids.
"3": Finally, we fetch the information we need to display the mailbox "3": Finally, we fetch the information we need to display the mailbox
listing (but no more!) for every message in each of these 30 threads. listing (but no more!) for every message in each of these 30 threads.
The client may aggregate this data for display, for example showing The client may aggregate this data for display, for example showing
the thread as "flagged" if any of the messages in it contain the the thread as "flagged" if any of the messages in it contain the
"$flagged" keyword. "$flagged" keyword.
The response from the server may look something like this: The response from the server may look something like this:
[[ "Email/query", { [[ "Email/query", {
"accountId": "ue150411c", "accountId": "ue150411c",
"queryState": "09aa9a075588-780599:0", "queryState": "09aa9a075588-780599:0",
"canCalculateChanges": true, "canCalculateChanges": true,
"position": 0, "position": 0,
"total": 115, "total": 115,
"ids": [ "Ma783e5cdf5f2deffbc97930a", "M9bd17497e2a99cb345fc1d0a", ... ] "ids": [ "Ma783e5cdf5f2deffbc97930a",
}, "0" ], "M9bd17497e2a99cb345fc1d0a", ... ]
[ "Email/get", { }, "0" ],
"accountId": "ue150411c", [ "Email/get", {
"state": "780599", "accountId": "ue150411c",
"list": [{ "state": "780599",
"id": "Ma783e5cdf5f2deffbc97930a", "list": [{
"threadId": "T36703c2cfe9bd5ed" "id": "Ma783e5cdf5f2deffbc97930a",
}, { "threadId": "T36703c2cfe9bd5ed"
"id": "M9bd17497e2a99cb345fc1d0a", }, {
"threadId": "T0a22ad76e9c097a1" "id": "M9bd17497e2a99cb345fc1d0a",
}, ... ], "threadId": "T0a22ad76e9c097a1"
"notFound": [] }, ... ],
}, "1" ], "notFound": []
[ "Thread/get", { }, "1" ],
"accountId": "ue150411c", [ "Thread/get", {
"state": "22a8728b", "accountId": "ue150411c",
"list": [{ "state": "22a8728b",
"id": "T36703c2cfe9bd5ed", "list": [{
"emailIds": [ "Ma783e5cdf5f2deffbc97930a" ] "id": "T36703c2cfe9bd5ed",
}, { "emailIds": [ "Ma783e5cdf5f2deffbc97930a" ]
"id": "T0a22ad76e9c097a1", }, {
"emailIds": [ "M3b568670a63e5d100f518fa5", "M9bd17497e2a99cb345fc1d0a" ] "id": "T0a22ad76e9c097a1",
}, ... ], "emailIds": [ "M3b568670a63e5d100f518fa5",
"notFound": [] "M9bd17497e2a99cb345fc1d0a" ]
}, "2" ], }, ... ],
[ "Email/get", { "notFound": []
"accountId": "ue150411c", }, "2" ],
"state": "780599", [ "Email/get", {
"list": [{ "accountId": "ue150411c",
"id": "Ma783e5cdf5f2deffbc97930a", "state": "780599",
"threadId": "T36703c2cfe9bd5ed", "list": [{
"mailboxIds": { "id": "Ma783e5cdf5f2deffbc97930a",
"fb666a55": true "threadId": "T36703c2cfe9bd5ed",
}, "mailboxIds": {
"keywords": { "fb666a55": true
"$seen": true, },
"$flagged": true "keywords": {
}, "$seen": true,
"hasAttachment": true, "$flagged": true
"from": [{ },
"email": "jdoe@example.com", "hasAttachment": true,
"name": "Jane Doe" "from": [{
}], "email": "jdoe@example.com",
"subject": "The Big Reveal", "name": "Jane Doe"
"receivedAt": "2018-06-27T00:20:35Z", }],
"size": 175047, "subject": "The Big Reveal",
"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 the ..." "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" ]] ...
],
"notFound": []
}, "3" ]]
Now, on another device the user marks the first message as unread, Now, on another device the user marks the first message as unread,
sending this API request: sending this API request:
[[ "Email/set", { [[ "Email/set", {
"accountId": "ue150411c", "accountId": "ue150411c",
"update": { "update": {
"Ma783e5cdf5f2deffbc97930a": { "Ma783e5cdf5f2deffbc97930a": {
"keywords/$seen": null "keywords/$seen": null
} }
skipping to change at page 55, line 5 skipping to change at page 54, line 13
"Me8de6c9f6de198239b982ea2" at position 0. As it does not have the "Me8de6c9f6de198239b982ea2" at position 0. As it does not have the
data for this new email, it will then fetch it (it also could have data for this new email, it will then fetch it (it also could have
done this in the same request using back-references). done this in the same request using back-references).
It knows something has changed about "Ma783e5cdf5f2deffbc97930a", so It knows something has changed about "Ma783e5cdf5f2deffbc97930a", so
it will refetch the mailboxes and keywords (the only mutable it will refetch the mailboxes and keywords (the only mutable
properties) for this email too. properties) for this email too.
The user composes a new message and saves a draft. The client sends: The user composes a new message and saves a draft. The client sends:
[[ "Email/set", { [[ "Email/set", {
"accountId": "ue150411c", "accountId": "ue150411c",
"create": { "create": {
"k1546": { "k1546": {
"mailboxIds": { "mailboxIds": {
"2ea1ca41b38e": true "2ea1ca41b38e": true
}, },
"keywords": { "keywords": {
"$seen": true, "$seen": true,
"$draft": true "$draft": true
}, },
"from": [{ "from": [{
"name": "Joe Bloggs", "name": "Joe Bloggs",
"email": "joe@example.com" "email": "joe@example.com"
}], }],
"to": [{ "to": [{
"name": "John", "name": "John",
"email": "john@example.com" "email": "john@example.com"
}], }],
"subject": "World domination", "subject": "World domination",
"receivedAt": "2018-07-10T01:05:08Z", "receivedAt": "2018-07-10T01:05:08Z",
"sentAt": "2018-07-10T11:05:08+10:00", "sentAt": "2018-07-10T11:05:08+10:00",
"bodyStructure": { "bodyStructure": {
"type": "multipart/alternative", "type": "multipart/alternative",
"subParts": [{ "subParts": [{
"partId": "49db", "partId": "49db",
"type": "text/html" "type": "text/html"
}, { }, {
"partId": "bd48", "partId": "bd48",
"type": "text/plain" "type": "text/plain"
}] }]
}, },
"bodyValues": { "bodyValues": {
"bd48": { "bd48": {
"value": "I have the most brilliant plan. Let me tell you all about it. What we do is, we", "value": "I have the most brilliant plan. Let me tell you
"isTruncated": false all about it. What we do is, we",
}, "isTruncated": false
"49db": { },
"value": "<!DOCTYPE html><html><head><title></title><style type=\"text/css\">div{font-size:16px}</style></head><body><div>I have the most brilliant plan. Let me tell you all about it. What we do is, we</div></body></html>", "49db": {
"isTruncated": false "value": "<!DOCTYPE html><html><head><title></title>
} <style type=\"text/css\">div{font-size:16px}</style></head>
} <body><div>I have the most brilliant plan. Let me tell you
} all about it. What we do is, we</div></body></html>",
} "isTruncated": false
}, "0" ]] }
}
}
}
}, "0" ]]
The server creates the message and sends the success response: The server creates the message and sends the success response:
[[ "Email/set", { [[ "Email/set", {
"accountId": "ue150411c", "accountId": "ue150411c",
"oldState": "780823", "oldState": "780823",
"newState": "780839", "newState": "780839",
"created": { "created": {
"k1546": { "k1546": {
"id": "Md45b47b4877521042cec0938", "id": "Md45b47b4877521042cec0938",
skipping to change at page 59, line 26 skipping to change at page 58, line 29
"emailIds": [ "emailIds": [
"M44200ec123de277c0c1ce69c", "M44200ec123de277c0c1ce69c",
"M7bcbcb0b58d7729686e83d99", "M7bcbcb0b58d7729686e83d99",
"M28d12783a0969584b6deaac0", "M28d12783a0969584b6deaac0",
... ...
] ]
}, "tag-0" ]] }, "tag-0" ]]
Example response: Example response:
[[ "SearchSnippet/get", { [[ "SearchSnippet/get", {
"accountId": "ue150411c", "accountId": "ue150411c",
"list": [{ "list": [{
"emailId": "M44200ec123de277c0c1ce69c", "emailId": "M44200ec123de277c0c1ce69c",
"subject": null, "subject": null,
"preview": null "preview": null
}, { }, {
"emailId": "M7bcbcb0b58d7729686e83d99", "emailId": "M7bcbcb0b58d7729686e83d99",
"subject": "The <mark>Foo</mark>sball competition", "subject": "The <mark>Foo</mark>sball competition",
"preview": "...year the <mark>foo</mark>sball competition will be held in the Stadium de ..." "preview": "...year the <mark>foo</mark>sball competition will
}, { be held in the Stadium de ..."
"emailId": "M28d12783a0969584b6deaac0", }, {
"subject": null, "emailId": "M28d12783a0969584b6deaac0",
"preview": "...the <mark>Foo</mark>/bar method results often returns &lt;1 widget rather than the complete..." "subject": null,
}, "preview": "...the <mark>Foo</mark>/bar method results often
... returns &lt;1 widget rather than the complete..."
], },
"notFound": null ...
}, "tag-0" ]] ],
"notFound": null
}, "tag-0" ]]
6. Identities 6. Identities
An *Identity* object stores information about an email address (or An *Identity* object stores information about an email address (or
domain) the user may send from. It has the following properties: domain) the user may send from. It has the following properties:
o *id*: "Id" (immutable; server-set) The id of the identity. o *id*: "Id" (immutable; server-set) The id of the identity.
o *name*: "String" (default: "") The "From" _name_ the client SHOULD o *name*: "String" (default: "") The "From" _name_ the client SHOULD
use when creating a new message from this identity. use when creating a new message from this identity.
skipping to change at page 62, line 5 skipping to change at page 61, line 5
6.4. Example 6.4. Example
Request: Request:
[ "Identity/get", { [ "Identity/get", {
"accountId": "acme" "accountId": "acme"
}, "0" ] }, "0" ]
with response: with response:
[ "Identity/get", { [ "Identity/get", {
"accountId": "acme", "accountId": "acme",
"state": "99401312ae-11-333", "state": "99401312ae-11-333",
"list": [ "list": [
{ {
"id": "3301-222-11_22AAz", "id": "3301-222-11_22AAz",
"name": "Joe Bloggs", "name": "Joe Bloggs",
"email": "joe@example.com", "email": "joe@example.com",
"replyTo": null, "replyTo": null,
"bcc": [{ "bcc": [{
"name": null, "name": null,
"email": "joe+archive@example.com" "email": "joe+archive@example.com"
}], }],
"textSignature": "-- \nJoe Bloggs\nMaster of Email", "textSignature": "-- \nJoe Bloggs\nMaster of Email",
"htmlSignature": "<div><b>Joe Bloggs</b></div><div>Master of Email</div>", "htmlSignature": "<div><b>Joe Bloggs</b></div>
"mayDelete": false <div>Master of Email</div>",
}, "mayDelete": false
{ },
"id": "9911312-11_22AAz", {
"name": "Joe B", "id": "9911312-11_22AAz",
"email": "*@example.com", "name": "Joe B",
"replyTo": null, "email": "*@example.com",
"bcc": null, "replyTo": null,
"textSignature": "", "bcc": null,
"htmlSignature": "", "textSignature": "",
"mayDelete": true "htmlSignature": "",
} "mayDelete": true
], }
"notFound": [] ],
}, "0" ] "notFound": []
}, "0" ]
7. Email submission 7. Email submission
An *EmailSubmission* object represents the submission of an email for An *EmailSubmission* object represents the submission of an email for
delivery to one or more recipients. It has the following properties: delivery to one or more recipients. It has the following properties:
o *id*: "Id" (immutable; server-set) The id of the email submission. o *id*: "Id" (immutable; server-set) The id of the email submission.
o *identityId*: "Id" (immutable) The id of the identity to associate o *identityId*: "Id" (immutable) The id of the identity to associate
with this submission. with this submission.
skipping to change at page 65, line 16 skipping to change at page 64, line 19
is replaced with a space. is replaced with a space.
+ Any prefix in common with the first line is stripped from + Any prefix in common with the first line is stripped from
lines after the first. lines after the first.
+ CRLF is replaced by a space. + CRLF is replaced by a space.
For example: For example:
550-5.7.1 Our system has detected that this message is 550-5.7.1 Our system has detected that this message is
550 5.7.1 likely spam, sorry. 550 5.7.1 likely spam.
would become: would become:
550 5.7.1 Our system has detected that this message is likely spam, sorry. 550 5.7.1 Our system has detected that this message is likely spam.
For emails relayed via an alternative to SMTP, the server MAY For emails relayed via an alternative to SMTP, the server MAY
generate a synthetic string representing the status instead. generate a synthetic string representing the status instead.
If it does this, the string MUST be of the following form: If it does this, the string MUST be of the following form:
+ A 3-digit SMTP reply code, as defined in [RFC5321], section + A 3-digit SMTP reply code, as defined in [RFC5321], section
4.2.3. 4.2.3.
+ Then a single space character. + Then a single space character.
skipping to change at page 66, line 34 skipping to change at page 65, line 37
+ "unknown": The display status is unknown. This is the + "unknown": The display status is unknown. This is the
initial value. initial value.
+ "yes": The recipient's system claims the email content has + "yes": The recipient's system claims the email content has
been displayed to the recipient. Note, there is no been displayed to the recipient. Note, there is no
guarantee that the recipient has noticed, read, or guarantee that the recipient has noticed, read, or
understood the content. understood the content.
If an MDN is received for this recipient with Disposition-Type If an MDN is received for this recipient with Disposition-Type
(as per [RFC3798] section 3.2.6.2) equal to "displayed", this (as per [RFC8098] section 3.2.6.2) equal to "displayed", this
property SHOULD be set to "yes". The server MAY also set this property SHOULD be set to "yes". The server MAY also set this
property based on other feedback channels. property based on other feedback channels.
o *dsnBlobIds*: "Id[]" (server-set) A list of blob ids for DSNs o *dsnBlobIds*: "Id[]" (server-set) A list of blob ids for DSNs
received for this submission, in order of receipt, oldest first. received for this submission, in order of receipt, oldest first.
o *mdnBlobIds*: "Id[]" (server-set) A list of blob ids for MDNs o *mdnBlobIds*: "Id[]" (server-set) A list of blob ids for MDNs
received for this submission, in order of receipt, oldest first. received for this submission, in order of receipt, oldest first.
JMAP servers MAY choose not to expose DSN and MDN responses as Email JMAP servers MAY choose not to expose DSN and MDN responses as Email
skipping to change at page 86, line 19 skipping to change at page 85, line 19
[RFC3463] Vaudreuil, G., "Enhanced Mail System Status Codes", [RFC3463] Vaudreuil, G., "Enhanced Mail System Status Codes",
RFC 3463, DOI 10.17487/RFC3463, January 2003, RFC 3463, DOI 10.17487/RFC3463, January 2003,
<https://www.rfc-editor.org/info/rfc3463>. <https://www.rfc-editor.org/info/rfc3463>.
[RFC3464] Moore, K. and G. Vaudreuil, "An Extensible Message Format [RFC3464] Moore, K. and G. Vaudreuil, "An Extensible Message Format
for Delivery Status Notifications", RFC 3464, for Delivery Status Notifications", RFC 3464,
DOI 10.17487/RFC3464, January 2003, DOI 10.17487/RFC3464, January 2003,
<https://www.rfc-editor.org/info/rfc3464>. <https://www.rfc-editor.org/info/rfc3464>.
[RFC3798] Hansen, T., Ed. and G. Vaudreuil, Ed., "Message
Disposition Notification", RFC 3798, DOI 10.17487/RFC3798,
May 2004, <https://www.rfc-editor.org/info/rfc3798>.
[RFC3834] Moore, K., "Recommendations for Automatic Responses to [RFC3834] Moore, K., "Recommendations for Automatic Responses to
Electronic Mail", RFC 3834, DOI 10.17487/RFC3834, August Electronic Mail", RFC 3834, DOI 10.17487/RFC3834, August
2004, <https://www.rfc-editor.org/info/rfc3834>. 2004, <https://www.rfc-editor.org/info/rfc3834>.
[RFC4422] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple [RFC4422] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple
Authentication and Security Layer (SASL)", RFC 4422, Authentication and Security Layer (SASL)", RFC 4422,
DOI 10.17487/RFC4422, June 2006, DOI 10.17487/RFC4422, June 2006,
<https://www.rfc-editor.org/info/rfc4422>. <https://www.rfc-editor.org/info/rfc4422>.
[RFC4616] Zeilenga, K., Ed., "The PLAIN Simple Authentication and [RFC4616] Zeilenga, K., Ed., "The PLAIN Simple Authentication and
skipping to change at page 87, line 44 skipping to change at page 86, line 39
[RFC6533] Hansen, T., Ed., Newman, C., and A. Melnikov, [RFC6533] Hansen, T., Ed., Newman, C., and A. Melnikov,
"Internationalized Delivery Status and Disposition "Internationalized Delivery Status and Disposition
Notifications", RFC 6533, DOI 10.17487/RFC6533, February Notifications", RFC 6533, DOI 10.17487/RFC6533, February
2012, <https://www.rfc-editor.org/info/rfc6533>. 2012, <https://www.rfc-editor.org/info/rfc6533>.
[RFC6710] Melnikov, A. and K. Carlberg, "Simple Mail Transfer [RFC6710] Melnikov, A. and K. Carlberg, "Simple Mail Transfer
Protocol Extension for Message Transfer Priorities", Protocol Extension for Message Transfer Priorities",
RFC 6710, DOI 10.17487/RFC6710, August 2012, RFC 6710, DOI 10.17487/RFC6710, August 2012,
<https://www.rfc-editor.org/info/rfc6710>. <https://www.rfc-editor.org/info/rfc6710>.
[RFC8098] Hansen, T., Ed. and A. Melnikov, Ed., "Message Disposition
Notification", STD 85, RFC 8098, DOI 10.17487/RFC8098,
February 2017, <https://www.rfc-editor.org/info/rfc8098>.
[RFC8314] Moore, K. and C. Newman, "Cleartext Considered Obsolete: [RFC8314] Moore, K. and C. Newman, "Cleartext Considered Obsolete:
Use of Transport Layer Security (TLS) for Email Submission Use of Transport Layer Security (TLS) for Email Submission
and Access", RFC 8314, DOI 10.17487/RFC8314, January 2018, and Access", RFC 8314, DOI 10.17487/RFC8314, January 2018,
<https://www.rfc-editor.org/info/rfc8314>. <https://www.rfc-editor.org/info/rfc8314>.
[RFC8457] Leiba, B., Ed., "IMAP "$Important" Keyword and [RFC8457] Leiba, B., Ed., "IMAP "$Important" Keyword and
"\Important" Special-Use Attribute", RFC 8457, "\Important" Special-Use Attribute", RFC 8457,
DOI 10.17487/RFC8457, September 2018, DOI 10.17487/RFC8457, September 2018,
<https://www.rfc-editor.org/info/rfc8457>. <https://www.rfc-editor.org/info/rfc8457>.
 End of changes. 28 change blocks. 
362 lines changed or deleted 386 lines changed or added

This html diff was produced by rfcdiff 1.47. The latest version is available from http://tools.ietf.org/tools/rfcdiff/