Skip to main content
Glama
unstko

postfach-mcp

by unstko

postfach-mcp

A self-hosted remote MCP server that gives an AI assistant read-focused access to any IMAP mailbox: search, read, create drafts, light triage. Postfach is German for mailbox.

Deliberately no send, no delete — by design, not by configuration. E-mail is untrusted third-party input; an assistant that reads it can be manipulated by it. This server keeps the blast radius small: the worst a hijacked session can do is file a draft, create a folder or move a message — all of it sits in your mailbox, in plain sight, reversible. A send tool may appear in a later version, but only behind an explicit opt-in flag, unregistered by default. Feature requests to weaken this stance will be declined.

Status

v0.2.0 — in production use by the author against a real IMAP mailbox, with three client paths verified: Claude Code, claude.ai on the web, and the Claude Android app. Still a 0.x: the tool surface and configuration may change between releases. Changes are tracked in CHANGELOG.md.

Related MCP server: imap-mcp

Tools

Tool

Purpose

list_folders

List all folders, optionally with message and unseen counts

folder_status

Message and unseen counts for a folder

list_messages

Newest messages in a folder

list_headers

Page through the header data of a whole folder, oldest first

search_messages

Server-side IMAP search, including arbitrary header matches

get_message

Full message: headers, body, attachment metadata

create_draft

Build an RFC-822 message and file it in the drafts folder — never sends

create_folder

Create a folder — creating only, no deleting, no renaming

mark_read

Set or clear the seen flag

mark_flagged

Set or clear the flagged star

move_messages

Move messages to another folder, reporting the new uids when the server supports UIDPLUS

Messages are addressed by folder + uid; UIDs are per-folder. Reading never sets the seen flag — only mark_read does, when asked.

Installation

Requires Python 3.11+.

pip install git+https://github.com/unstko/postfach-mcp

Or from a clone: pip install . — both install the postfach-mcp command.

Configuration

Everything is environment variables prefixed POSTFACH_MCP_; a commented template is in .env.example. Missing or invalid variables are reported together, each by name.

Variable

Default

Purpose

IMAP_HOST

(required)

IMAP server to connect to

IMAP_USER

(required)

Login name

IMAP_PASSWORD

(required)

Password — use an app password if your provider offers them

IMAP_PORT

993

IMAP over TLS port

DRAFTS_FOLDER

Drafts

Folder that receives created drafts; postfach-mcp check verifies it exists

FROM_ADDRESS

IMAP_USER

From header for drafts, e.g. Your Name <you@example.org>

FROM_ADDRESSES

Comma-separated additional sender identities create_draft may select via its from_address argument; anything not listed here or in FROM_ADDRESS is rejected

DRAFT_FORMAT

text

text writes plain-text drafts; html adds an HTML rendering of the same text as a multipart/alternative part — for clients whose HTML-based composer collapses plain-text line breaks (Spark, for example)

TOKEN

(required for serve)

Bearer token, at least 32 characters (openssl rand -hex 32)

EXTRA_TOKENS

Comma-separated additional bearer tokens, same length rule — give each client its own so one can be revoked without touching the others

HOST

127.0.0.1

Bind address of the HTTP server

PORT

8000

Port of the HTTP server

ALLOWED_HOSTS

127.0.0.1,localhost

Comma-separated Host header allowlist — add the public name your proxy or tunnel uses

ENABLE_SEND

Reserved for a future explicit opt-in; not implemented in v0.1

Running

postfach-mcp check   # probe the IMAP login, list folders, verify the drafts folder
postfach-mcp serve   # run the HTTP server (--host/--port override the environment)

check is the deploy diagnosis: it answers "are the credentials right, and what is the drafts folder actually called on this server?" without starting anything. serve exposes the MCP endpoint at /mcp and an unauthenticated health probe at /api/health.

Connecting clients

Any Streamable-HTTP MCP client that can send an Authorization header works. The two most common ones:

Claude Code

claude mcp add --transport http postfach https://mail.example.org/mcp \
  -H "Authorization: Bearer <token>" --scope user

claude.ai custom connector (web and mobile apps)

claude.ai can talk to this server as a custom connector, which also makes it available in the Claude mobile apps — the connector is configured once and appears there automatically.

Custom connectors normally require OAuth, which this server does not offer. What it relies on instead is the request-header option in the add-connector dialog (authentication "None" plus a static header). At the time of writing Anthropic describes that option as available to a limited set of organizations, so it may not appear for your account — check the dialog before planning around it.

Configuration, and the three pitfalls that cost the author an afternoon:

  • URL: your public https://…/mcp endpoint. Authentication: None. Add a request header Authorization with the value Bearer <token>including the scheme and the space; the value is sent verbatim, so a bare token produces nothing but 401s.

  • A connector's authentication settings cannot be changed later. To rotate a token, delete the connector and create it again. This pairs well with EXTRA_TOKENS: give the connector its own token and it can be revoked without touching your other clients.

  • The connection test in the dialog can fail even when everything is configured correctly (some of its probes are sent without the header). If the server logs show 401s from the dialog but your header is right, deleting and re-creating the connector is faster than debugging.

One consequence for your network setup: connector traffic originates from Anthropic's backend, not from your browser — the server must be reachable from the public internet, a VPN or tailnet is not enough for this path. To keep the exposed surface small you can restrict it to Anthropic's published egress IP range at your proxy or firewall; the bearer token remains the actual authentication either way.

Security model

  • No send, no delete. The server cannot transmit mail or destroy it; those tools do not exist at runtime. Drafts are filed via IMAP APPEND into your drafts folder and stay there until you act on them.

  • Mail content is untrusted. Bodies and headers are returned in structured fields, never interpreted; tool descriptions warn the model that message content is third-party input. Header fields of drafts are validated against CRLF injection.

  • Bearer token on every MCP request (constant-time comparison), minimum 32 characters. The health endpoint is the only unauthenticated route.

  • Host header allowlist (ALLOWED_HOSTS) rejects requests addressed under any other name — DNS-rebinding protection. Behind a proxy or tunnel you must add the public host name, or every request fails with 421.

  • Transport security is your job. The server speaks plain HTTP and binds to localhost by default; put a TLS-terminating reverse proxy, tunnel, or VPN in front of it. Do not expose the port directly.

  • Errors are terse. IMAP failures reach the client as one English sentence; credentials and tracebacks never do.

Limitations

  • One account per server instance.

  • Drafts carry no formatting beyond line and paragraph breaks, and IMAP cannot edit them in place — a changed draft means a new one.

  • Some clients render plain-text drafts through an HTML composer and lose all line breaks (observed in Spark on macOS and Android; webmail shows the same draft correctly). DRAFT_FORMAT=html works around this by adding an HTML alternative part.

  • Attachments are reported as metadata only (name, type, size); their content is not retrievable.

  • Message bodies are capped at 50,000 characters, list/search results at 100 messages per call, list_headers at 500 headers per page; drafts at 500,000 characters.

  • Lossless export of raw messages is out of scope — for backups, use a dedicated tool such as mbsync or offlineimap.

  • HTML-only messages are converted to text with a deliberately simple converter — layout is lost, links are kept visible.

  • mark_read, mark_flagged and move_messages trigger an expunge in the source folder (imap-tools behavior; a move is IMAP-internally copy + delete + expunge). Harmless for this server, which never sets the deleted flag itself, but it also purges messages other clients have marked deleted in that folder.

Development

python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest
.venv/bin/python -m ruff check . && .venv/bin/python -m mypy

Tests run entirely without network access — enforced by the test suite itself, which fails any accidental socket connect.

License

MIT


Built with assistance from Claude Code.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables LLMs to read, search, and manage emails via IMAP with secure, read-only access to email accounts.
    6
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Read-only MCP server for IMAP email access, enabling AI agents to read, search, and monitor email without sending or deleting messages.
    31
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables LLM clients to read and search email via IMAP with tools for listing folders, searching messages, and fetching message content. It supports pagination, snippets, and thread context, and is designed for local AI workflows.
    10
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Enables AI assistants to read and interact with a user's email inbox via IMAP, fetching recent emails and reading their full content.
    8
    -