Skip to main content

Jmaple

A pluggable JMAP server framework, built on FastAPI.

📖 Documentation — tutorial, topic guides, and API reference.

JMAP (RFC 8620) was designed as a generic JSON-over-HTTP sync protocol with a clean separation between core machinery (sessions, request/response envelope, method dispatch, references, state strings, push) and data-type capabilities (Mail = RFC 8621, Contacts = RFC 9610, …). Jmaple takes that separation seriously and treats every data type — including Mail — as a plugin.

  • Python: 3.14+
  • Package manager: uv
  • Web framework: FastAPI
  • ORM: SQLAlchemy (async)
  • Migrations: Alembic (dynamically discovers plugin migrations via Python entry points)
  • Operator interface: a single jmaple CLI — there is no separate admin web UI.

What's in the box

  • Full JMAP Core (RFC 8620): session resource, method dispatch, result references, state strings, error handling, upload/download.
  • A clean plugin contract so a third party can add a new capability (urn:vendor:thing) by registering schemas and method handlers, without touching the core.
  • A pluggable auth subsystem (JWT, OIDC, opaque bearer) wired into a unified accounts/grants authorization layer stored in the database.
  • A pluggable persistence layer built on async SQLAlchemy 2.0 + Alembic, with PostgreSQL as the default and SQLite for development.
  • All three JMAP push transports: SSE (/eventsource), WebSocket (RFC 8887), and PushSubscription webhooks (RFC 8620 §7.2).
  • A reference plugin (urn:jmaple:notes) demonstrating end-to-end use of every abstraction.

Quickstart

You need uv and just on your $PATH.

just install              # uv sync
cp .env.example .env      # tweak JMAPLE_DATABASE_URL, JMAPLE_SECRET_KEY, …
just migrate              # apply Alembic migrations
just serve                # uvicorn at http://localhost:8000

# In another terminal: issue the first admin token.
uv run jmaple token issue --user alice --admin

Then point a JMAP client at http://localhost:8000/.well-known/jmap with the issued token in the Authorization: Bearer <token> header.

Top-level recipes

Recipe Description
just install uv sync.
just serve Run uvicorn with --reload.
just migrate jmaple migrate upgrade head.
just revision "msg" jmaple migrate revision --autogenerate -m "msg".
just fmt / just lint / just type / just test Ruff format / Ruff lint / ty check / pytest.
just check All four of the above.
just package uv build — produces wheel + sdist in dist/.

The jmaple CLI

The CLI is the only operator interface. Run uv run jmaple --help for the full tree. Top-level groups:

  • jmaple serve — start the FastAPI server.
  • jmaple migrate {upgrade, downgrade, revision, history, current} — Alembic.
  • jmaple user {list, show, create, promote} — manage users.
  • jmaple account {list, create, rename, delete} — manage accounts.
  • jmaple grant {list, add, remove} — manage account-sharing grants.
  • jmaple token {issue, list, revoke} — manage opaque bearer tokens.
  • jmaple subscription {list, revoke} — inspect / revoke push subscriptions.
  • jmaple capability {list, settings get, settings set} — inspect / configure registered capabilities.
  • jmaple version — print the installed version.

Public API

When writing a capability, import from these modules. Everything under jmaple.core.*, jmaple.auth.*, and jmaple.api.* is internal and may change without notice.

Module What's in it
jmaple.capabilities Capability, CrudCapability, BaseCapability, MethodHandler, DataType, make_capability, register_capability
jmaple.db Base, JMAPObject, DataChange, UTCDateTime, new_id, utcnow, id_column, created_column
jmaple.schemas JMAPGetArgs, JMAPGetResponse, JMAPSetArgs, JMAPSetResponse, JMAPQueryArgs, JMAPQueryResponse, JMAPChangesArgs, JMAPChangesResponse, JMAPCopyArgs, JMAPCopyResponse, JMAPQueryChangesArgs, JMAPSort
jmaple.types Id, UTCDate, ResultReference, PatchObject
jmaple.context MethodContext
jmaple.errors MethodError, SetError, all ERR_* constants

A minimal capability looks like:

# my_plugin/models.py
from jmaple.db import Base, JMAPObject
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

class Todo(Base, JMAPObject):
    __tablename__ = "todos"
    text: Mapped[str] = mapped_column(String(512))

# my_plugin/capability.py
from jmaple.capabilities import CrudCapability
from my_plugin.models import Todo
from my_plugin.schema import TodoSchema, TodoCreate

class TodosCapability(CrudCapability):
    urn = "urn:example:todos"
    data_type_name = "Todo"
    model = Todo
    schema_class = TodoSchema
    create_schema_class = TodoCreate

capability = TodosCapability.as_capability()

Register it programmatically in your ASGI app module:

# my_plugin/app.py
from jmaple.app import create_app
from jmaple.capabilities import register_capability
from my_plugin import models as _models  # noqa: F401  # land Todo on Base.metadata
from my_plugin.capability import capability

register_capability(capability)
app = create_app()

Run with uvicorn my_plugin.app:app. For CLI ops, write a parallel my_plugin/cli.py that registers the capability then re-exposes jmaple.cli:app, and point pyproject.toml's jmaple script at it.

Environment

The server reads its config from .env in the working directory (via pydantic-settings). Copy .env.example and adjust as needed:

  • JMAPLE_DATABASE_URL — SQLAlchemy async URL (defaults to a local SQLite file).
  • JMAPLE_SECRET_KEY — random string for state-vector / session signing.
  • JMAPLE_DEBUGtrue for verbose logs.

Operators of the published PyPI wheel set the same JMAPLE_* env at deploy time.

Release files for jmaple 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jmaple 0.1.0
File Size Uploaded
jmaple-0.1.0.tar.gz 80.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jmaple 0.1.0
File Interpreter ABI Platform
jmaple-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 168.7 kB

Release files / jmaple-0.1.0.tar.gz

Download URL jmaple-0.1.0.tar.gz
Size 80.9 kB
Tags Source
SHA-256 checksum
How to use checksums
1ef0f379cc5fdbc24cd8800fc73a0447e73edd2940ccdff1fa598935f02307a9
BLAKE2b-256 checksum
How to use checksums
df0b73d57686914f38faf9a8cd43849a6c4f3ac970560db646219ae646fadf4e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / jmaple-0.1.0-py3-none-any.whl

Download URL jmaple-0.1.0-py3-none-any.whl
Size 87.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ecd0130bc2e57bef8383b04eaf7e756876bb9c45e9492ed70739a4ddc6c59093
BLAKE2b-256 checksum
How to use checksums
74c51317d1af09a902e433dd77b8c1743747bfb48fd2a73ddf520465ba64926d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page