npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

imap-rest-mailcow

v0.5.0

Published

Stateless REST facade for IMAP, designed as a third-party addon for mailcow-dockerized. Optional Mistral OCR for attachments.

Readme

imap-rest-mailcow

Stateless REST facade for IMAP, designed as a third-party addon for mailcow-dockerized. Authenticates every request against mailcow's Dovecot using HTTP Basic Auth, pools IMAP connections, and exposes a small set of mailbox/message endpoints. Optional Mistral OCR endpoint returns attachment contents as text.

No database of accounts, no credential store, no persistent state — just HTTP Basic Auth in front of IMAP, with a short-lived auth cache.

Overview

Every API call carries Authorization: Basic <base64(email:password)>. On each request:

  1. The credentials hash is looked up in a SQLite (WAL) auth cache.
  2. On miss, the addon attempts an IMAP LOGIN against mailcow's Dovecot. The result (valid/invalid) is cached with a short TTL.
  3. Valid requests acquire a pooled IMAP connection for that user, run the operation against mailcow's Dovecot, and release the connection.

No credentials are ever written to disk. The cache stores only SHA-256 hashes with expiry timestamps.

Prerequisites

  • A running mailcow-dockerized install on the same host.
  • Docker + Docker Compose v2.
  • Optional: a Mistral API key, only if you want the OCR endpoint enabled.

Install

This addon is designed to live outside /opt/mailcow-dockerized/, so mailcow's update.sh (which resets its own working tree) never touches it.

git clone <this-repo> /opt/imap-rest-mailcow
cd /opt/imap-rest-mailcow
cp .env.example .env        # tweak if you want non-defaults
sudo install/setup.sh       # builds image, copies nginx config, restarts nginx-mailcow

After install the API is reachable at https://<your-mailcow-host>/imap-rest/. Browse there in any browser to load the interactive Swagger UI.

The setup script is idempotent — re-run after git pull to upgrade. The only file it drops into mailcow's tree is data/conf/nginx/site.imap-rest.custom, which mailcow preserves across update.sh runs.

If your mailcow lives somewhere other than /opt/mailcow-dockerized, override it: sudo MAILCOW_PATH=/srv/mailcow install/setup.sh.

Use the prebuilt image

Each push to master and each tagged release publishes a container image to GHCR. To use it instead of building locally, edit docker-compose.yml so the imap-rest service uses image: instead of build::

services:
  imap-rest:
    image: ghcr.io/<owner>/imap-rest-mailcow:latest
    # remove or comment out the `build:` block above

Available tags: latest (latest tagged release), master (head of master), <version> (e.g. 0.2.0), sha-<short> (specific commit).

Configuration

All settings are environment variables; see .env.example for the full list. The most common:

| Var | Default | Notes | |---|---|---| | PORT | 3001 | Host listen port | | BIND_ADDR | 127.0.0.1 | Set to 0.0.0.0 only when bypassing mailcow's nginx | | IP_ALLOWLIST | empty | Comma-separated CIDRs / IPs allowed to reach the API. Empty = all. Loopback always allowed. | | TRUST_PROXY | true | Honor X-Forwarded-For from upstream proxies | | IMAP_HOST | dovecot-mailcow | mailcow Dovecot service | | IMAP_PORT | 143 | Plain or STARTTLS port | | IMAP_SECURE | false | true = implicit TLS (993) | | CACHE_TTL_VALID_MS | 300000 | Positive-auth cache TTL | | CACHE_TTL_INVALID_MS | 10000 | Negative-auth cache TTL | | POOL_MAX | 50 | Max live IMAP connections | | POOL_IDLE_MS | 30000 | Idle connection eviction | | TLS_CERT / TLS_KEY | — | Optional HTTPS termination | | LOG_LEVEL | info | pino level | | MAILCOW_NETWORK | mailcowdockerized_mailcow-network | Override if mailcow renames it | | MISTRAL_API_KEY | — | Optional. Enables OCR + AI summarize/draft. | | MISTRAL_OCR_MODEL | mistral-ocr-latest | Pin to mistral-ocr-2512 for stable output | | MISTRAL_OCR_TIMEOUT_MS | 60000 | Outbound OCR request timeout | | MISTRAL_CHAT_MODEL | mistral-small-latest | Used by /v1/ai/summarize and /v1/ai/draft-reply | | MISTRAL_CHAT_TIMEOUT_MS | 30000 | Outbound chat request timeout | | OCR_CACHE_ENABLED | true | Cache OCR results by content hash; no TTL | | OCR_CACHE_MAX_ENTRIES | 1000 | Max cached OCR results before oldest are evicted | | WEBMAIL_ENABLED | true | Mounts the Svelte SPA at /webmail/. Set false to disable. | | WEBMAIL_DIST | ./webmail/dist | Path to the built SPA. Containerized image rebuilds in CI. | | SMTP_HOST / SMTP_USER / SMTP_PASS | — | Reserved. Send is stubbed; future release will wire it up. |

Public exposure

The recommended path is behind mailcow's nginx, which is what install/setup.sh configures. The addon listens only on 127.0.0.1 by default, and nginx-mailcow reaches it via the docker-network alias imap-rest:3001. The addon is published at https://<mailcow-host>/imap-rest/ using mailcow's existing TLS.

Use IP_ALLOWLIST (e.g. IP_ALLOWLIST=10.0.0.0/8,203.0.113.5) to further restrict who can reach the API. Loopback is always allowed so the docker healthcheck still works.

Advanced: bypass mailcow's proxy

Set BIND_ADDR=0.0.0.0 and either configure TLS_CERT/TLS_KEY to terminate TLS in the addon, or front it with your own TLS terminator. This is not the recommended path — never expose the addon directly without TLS.

API docs

Browse to the addon's root path in any browser to load the Swagger UI:

  • Behind mailcow's proxy: https://<mailcow-host>/imap-rest/
  • Direct: http://<host>:3001/

The page loads without authentication; click Authorize and enter your mailcow email + password (use a SOGo App Password if your account has 2FA enabled). The raw OpenAPI document is at /openapi.json on the same base URL.

Webmail UI

The container ships with a Svelte single-page webmail at /webmail/:

  • Behind mailcow's proxy: https://<mailcow-host>/imap-rest/webmail/
  • Direct: http://<host>:3001/webmail/

Polished light + dark themes (auto-detected, user-overridable). Three-pane Gmail-style layout with keyboard shortcuts (j/k next/prev, s star, u toggle read, # trash, c compose, / focus search, Esc close).

AI features (require MISTRAL_API_KEY):

  • Summarize the open message into 3–5 bullets.
  • Draft reply with optional intent ("decline politely", "ask for an extension"…).
  • OCR any image / PDF attachment inline.

Send is intentionally stubbed for v1: the compose modal posts to POST /v1/messages/send which returns 501 Not Implemented with a friendly explanation. SMTP wiring will land in a future release behind the reserved SMTP_* env vars.

To rebuild the SPA locally (only needed if you change webmail/):

npm run build:webmail

Screenshots of the UI live under webmail/test/screenshots/.

Upgrade safety

This addon is upgrade-safe by design:

  • Lives outside /opt/mailcow-dockerized/, so mailcow's update.sh cannot touch it.
  • Joins mailcow's network as external; if mailcow renames the network in a future major version, override MAILCOW_NETWORK in .env.
  • The optional nginx site file (install/site.imap-rest.custom) lives under data/conf/, which mailcow preserves across updates.

To upgrade the addon itself: git pull && sudo install/setup.sh.

Uninstall

cd /opt/imap-rest-mailcow
docker compose down -v
rm -rf /opt/imap-rest-mailcow

If you installed the nginx snippet, also:

rm /opt/mailcow-dockerized/data/conf/nginx/site.imap-rest.custom
docker compose -f /opt/mailcow-dockerized/docker-compose.yml restart nginx-mailcow

Troubleshooting

/health returns 200 but /v1/mailboxes returns 401. The credentials Basic-Auth'd in are not valid mailcow accounts. Check mailcow's SOGo/admin UI to confirm the mailbox exists.

Container can't reach dovecot-mailcow. The mailcow-network external network may have been renamed. Run docker network ls | grep mailcow and set MAILCOW_NETWORK in .env.

OCR endpoint returns 501. MISTRAL_API_KEY is unset. The addon ships with OCR disabled by default. Set the env var in .env and docker compose up -d to enable.

OCR endpoint returns 502. The addon reached Mistral but Mistral rejected our credentials (401), denied access (403), or returned a 5xx. Check the container logs.

API

All authenticated routes require Authorization: Basic <base64(email:password)>. Errors follow RFC 7807 (application/problem+json).

Mailboxes

  • GET /v1/mailboxes
  • POST /v1/mailboxes{path}
  • PUT /v1/mailboxes/:path{newPath}
  • DELETE /v1/mailboxes/:path

Messages

  • GET /v1/mailboxes/:path/messages?page=0&pageSize=20&search=...
  • GET /v1/mailboxes/:path/messages/:uid
  • GET /v1/mailboxes/:path/messages/:uid/raw
  • GET /v1/mailboxes/:path/messages/:uid/attachments/:id
  • GET /v1/mailboxes/:path/messages/:uid/attachments/:id/text — OCR (requires MISTRAL_API_KEY)
  • PUT /v1/mailboxes/:path/messages/:uid/flags{add?,remove?,set?}
  • PUT /v1/mailboxes/:path/messages/:uid/move{path}
  • DELETE /v1/mailboxes/:path/messages/:uid
  • POST /v1/messages/sendstubbed; returns 501 until SMTP is wired

AI (require MISTRAL_API_KEY)

  • POST /v1/ai/summarize{text, maxWords?}{content, model}
  • POST /v1/ai/draft-reply{thread, intent?}{content, model}

Public

  • GET /health
  • GET / — Swagger UI
  • GET /openapi.json — OpenAPI 3.1 document
  • GET /webmail/ — Svelte webmail SPA

:path is URL-encoded to support IMAP namespaces and delimiters.

Attachment OCR

# Plain text (page markdowns joined with "\n\n---\n\n")
curl -u '[email protected]:password' \
  https://mail.example.com/imap-rest/v1/mailboxes/INBOX/messages/42/attachments/2/text

# Full Mistral response (per-page markdown, usage_info, bbox)
curl -u '[email protected]:password' \
  'https://mail.example.com/imap-rest/v1/mailboxes/INBOX/messages/42/attachments/2/text?format=json'

If MISTRAL_API_KEY is unset, the endpoint returns 501 Not Implemented. Attachments above 50 MB return 413 Payload Too Large (Mistral's hard limit). On 429, the addon forwards Mistral's Retry-After header.

OCR results are cached by content hash (sha256 of attachment bytes + model name) in the same SQLite file as the auth cache. Two messages containing the identical attachment share the cache entry, so the second read returns instantly without calling Mistral again. The cache has no TTL — OCR output is deterministic for given (bytes, model) — and is bounded by row count via OCR_CACHE_MAX_ENTRIES (default 1000). Set OCR_CACHE_ENABLED=false to disable.

MCP server

This package ships an optional Model Context Protocol server that exposes the REST API as tools an LLM can call. The MCP server runs as a local subprocess over stdio (the standard MCP pattern) and talks to the REST API over HTTP, so it works against any running imap-rest-mailcow instance — local or remote.

Tools

| Tool | Wraps | |---|---| | list_mailboxes | GET /v1/mailboxes | | create_mailbox | POST /v1/mailboxes | | rename_mailbox | PUT /v1/mailboxes/:path | | delete_mailbox | DELETE /v1/mailboxes/:path | | list_messages | GET /v1/mailboxes/:path/messages | | get_message | GET /v1/mailboxes/:path/messages/:uid | | ocr_attachment | GET .../attachments/:id/text (Mistral OCR) | | flag_message | PUT .../messages/:uid/flags | | move_message | PUT .../messages/:uid/move | | delete_message | DELETE .../messages/:uid |

Configure

The most reliable setup is to point your MCP client at this checkout explicitly with node, rather than assuming imap-rest-mcp is installed globally on PATH.

Claude Desktop / Claude Code

{
  "mcpServers": {
    "imap-rest-mailcow": {
      "command": "node",
      "args": ["/opt/imap-rest-mailcow/bin/imap-rest-mcp"],
      "env": {
        "IMAP_REST_BASE_URL": "http://127.0.0.1:3001",
        "IMAP_REST_USER": "[email protected]",
        "IMAP_REST_PASS": "your-mailcow-password"
      }
    }
  }
}

Codex

Add to your Codex MCP config:

{
  "mcpServers": {
    "imap-rest-mailcow": {
      "command": "node",
      "args": ["/opt/imap-rest-mailcow/bin/imap-rest-mcp"],
      "env": {
        "IMAP_REST_BASE_URL": "http://127.0.0.1:3001",
        "IMAP_REST_USER": "[email protected]",
        "IMAP_REST_PASS": "your-mailcow-password"
      }
    }
  }
}

Kimi CLI

kimi mcp add --transport stdio \
  -e IMAP_REST_BASE_URL=http://127.0.0.1:3001 \
  -e [email protected] \
  -e IMAP_REST_PASS=your-mailcow-password \
  imap-rest-mailcow -- \
  node /opt/imap-rest-mailcow/bin/imap-rest-mcp

Then verify:

kimi mcp test imap-rest-mailcow

Install via npm / npx

You can also run the MCP server without cloning the repo:

npx --yes --package imap-rest-mailcow imap-rest-mcp

Or configure your MCP client to invoke it via npx:

{
  "mcpServers": {
    "imap-rest-mailcow": {
      "command": "npx",
      "args": ["--yes", "--package", "imap-rest-mailcow", "imap-rest-mcp"],
      "env": {
        "IMAP_REST_BASE_URL": "http://127.0.0.1:3001",
        "IMAP_REST_USER": "[email protected]",
        "IMAP_REST_PASS": "your-mailcow-password"
      }
    }
  }
}

If you prefer a global binary, you can also install the package and use:

{
  "mcpServers": {
    "imap-rest-mailcow": {
      "command": "imap-rest-mcp",
      "env": {
        "IMAP_REST_BASE_URL": "http://127.0.0.1:3001",
        "IMAP_REST_USER": "[email protected]",
        "IMAP_REST_PASS": "your-mailcow-password"
      }
    }
  }
}

but that requires imap-rest-mcp to be installed somewhere your MCP client can execute it from.

The MCP server uses static credentials from the env — single-user. It includes Basic Auth on every REST call. Per-call credentials are not supported.

Running tests

Server unit tests (mocked imapflow + mocked undici, no network):

npm install
npm test

Webmail end-to-end tests (Playwright + Chromium against a Vite preview server with mocked API responses):

cd webmail
npm install
npx playwright install chromium
npm run test:e2e

The Playwright config runs serially with a single worker — fine on RAM-constrained hosts. Screenshots land in webmail/test/screenshots/ and on failure under webmail/test-results/.

There is no integration test tier in this project; if you add one, ensure your tests trap "docker compose down -v" EXIT so no Docker state is left behind.

License

MIT — see LICENSE.

This is a fresh project, not a fork. It depends on imapflow (MIT) for IMAP client behaviour.