Not production ready. This is a v0.1 scaffold. Some JMAP methods are still missing (see Status), there are no security audits, and the API and storage formats may change without notice. Use it for development, testing, and self-hosted experiments only.
IMAP / SMTP / ManageSieve to JMAP gateway. Lets any JMAP client talk to Gmail, your hosting provider's mailbox, or any RFC 3501 IMAP server.
Front: JMAP for Mail (RFC 8620 + RFC 8621). Back: IMAP4rev1, SMTP Submission, ManageSieve.
v0.1. Working:
- JMAP
Sessionwith the full capability table - Batched
methodCalls, back-references (#ref) Mailbox/getEmail/get,Email/query,Email/set(create drafts, flag/mailbox updates, destroy)EmailSubmission/get|query|changes|setIdentity/getVacationResponse/get|setContactCard/get|query|setvia CardDAV- IMAP via imapflow, per-account connection pool
- ManageSieve (RFC 5804), used by
VacationResponse - SMTP submission (nodemailer)
- Auth: PLAIN, LOGIN, XOAUTH2. HMAC session tokens. AES-256-GCM vault
- better-sqlite3 state store
- EventSource transport (SSE endpoint, ping keepalive)
- Docker image + compose files (prod, integration)
- 28 unit tests, jmap-test-suite runner
Not done yet: Email/copy, Email/import, Thread/get, IMAP IDLE -> SSE
publishing (the SSE endpoint exists but never emits state changes),
WebSocket transport (advertised in the Session capability, no handler yet).
You need Docker. Two ways to run it:
mkdir legacy-proxy && cd legacy-proxy
cat > .env <<EOF
VAULT_KEY=$(openssl rand -base64 32)
SESSION_HMAC_KEY=$(openssl rand -base64 32)
EOF
chmod 600 .env
curl -fsSLo providers.json https://raw.githubusercontent.com/bulwarkmail/legacy-proxy/main/providers.example.json
curl -fsSLo compose.prod.yml https://raw.githubusercontent.com/bulwarkmail/legacy-proxy/main/compose.prod.yml
$EDITOR providers.json # point the `generic` entry at your IMAP/SMTP host
docker compose -f compose.prod.yml up -dcurl http://localhost:8080/healthz returns ok if it's up. JMAP clients
connect to http://localhost:8080/.well-known/jmap.
git clone https://github.com/bulwarkmail/legacy-proxy.git
cd legacy-proxy
npm run setup
docker compose up -dnpm run setup writes .env and providers.json. It won't clobber
existing files. Pass -- --force to overwrite.
Trade IMAP credentials for a JMAP session token:
curl -s http://localhost:8080/api/login \
-H 'content-type: application/json' \
-d '{"username":"you@example.com","password":"...","provider":"generic"}'Use the returned token as Authorization: Bearer ... on JMAP requests.
Gmail wants an App Password
(2FA must be on). Use "provider": "gmail". XOAUTH2 works too if you bring
your own tokens.
The proxy serves plain HTTP. Put Caddy, Traefik, or nginx in front of it
and set PUBLIC_URL to the URL clients see. The Session resource bakes
URLs from PUBLIC_URL, so a wrong value breaks every JMAP client silently.
| env var | default | notes |
|---|---|---|
PORT |
8080 |
HTTP listen port |
PUBLIC_URL |
http://localhost:$PORT |
URL clients see; baked into Session |
DATA_DIR |
/data |
SQLite and blob cache |
VAULT_KEY |
required | base64 32-byte AES-GCM key |
SESSION_HMAC_KEY |
required | base64 32-byte HMAC-SHA-256 key |
DEFAULT_PROVIDER |
generic |
provider key when /api/login omits |
PROVIDERS_FILE |
/etc/legacy-proxy/providers.json |
provider catalogue |
LOG_LEVEL |
info |
pino level |
providers.example.json ships Gmail and a generic
$IMAP_HOST/$SMTP_HOST/$SIEVE_HOST template.
npm test # unit tests
npm run test:integration # needs compose.test.yml; gated by RUN_INTEGRATION=1
npm run test:compliance # jmap-test-suite against a live proxy
npm run test:alltest:compliance clones jmap-test-suite
into vendor/jmap-test-suite/ and runs it against PROXY_URL. Allow-listed
upstream failures live in test/compliance/known-failures.txt, mostly
things the IMAP server can't offer (e.g. queryChanges without CONDSTORE).
src/
server.ts fastify bootstrap
jmap/ session, router, capabilities, methods
imap/ imapflow pool, fetcher, search compiler
smtp/ nodemailer submission
sieve/ ManageSieve client, vacation
auth/ session tokens, credential vault, providers
mapping/ IMAP <-> JMAP id codecs, body structure, flags
state/ SQLite store, opaque state strings
push/ eventsource, websocket
AGPL-3.0