RFC 8620: JMAP — JSON 元应用协议
ELI5: IMAP 是在 1990 年代设计的,当时客户端通过原始 TCP 套接字与服务器通信,使用自定义文本协议。JMAP 的概念相同——访问邮箱、管理文件夹、同步更改——但使用每位开发者都已了解的工具:JSON over HTTPS。这是一个类似 REST 的电子邮件 API。不需要套接字管理、不需要解析奇怪的文本响应、不需要扩展协商。只需 POST 一个 JSON 请求,获取 JSON 响应。
为什么存在
IMAP有效,但它承载了数十年的复杂性:
- 有状态的TCP连接。IMAP要求每个邮箱保持持久连接。不可靠网络上的移动客户端不断重新连接和重新同步。
- 自定义文本协议。IMAP的传输格式需要专门的解析器。每种语言都需要专用的IMAP库。
- 扩展碎片化。关键功能(CONDSTORE、MOVE、SPECIAL-USE)是可选扩展,服务器可能支持也可能不支持。
- 推送通知限制。IMAP IDLE一次只能用于一个邮箱,许多代理/防火墙会杀死空闲TCP连接。
JMAP解决了所有这些问题。它是无状态的(每个请求是独立的),使用HTTPS(通过任何代理和防火墙工作),使用JSON(每种语言都有解析器),并通过EventSource或Web Push将推送通知定义为一等特性。
工作原理
服务发现
客户端通过well-known URI发现JMAP端点:
GET https://example.com/.well-known/jmap { "capabilities": { "urn:ietf:params:jmap:core": { ... }, "urn:ietf:params:jmap:mail": { ... } }, "apiUrl": "https://jmap.example.com/api/", "uploadUrl": "https://jmap.example.com/upload/{accountId}/", "downloadUrl": "https://jmap.example.com/download/{accountId}/{blobId}/{name}", "eventSourceUrl": "https://jmap.example.com/events/" }
进行API调用
所有JMAP操作作为单个POST发送到API URL。请求体包含方法调用数组,响应包含对应的结果:
POST https://jmap.example.com/api/ { "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], "methodCalls": [ ["Mailbox/get", { "accountId": "u1234", "ids": null }, "a"] ] } { "methodResponses": [ ["Mailbox/get", { "accountId": "u1234", "state": "m42", "list": [ { "id": "mb1", "name": "收件箱", "totalEmails": 142 }, { "id": "mb2", "name": "已发送", "totalEmails": 87 }, { "id": "mb3", "name": "存档", "totalEmails": 4210 } ] }, "a"] ] }
批处理和后向引用
多个方法调用可在一个请求中批处理。后向引用让一个调用使用前一个调用的结果:
"methodCalls": [ ["Email/query", { "accountId": "u1234", "filter": { "inMailbox": "mb1", "after": "2025-03-01T00:00:00Z" }, "sort": [{ "property": "receivedAt", "isAscending": false }], "limit": 10 }, "q"], ["Email/get", { "accountId": "u1234", "#ids": { "resultOf": "q", "name": "Email/query", "path": "/ids" }, "properties": ["from", "subject", "receivedAt", "preview"] }, "g"] ]
这会搜索收件箱并在一个HTTP往返中获取前10个结果——这需要多个IMAP命令。
关键技术细节
状态字符串和同步
每个JMAP /get响应都包含一个state字符串。要同步更改,客户端使用上次已知状态调用/changes:
["Email/changes", { "accountId": "u1234", "sinceState": "e789" }, "c"] ["Email/changes", { "oldState": "e789", "newState": "e801", "created": ["em500", "em501"], "updated": ["em480"], "destroyed": ["em312"] }, "c"]
客户端随后仅获取创建/更新的项。这比IMAP的CONDSTORE更有效,因为它同时在所有邮箱中工作,不需要持久连接。
推送通知
JMAP定义了两种推送机制:
-
EventSource:客户端打开到
eventSourceUrl的长期SSE连接。服务器在状态变化时发送事件。 - Web Push(RFC 8030):用于移动/后台客户端。服务器向客户端提供的推送订阅URL发送推送通知。
Foo/get、Foo/set、Foo/query模式
JMAP为每种数据类型使用统一的方法模式:
| 方法 | 用途 |
|---|---|
Foo/get |
按ID获取对象 |
Foo/changes |
获取自状态以来已更改的对象ID |
Foo/set |
创建、更新或销毁对象 |
Foo/query |
搜索/过滤并排序,返回匹配的ID |
Foo/queryChanges |
对前一个查询结果的增量更新 |
对于电子邮件,Foo是Email、Mailbox、Thread、EmailSubmission等。这种一致性意味着一旦你理解了一种数据类型,你就理解了它们所有。
常见错误
- 将JMAP视为传统REST API。JMAP使用单个端点,请求体中有方法调用,而不是基于资源的URL。不要尝试将其映射到REST约定。
- 忽略状态字符串。每个响应都有一个状态。如果你丢弃它,你将失去有效的同步,必须在下一个调用中重新获取所有内容。
-
获取所有属性。不带
properties列表的Email/get返回所有内容,包括完整的消息体。始终指定你需要的属性。 - 不使用后向引用。为查询+获取进行单独的HTTP请求浪费往返。使用后向引用来批处理它们。
- 假设通用服务器支持。截至2026年,JMAP采用量在增长,但IMAP仍然部署范围更广。Fastmail是最杰出的JMAP提供商。在承诺仅使用JMAP之前检查服务器功能。
- 轮询而不是使用推送。JMAP提供EventSource和Web Push正是为了你不必轮询。使用它们。
可交付性影响
-
JMAP主要是一个检索协议。像IMAP和POP3一样,它管理收件人如何访问邮件,而不是你如何发送邮件。但是,JMAP也为发送定义了
EmailSubmission,这可能会替代某些用例中的SMTP提交。 - 更好的参与数据。JMAP对标志和邮箱移动的结构化方法为提供商提供了更清晰的参与信号。当用户通过JMAP将你的邮件从垃圾邮件移到收件箱时,提供商看到一个清晰的信号。
- 更快的客户端同步。因为JMAP同步比IMAP更有效,移动客户端保持同步更可靠。这意味着收件人更快地看到你的邮件,并且参与(打开、点击)发生得更快。
-
EmailSubmission对象。JMAP的
EmailSubmission/set方法允许通过用于阅读的相同API发送电子邮件。对于既发送又接收的应用程序,这在一个协议下统一了整个电子邮件工作流。