Skip to Content

You write the request.jmapc writes the client.

JMAP is the IETF standard for mail, contacts and calendars: one JSON API over HTTP in place of IMAP, CardDAV and CalDAV.

requests/ListInboxEmails.jmap.json
{ "methodCalls": [ ["Email/query", { "filter": {"inMailbox": "{{mailboxId}}"}, "limit": "{{limit}}" }, "search"], ["Email/get", { "#ids": {"resultOf": "search", "name": "Email/query", "path": "/ids"}, "properties": ["id", "subject", "from", "receivedAt"] }, "fetch"] ], "_returns": "fetch" }
jmapc generate
inbox.go
res, err := client.ListInboxEmails(ctx, c, client.ListInboxEmailsParams{ MailboxID: inbox, Limit: 25, }) if err != nil { return err } for _, email := range res.List { fmt.Println(email.ReceivedAt, email.From[0].Email, *email.Subject) }

jmapc is a compiler for it. The request is the JSON the specification already defines; jmapc checks it against the specification and writes a client that can only call it correctly.

Read the documentationWhy not a library?

Verification

A request that is wrong never reaches a server

Every request is read against the specification before a line of code is generated — as you type, when you build, and against a running server when you ask. A misspelling comes back with the name you meant.

requests/BadQuery.jmap.json: methodCalls[0].arguments.filter.hasAttachmnt: EmailFilterCondition has no property "hasAttachmnt" did you mean "hasAttachment"? requests/BadQuery.jmap.json: methodCalls[1].arguments.#ids.name: the referenced call is Email/query, but the reference names Email/get call "c0" invokes Email/query

How a request is checked

Languages

Three languages, and almost nothing to install

The runtime is generated alongside the requests rather than shipped as a library, so what a project takes on is the generated directory and little else.

Go

The generated package uses the standard library and nothing outside it. Transmission is an http.Client you already have.

Rust

serde and serde_json, and no more: no HTTP stack, no TLS backend and no async runtime come with it. Transmission is a Transport you implement over whichever client the program already uses.

TypeScript

No dependencies at all. The only thing the platform has to provide is fetch.

Rust and TypeScript

Coverage

Everything JMAP has standardised

Not a subset chosen to make a demo work: the protocol as the standards bodies have published it so far.

15 capabilities

Every one IANA lists — mail, submission, contacts, calendars, principals, sieve, quota, blob, push and the rest — is in the data model a request is checked against. One that is not, a vendor’s own among them, is described in a schema file and checked like any other.

81 methods

Across twenty-one types, all generated and checked the same way. Email/query, CalendarEvent/copy and SieveScript/validate are the same kind of thing to jmapc.

Every capability and method

A compiler, not a wrapper

These five all follow from that one difference.

No API of its own

The request file is JMAP. What you learn is the specification, and it is the same knowledge in all three languages.

A response shaped like the question

The result carries the properties the request asked for and nothing else. Ask for one more and the type grows; ask for one that does not exist and the build stops.

Errors at every level JMAP reports them

A failed request, a failed method call, a /set that answers 200 with a refusal — the generated code tells them apart so the caller does not have to.

Push and paging as loops, not plumbing

A request marked as watched follows the changes the server reports. One marked as paged reads a result the server returns a part at a time.

Tools around the edges

A command that sends a request to a real server, a JMAP server to test your own code against, and a schema file for the capabilities jmapc does not know yet.

Install

The client is generated with go generate, so record the tool in the module that uses it. A Rust or TypeScript project has no Go toolchain to run go tool with, and takes a binary from the releases instead.

go get -tool github.com/linyows/jmapc/cmd/jmapc

Write your first request