I/O JMAP

JMAP client library, written in Rust.
This library is composed of 3 feature-gated layers:
- Low-level I/O-free coroutines: these
no_std-compatible state machines contain the whole JMAP logic and can be used anywhere - Mid-level light client: a standard, blocking JMAP client using a
Stream: Read + Write - High-level full client: light client + TCP connections and TLS negotiations handled for you
Table of contents
Features
- I/O-free coroutines:
no_stdstate machines; no sockets, no async runtime, nostdrequired, drive against any blocking, async, or fuzz harness. - Light standard, blocking client (requires
clientfeature) - Full standard, blocking client with TLS support:
- Rustls with ring crypto (requires
rustls-ringfeature) - Rustls with aws crypto (requires
rustls-awsfeature) - Native TLS (requires
native-tlsfeature)
- Rustls with ring crypto (requires
- HTTP Auth mechanisms:
BASIC,BEARER
[!TIP] I/O JMAP is written in Rust and uses cargo features to gate backend support. The default feature set is declared in Cargo.toml or on docs.rs.
RFC coverage
| Module | What it covers |
|---|---|
| 8620 | JMAP core: session discovery, API requests, Foo/get, Foo/set, Foo/query, Foo/changes, blobs |
| 8621 | JMAP for Mail: Mailbox, Email, Thread, Identity, EmailSubmission, VacationResponse |
Usage
I/O JMAP can be consumed three ways, depending on how much of the I/O stack you want to own. Each mode is gated by cargo features.
Whichever mode you pick, every standard-shape coroutine implements the JmapCoroutine trait with two associated types: Yield (intermediate progress) and Return (terminal value, by convention Result<Output, Error>). Its resume(arg: Option<&[u8]>) method returns a JmapCoroutineState<Yield, Return> with two variants:
Yielded(Yield): intermediate yield. Most coroutines pick the standardJmapYieldwithWantsRead/WantsWrite(Vec<u8>). PassSome(&[])afterWantsReadto signal EOF.Complete(Return): terminal yield, carryingOk(Output)on success orErr(Error)on failure.
Three coroutines (JmapSessionGet, JmapBlobDownload, JmapBlobUpload) declare their own JmapRedirectYield which extends the standard variants with WantsRedirect { url, keep_alive, same_origin }: the server responded with a 3xx and the caller chooses whether to open a new connection to url and retry, or surface the redirect as an error.
I/O-free coroutines
No features required: works in #![no_std], no sockets, no async runtime. You own the loop and the bytes; the library only produces request bytes and consumes server responses.
Fetch a JMAP session against a blocking rustls socket:
use ;
use ;
use ;
use ConfigVerifierExt;
use SecretString;
use Url;
let http_auth = from;
let base_url = parse.unwrap;
let config = with_platform_verifier.unwrap;
let server_name = base_url.host_str.unwrap.to_string.try_into.unwrap;
let conn = new.unwrap;
let tcp = connect.unwrap;
let mut stream = new;
let mut coroutine = new;
let mut arg: = None;
let mut buf = ;
let mut read_buf = Vec::new;
let session = loop ;
println!;
println!;
Light client
Enable the client feature. JmapClientStd::new(stream, http_auth) wraps any blocking Read + Write and exposes one method per JMAP coroutine. You still open the TCP socket and run TLS yourself, and hand over a ready-to-talk stream; the client takes it from there.
[]
= { = "0.1.0", = false, = ["client"] }
use ;
use ;
use ;
use ConfigVerifierExt;
use SecretString;
use Url;
let http_auth = from;
let session_url = parse.unwrap;
let config = with_platform_verifier.unwrap;
let server_name = session_url.host_str.unwrap.to_string.try_into.unwrap;
let conn = new.unwrap;
let tcp = connect.unwrap;
let stream = new;
let mut client = new;
let session = client.session_get.unwrap;
println!;
let mailboxes = client.mailbox_query.unwrap;
for mailbox in &mailboxes.mailboxes
Full client
Enable one of the TLS feature flags: rustls-ring (default), rustls-aws, or native-tls. JmapClientStd::connect(url, tls, http_auth) opens http:// / https:// (or jmap:// / jmaps://) URLs via pimalaya/stream.
[]
= "0.1.0" # rustls-ring is enabled by default
use ;
use Tls;
use SecretString;
use Url;
let http_auth = from;
let session_url = parse.unwrap;
let tls = default;
let mut client = connect.unwrap;
let session = client.session_get.unwrap;
println!;
let mailboxes = client.mailbox_query.unwrap;
for mailbox in &mailboxes.mailboxes
JMAP typically reuses a single connection for the entire session, so the client wraps one stream. When the apiUrl, uploadUrl or downloadUrl resolves to a different authority than where you first connected, use JmapClientStd::set_stream to swap in a new transport.
Examples
See complete examples at ./examples.
Have also a look at real-world projects built on top of this library:
- Himalaya CLI: CLI to manage emails
- Himalaya TUI: TUI to manage emails
- Neverest: CLI to synchronize emails
- Mirador: CLI to watch mailbox changes and fire hooks on every event
AI disclosure
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
-
Tools: Claude Code (Anthropic), Opus 4.7, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
-
Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
-
Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
-
Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (
nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour. -
Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
-
Last reviewed: 05/06/2026
License
This project is licensed under either of:
at your option.
Social
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Sponsoring
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
