imap-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@imap-mcp-servershow me my unread emails in the inbox"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
imap-mcp-gateway
A self-hosted HTTP gateway that puts authentication, per-user isolation, and
encrypted account storage in front of the upstream imap-mcp-server MCP tool
set. One Docker Compose service (imap-gateway) serves a single-page web
application (one Vue 3 app, two role surfaces — /admin and /user), JSON
APIs, and an MCP Streamable HTTP endpoint at /mcp. The gateway is Python
(FastAPI); for tool execution it spawns a child process running the vendored,
pinned imap-mcp-server@2.2.0 Node package — one child per MCP session.
One-time secrets — read this first
The single most destructive mistake is losing a key. Three secrets are displayed exactly once — in the browser or in the first-boot container log — and no endpoint can ever re-display them. Copy each at the moment it appears; reloading the page or navigating away discards it for good.
Secret | Format | Shown | Where it lives |
Admin key |
| once, in the first-boot log line | scrypt hash in |
User login key |
| once, in the browser, at user creation or rotation | scrypt hash in |
Account encryption key (AEK) | 64 lowercase hex characters | once, in the browser, at activation or first account add | wrapped under |
Browser storage holds only the theme preference under localStorage key
imapgw.theme. No token or secret is ever stored in the browser; the CSRF token
lives in memory only and is re-fetched after a reload. The theme toggle
(light/dark) in the top bar persists under that key.
Full lifecycles, regeneration, and recovery: docs/keys-and-security.md.
Related MCP server: email-mcp
Quickstart
mkdir -p data/config data/users data/mail
docker compose up --build -d
docker compose logs imap-gatewayThe host directories must be writable by uid 1000 (the container runs as app,
uid 1000) — create them as that user or chown 1000:1000 them.
On the first boot — when ./data/config/admin-key.hash does not exist — the
gateway mints the admin key and prints it exactly once, on one log line:
<timestamp> INFO [auth] admin key created — store it now; it will not be shown again: adm_...Copy the adm_... value from that line. It exists nowhere else. The same boot
then logs [auth] hash file: /data/config/admin-key.hash and
[config] wrap key created. Later boots log [auth] existing key found and
never re-display the key. If you miss it, reset it:
docs/deployment.md.
Then check the stack is up:
curl -sS http://localhost:3030/healthz
# {"status":"ok"}The published port defaults to 3030. Override it for both host and
container with GATEWAY_PORT=… (e.g. GATEWAY_PORT=8080 docker compose up -d).
The bare image's own default is HTTP_PORT=3000 — that value only applies when
you run the image directly, outside Compose.
Full procedures (data layout, restart/reset, backup, upgrade): docs/deployment.md.
URLs
The published default port is 3030. GET / is not an entry point — it
returns a JSON 404 ({"error":"not_found","path":"/"}); the Vue router
redirects / to the admin home inside the SPA, so your first visit must be
/admin or /user.
URL | Purpose |
| Admin surface — dashboard, users, keys |
| User surface — accounts, encryption, settings |
| Deep links into the SPA (server serves the shell) |
| Admin JSON API — admin key (Bearer or session cookie) |
| User JSON API — browser session cookie + CSRF token |
| MCP Streamable HTTP endpoint — user login key |
| Health probe; returns |
| Immutable hashed build assets |
The web app is one Vue 3 single-page application. /admin and /user are two
entry points into it, and every deep link below them works: the server answers
those paths with the app shell and the app resolves the route in the browser.
The app talks to the JSON APIs above; there are no server-rendered pages.
The stack speaks plain HTTP. For anything beyond localhost, put a TLS-terminating reverse proxy in front: docs/security.md.
Using the web app
Signing in
Admin: open
/admin, enter the Admin key (password field), click Sign in. Wrong key → "Sign-in failed. Check the key and try again."; repeated failures → "Too many sign-in attempts. Please wait before trying again." with a countdown (429).User: open
/user, enter the Login key, click Sign in.
Sessions last 12 hours; a gateway restart ends them. An expired session drops you to the owning surface's login with the notice "Your session has expired. Please sign in again."; a manual sign-out shows "You have been signed out." A visitor who is already signed in is forwarded to their home when they open a login page.
Admin surface
Nav: Dashboard, Users, New user; theme toggle in the top bar; Sign out. Nav is hidden on the login page.
Dashboard (
/admin): cards Users (total, "registered in total"), Active, Disabled, and API health (badge OK/Unavailable, captionGET /api/admin/health); Create user CTA; empty state "No users yet".Users (
/admin/users): table columns Name / Status / Key generation / Accounts / Created; status badge "Active" on active users; Refresh button ("The user list was refreshed."); row link Manage → detail.New user (
/admin/users/new): field "Display name"; submit "Create user". Success shows a one-time "One-time login key" card with KeyDisplay (label "Login key", copy button, confirm "I have saved it", warning "Shown once — it will never be shown again. Copy and store it now."). The value is zeroed on confirm or unmount and is never retrievable again.User detail (
/admin/users/:id): Details card (Name, Status, Key generation, Accounts, Created, plus registry id); "New login key" card (rotated key shown once, same KeyDisplay/ack); Actions card: "Rotate login key" ("A new login key was minted. The previous key no longer works."), "Disable user"/"Enable user" (hint: "Disabling a user keeps the key but blocks sign-in."), and "Delete user" — destructive, behind a type-to-confirm dialog titled "Delete user": "This removes the user and their whole directory. It cannot be undone. Type the user's exact name to confirm." The Delete button stays disabled until you type the user's exact name.
User surface
Shell title "My account". Nav: Accounts (/user), Add account
(/user/accounts/new), Settings (/user/settings); theme toggle; Sign
out.
Accounts (
/user): profile header card (Name, Key generation, Account encryption badge Active/Not active fromaek_active); account table columns Name / Host / User / TLS / SMTP (Host rendershost:port; TLS badge "TLS"/"Plain"; SMTP = host or "—"); row Edit / Delete (type-to-confirm: "This removes the account and its stored credentials. It cannot be undone. Type the account's exact name to confirm."); Refresh and Add account CTA; empty states "No accounts yet" / "Add your first mail account. The credentials are stored encrypted and are never shown again."Add account (
/user/accounts/new): page note "The credentials are encrypted before they are stored and are never shown again." Sections IMAP / Sending / SMTP (optional) with fields Account name, Host, Port (default 993), User name, Password, TLS (default on), Allow STARTTLS; E-mail address, Save sent messages to the Sent folder, Sent folder, Default BCC ("One address, or several separated by commas."), Allowed senders (same hint); SMTP host, SMTP port (default 587), SMTP over TLS, SMTP user name, SMTP password. Submit "Add account". A one-time Account encryption key is shown only when the create response carries it (first add activates the AEK; confirm "I have saved it"); a 409 means encryption is already active.Edit account (
/user/accounts/:id): page note "Credentials are write-only: leave them blank to keep the stored values." Non-credential fields are prefilled (name, host, port, user, TLS, STARTTLS, email, save_to_sent, sent_folder, default_bcc, allowed_from, SMTP host/port/secure); credential inputs (Password, SMTP user name, SMTP password) are always blank ("Leave blank to keep the stored password." etc.). Switches "Clear the stored IMAP credential" and "Clear the stored SMTP credential" (the latter disabled unless an SMTP host is present) send an empty value on purpose. Submit "Save changes" → "The account was updated."Settings (
/user/settings): Profile card (Name, Created, Key generation, Account encryption badge). "Activate account encryption" button shown only when encryption is not yet active — the response AEK is displayed once. MCP tool calls require an active AEK (the server answers "initialize account encryption in the web UI first"). "Regenerate login key" → one-time "New login key" card, warning "Shown once — it will never be shown again. The previous key stops working immediately. Copy and store it now." Sign out.
Sessions, theme, and browser storage
Browser sessions use cookies
imapgw_admin/imapgw_user(HttpOnly, 12 h TTL). The user API requires the cookie plus anX-CSRF-Tokenheader;Authorization: Beareron/api/me/*gets 401.The only browser storage entry is the theme preference under
imapgw.theme; no secret is ever stored. The CSRF token is in memory only.Theme: light/dark toggle in the top bar, persisted under
imapgw.theme.
For the exhaustive task list, see docs/user-guide.md.
Key model
Three kinds of secret, each with a different owner and lifetime. Full detail in docs/keys-and-security.md.
Admin key (adm_ + 43 characters) is the operator's root credential. The
first boot prints it to the logs once; only its hash is ever stored. It
authenticates the admin surface (/admin, through the admin API) and the
admin API itself, and it never works on /mcp.
User login key (usr_ + 43 characters) belongs to one user. The admin
sees it once, at creation or rotation; the user can rotate it themselves. It
logs the user into /user and authenticates their MCP sessions at /mcp.
Rotating it kills the old key immediately.
Account encryption key (AEK) — 64 lowercase hex characters — encrypts a user's IMAP account records at rest. The user activates it once and stores it; the admin never sees it. MCP tool calls require an active AEK.
A login key and an AEK are each displayed once, in the browser, and are never retrievable afterwards. Copy them at the moment they appear; reloading the page discards them for good.
Why Node.js ships in the image
The gateway does not re-implement the upstream tools. The image carries a
pinned Node.js 22.12 runtime whose only job is to run the vendored
imap-mcp-server@2.2.0 package as a child process of the Python gateway
(node /opt/vendor/npm/node_modules/imap-mcp-server/dist/index.js, one per
MCP session). This is a deliberate design decision: shipping the upstream
package was chosen over re-implementing its 40 tools in Python. The fuller
language-boundary statement lives in
docs/security.md; see also
docs/mcp-clients.md for the tool surface clients see.
The web app is built from TypeScript sources in frontend/, so the image
build also runs a Node toolchain — at build time only, in the web-build
stage: npm ci from the committed lockfile, then the bundle build, whose
last step is a secret/external-URL audit that fails the image build on a
violation. The runtime image receives only the built frontend/dist
(hashed assets plus index.html) — no node_modules, no frontend sources.
The Node binary that ships in the runtime image is there for the tool host
above, not for the web app. docs/security.md
has the supply-chain detail.
Documentation
docs/deployment.md — compose reference, data layout, first-boot key capture, restart/reset, backup and restore, upgrade pins
docs/keys-and-security.md — the three keys,
wrap.key, regeneration semanticsdocs/mcp-clients.md — connect Claude Desktop, Cursor, or any MCP client
docs/user-guide.md — day-to-day admin and user tasks
docs/security.md — runtime posture, redaction, rate limits, residual risks
docs/acceptance-checklist.md — requirement checklist with the dated gate evidence
docs/README.md — documentation index and the e2e release gate
This server cannot be deployed
Maintenance
Related MCP Connectors
Hosted email for AI agents: create inboxes, send, receive, and reply over MCP with scoped API keys
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Programmable email inbox for AI agents — JMAP, PoW auth, stdio MCP server.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
Related MCP Servers
- AlicenseAqualityAmaintenanceAn MCP server that gives AI agents permission-gated, audit-logged access to private email providers (Proton Mail via Bridge and plain IMAP), running locally with OAuth-based authentication and human-controlled escalation for destructive operations.6688 npm15MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP-compatible AI clients to access and manage a mailbox over IMAP and SMTP, including reading, reviewing, and summarizing email while keeping credentials outside the repository.MIT
- AlicenseNot gradedqualityBmaintenanceConnects existing IMAP/POP3/SMTP email accounts to AI assistants via MCP, with a web client for managing accounts and downloading attachments.19 npm1MIT
- AlicenseNot gradedqualityAmaintenanceGives AI agents a self-hosted Cloudflare mailbox they can use to receive, search, wait for, read, send, reply to and forward email, with attachments and threads, over MCP or REST behind a single bearer token.5MIT