← RFC Reference

RFC 8620: JMAP — JSON Meta Application Protocol

Standards Track Mail Access Protocols Published July 2019
ELI5: IMAP was designed in the 1990s when clients talked to servers over raw TCP sockets with a custom text protocol. JMAP is the same idea — access your mailbox, manage folders, sync changes — but using the tools every developer already knows: JSON over HTTPS. It’s a REST-like API for email. No socket management, no parsing quirky text responses, no extension negotiation. Just POST a JSON request, get a JSON response.

Why This Exists

IMAP works, but it carries decades of complexity:

JMAP solves all of these. It is stateless (each request is self-contained), uses HTTPS (works through every proxy and firewall), uses JSON (every language has a parser), and defines push notifications as a first-class feature via EventSource or Web Push.

How It Works

Service Discovery

A client discovers the JMAP endpoint via a well-known URI:

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/"
}

Making API Calls

All JMAP operations are sent as a single POST to the API URL. The request body contains an array of method calls, and the response contains the corresponding results:

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": "Inbox", "totalEmails": 142 },
        { "id": "mb2", "name": "Sent", "totalEmails": 87 },
        { "id": "mb3", "name": "Archive", "totalEmails": 4210 }
      ]
    }, "a"]
  ]
}

Batching and Back-References

Multiple method calls can be batched in a single request. Back-references let one call use the results of a previous call:

"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"]
]

This searches the inbox and fetches the top 10 results in a single HTTP round-trip — something that would take multiple IMAP commands.

Key Technical Details

State Strings and Synchronization

Every JMAP /get response includes a state string. To sync changes, the client calls /changes with the last known state:

["Email/changes", { "accountId": "u1234", "sinceState": "e789" }, "c"]

["Email/changes", {
  "oldState": "e789",
  "newState": "e801",
  "created": ["em500", "em501"],
  "updated": ["em480"],
  "destroyed": ["em312"]
}, "c"]

The client then fetches only the created/updated items. This is more efficient than IMAP's CONDSTORE because it works across all mailboxes simultaneously and does not require a persistent connection.

Push Notifications

JMAP defines two push mechanisms:

The Foo/get, Foo/set, Foo/query Pattern

JMAP uses a uniform method pattern for every data type:

Method Purpose
Foo/get Fetch objects by ID
Foo/changes Get IDs of objects changed since a state
Foo/set Create, update, or destroy objects
Foo/query Search/filter and sort, returning matching IDs
Foo/queryChanges Incremental updates to a previous query result

For email, Foo is Email, Mailbox, Thread, EmailSubmission, etc. This consistency means once you understand one data type, you understand them all.

Common Mistakes

Deliverability Impact

Related RFCs