Skip to main content

A pluggable JMAP server framework built on FastAPI.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jmaple-0.1.0.tar.gz (80.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jmaple-0.1.0-py3-none-any.whl (87.8 kB view details)

Uploaded Python 3

File details

Details for the file jmaple-0.1.0.tar.gz.

File metadata

  • Download URL: jmaple-0.1.0.tar.gz
  • Upload date:
  • Size: 80.9 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for jmaple-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1ef0f379cc5fdbc24cd8800fc73a0447e73edd2940ccdff1fa598935f02307a9
MD5 62db9c3bc3a26ac90ccbd5e6454a5cca
BLAKE2b-256 df0b73d57686914f38faf9a8cd43849a6c4f3ac970560db646219ae646fadf4e

See more details on using hashes here.

File details

Details for the file jmaple-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: jmaple-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 87.8 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for jmaple-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ecd0130bc2e57bef8383b04eaf7e756876bb9c45e9492ed70739a4ddc6c59093
MD5 aaa639b08bb2c7a145e83c1a66873748
BLAKE2b-256 74c51317d1af09a902e433dd77b8c1743747bfb48fd2a73ddf520465ba64926d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page