Skip to main content
Glama
monk24215

mailcow-mcp-server

by monk24215

mailcow-mcp-server

An MCP server that gives Claude direct access to a Mailcow install and the mail host underneath it — built for the part of self-hosted email that actually hurts: sending reputation.

Wrapping the Mailcow API is the easy half. The useful half is the analysis layer, which reads Postfix delivery logs and answers the question you actually have: is a provider unhappy with us, and if so, why, and what do we do about it?

> Give me a reputation snapshot for the last 24 hours.

Verdict: HEALTHY
1,265 delivery attempts across 606 recipients at major providers
Yahoo/AOL 344 · Microsoft 87 · Apple 61 · Google 60 — all 100% delivered
Queue empty · 0 bans · 0 self-throttled · Rspamd 0 rejects

⚠️ Read this before installing

This server hands a language model administrative control of a mail server, and some of the text it reads is written by strangers.

Untrusted input reaches tools that can delete things. mailcow_get_queued_message returns third-party message bodies. reputation_delivery_breakdown parses rejection strings written by remote mail servers. Both are attacker-influenced. The same session also has tools that can delete a mailbox, drop the queue, or rotate a DKIM key.

Two guardrails exist and you should not remove them:

  • Irreversible operations refuse without confirm: true — deleting a domain, mailbox, alias, DKIM key or quarantine item, and flushing or dropping the queue. The refusal tells the model to show you the scope first.

  • mailsrv_run allow-lists read-only commands (docker ps/logs/inspect, postqueue -p, rspamc stat, cat/grep/tail, df/free/uptime, dig, systemctl status). Everything else — restarts, postsuper, package installs, config edits — requires confirm: true.

If you want a lower-risk setup, issue a read-only Mailcow API key. Every read tool keeps working; the management tools fail cleanly with a message explaining why.


Related MCP server: ai-ssh-mcp

What's in it

Reputation analysis — the reason this exists.

Tool

Answers

reputation_snapshot

"How are we doing right now?" Containers, queue, delivery outcomes, self-throttling, Rspamd, bans → a verdict of healthy / watch / degraded / critical.

reputation_delivery_breakdown

"Who's unhappy and what did they say?" Per-recipient delivery outcomes grouped by mailbox provider, with each remote MTA's rejection text classified.

reputation_warmup_status

"Can we send more yet?" Per-provider acceptance against configured rate limits, returning increase / hold / reduce / stop for each.

reputation_auth_audit

"Is our DKIM actually valid?" Compares the key Mailcow signs with against what DNS publishes, plus SPF/DMARC presence and auth failures in the log.

Mailcow administration — 19 tools covering domains, mailboxes, aliases, DKIM, rate limits, allow/block policy, the queue, quarantine, and netfilter. Plus mailcow_api_request, a raw passthrough for any route without a dedicated tool, so you're never boxed in.

Mail host access — 6 SSH-backed tools for Postfix queue inspection, Rspamd stats, deep log search beyond Mailcow's Redis retention, host health, DNS/PTR checks, and arbitrary commands.

Full tool list in Tools below.


Requirements

  • Node.js 18+

  • A Mailcow install (developed against 2026-07a)

  • A Mailcow API key — read-write for management, read-only if you'd rather not grant that

  • Optional: SSH access to the mail host with Docker permissions, for the mailsrv_* tools and deeper log history


Install

git clone https://github.com/YOUR-USER/mailcow-mcp-server.git
cd mailcow-mcp-server
npm install
npm run build
cp .env.example .env      # fill it in
node scripts/selftest.mjs

selftest.mjs verifies the API key, checks whether it's read-write or read-only, confirms the queue and Postfix logs are readable, and — if SSH is configured — proves Docker access and resolves the Mailcow container names. Run it before registering. Every failure mode below is easier to diagnose there than through a chat client.

Getting a Mailcow API key

Mailcow UI → System → Configuration (/admin/system) → Access → Admins tab → scroll to Read-Write Access:

  1. Tick Activate API, hit Save — the key field populates

  2. Add the public IP of the machine running this server to "Allow API access from these IPs/CIDR network notations"

That second step causes most 401s. The IP that needs allow-listing is wherever the MCP server runs — usually your desktop, not the mail server. If your ISP rotates your address, either use a CIDR range or tick Skip IP check for API (which makes the key alone sufficient — treat it accordingly).

SSH setup

Generate a dedicated key rather than reusing your interactive one — it can be revoked independently and shows up distinctly in authorized_keys:

ssh-keygen -t ed25519 -N "" -C "mailcow-mcp" -f ~/.ssh/mailcow_mcp
ssh-copy-id -i ~/.ssh/mailcow_mcp.pub root@your-mail-host

-N "" sets an empty passphrase. An encrypted key fails with "Encrypted private OpenSSH key detected" — this server talks to the key file directly and never consults ssh-agent, so a key your normal ssh opens silently via the agent will still fail here.

The SSH user needs Docker access (root, or a member of the docker group). Note that docker-group membership is effectively root — what a dedicated user buys you is a separate audit trail and independent revocation, not reduced privilege.

Without SSH the server still works. You lose the mailsrv_* tools and log history is capped at what Mailcow keeps in Redis (a few thousand lines).


Configuration

Variable

Required

Notes

MAILCOW_BASE_URL

yes

Mailcow UI origin, no trailing slash

MAILCOW_API_KEY

yes

Read-write for management tools; read-only works for everything else

MAIL_SSH_HOST

no

Enables mailsrv_*. Prefer the IP over a hostname — a Cloudflare-proxied hostname won't reach SSH

MAIL_SSH_USER

no

Default root

MAIL_SSH_PORT

no

Default 22

MAIL_SSH_KEY_PATH

no

Absolute path. OpenSSH format — a PuTTY .ppk won't load

MAIL_SSH_KEY_PASSPHRASE

no

Only if the key is encrypted

MAIL_SSH_PASSWORD

no

Alternative to key auth

Note the server itself does not read .env — that file exists for selftest.mjs. At runtime, credentials come from your MCP client's config.

Registering

Claude Desktop — Settings → Developer → Edit Config:

{
  "mcpServers": {
    "mailcow": {
      "command": "node",
      "args": ["/absolute/path/to/mailcow-mcp-server/dist/index.js"],
      "env": {
        "MAILCOW_BASE_URL": "https://mail.example.com",
        "MAILCOW_API_KEY": "your-key",
        "MAIL_SSH_HOST": "203.0.113.10",
        "MAIL_SSH_USER": "root",
        "MAIL_SSH_KEY_PATH": "/home/you/.ssh/mailcow_mcp"
      }
    }
  }
}

On Windows, escape backslashes: "C:\\Users\\you\\.ssh\\mailcow_mcp". Then fully quit the app from the system tray — closing the window leaves the server process running with the old build.

Claude Code — one command:

claude mcp add mailcow -s user \
  -e MAILCOW_BASE_URL=https://mail.example.com \
  -e MAILCOW_API_KEY=your-key \
  -e MAIL_SSH_HOST=203.0.113.10 \
  -e MAIL_SSH_USER=root \
  -e MAIL_SSH_KEY_PATH=/home/you/.ssh/mailcow_mcp \
  -- node /absolute/path/to/mailcow-mcp-server/dist/index.js

Two ideas worth understanding

Everything useful in the analysis layer rests on these.

Recipients, not attempts

Postfix retries a deferred address for days. One permanently refused subscriber can generate 150+ delivery attempts. Count attempts and a dozen dead addresses look like a provider-wide outage:

Attempts

Unique recipients

usa.net

169

1

orange.fr + wanadoo.fr

468

9

hetnet.nl

97

2

Read as attempts, that's a 56% deferral rate and an urgent French deliverability crisis. Read as recipients, it's about twenty dead addresses that need suppressing.

Every tool reports both. unique_recipients and delivery_rate are the real numbers; attempts and attempts_per_recipient exist to expose retry storms. No verdict is issued below 20 recipients — the tools return insufficient_data rather than a percentage computed from noise.

Failure categories, not a bounce rate

An aggregate bounce rate tells you nothing actionable. These do:

Category

Severity

Means

Do

blocklisted

critical

An RBL is named in the response

Delist first — sending more deepens it

reputation_block

critical

5.x policy refusal; the provider has decided about you

Stop to that provider, fix the cause

auth_failure

critical

SPF/DKIM/DMARC failed at the receiver

Run reputation_auth_audit

throttled

warning

4.x rate signal

Slow down — volume is ahead of your standing

connection_error

warning

Reachability, not reputation

Usually transient

tls_error

warning

Certificate or handshake

Check your TLS config

greylisted

info

Normal; retries succeed

Nothing

user_unknown

info

List hygiene

Suppress — a high rate causes reputation damage

mailbox_full

info

Recipient-side

Nothing

domain_error

info

Dead domain

Suppress

Two rules the classifier enforces that are easy to get wrong:

The remote's SMTP code beats Postfix's DSN. Postfix records a connect-stage refusal as a deferral with a 4.x DSN even when the server plainly said 554. Trusting the DSN downgrades a hard reputation rejection into a throttle and buries it.

A 4.x response is never a permanent block. Gmail's 421-4.7.28 throttle contains the words "unusual rate of unsolicited mail" — matched naively, that reads as a hard block and sends you chasing the wrong fix. Blocklist mentions outrank both rules, because a deferral citing an RBL is still an RBL problem.


Tools

reputation_snapshot · reputation_delivery_breakdown · reputation_warmup_status · reputation_auth_audit

mailcow_status · mailcow_list_domains · mailcow_list_mailboxes · mailcow_list_aliases · mailcow_get_dkim · mailcow_get_logs · mailcow_get_queue · mailcow_get_queued_message · mailcow_get_quarantine · mailcow_get_ratelimits · mailcow_get_policy · mailcow_get_fail2ban · mailcow_get_rspamd_actions

mailcow_manage_domain · mailcow_manage_mailbox · mailcow_manage_alias · mailcow_manage_dkim · mailcow_manage_queue · mailcow_manage_quarantine · mailcow_set_ratelimit · mailcow_manage_policy · mailcow_edit_fail2ban · mailcow_api_request

mailsrv_postfix_queue · mailsrv_rspamd_stats · mailsrv_mail_log · mailsrv_host_health · mailsrv_dns_check · mailsrv_run

Every tool carries a full description with argument types, return schema, and worked examples — your client will show them.


Things it's good at

"Gmail deferrals are up — what exactly are they saying?"
"Does the DKIM record in DNS still match what Mailcow is signing with?"
"Are we clear to raise warmup volume this week?"
"The queue is at 4,000 — what's stuck and why?"
"Which addresses are being retried into the ground? I want a suppression list."
"Set the daily send limit on this domain to 2,000."

Troubleshooting

401 from the Mailcow API — Either the key is wrong or the source IP isn't allow-listed. It's usually the IP, and it's the IP of this machine, not the mail server. A read-only key also returns 401 on write routes; selftest.mjs distinguishes these.

Cannot read SSH key at ~/.ssh/... — Use an absolute path. ~ expansion depends on HOME/USERPROFILE being set, which isn't guaranteed under a GUI-launched process.

Encrypted private OpenSSH key detected — The key has a passphrase. Either set MAIL_SSH_KEY_PASSPHRASE or generate a passphrase-free key for this purpose. ssh-agent doesn't help here.

Tools show old behavior after an update — dist/ is what runs. Run npm run build, then fully restart the client. The tests import from dist/, so a passing test suite proves the build is current and points at the client process instead.

No running container matching "postfix-mailcow" — The SSH user can't reach Docker, or Mailcow isn't up. Check docker ps as that user.

Quarantine tools return nothing — Mailcow disables quarantine until you set retentions per mailbox and a maximum size under quarantine settings. Expected on a send-focused install.


Testing

npm test                      # parser + classifier, then server boot and guard checks
node scripts/parser-test.mjs  # 26 cases, offline — includes verbatim rejection strings from live logs
node scripts/smoke-test.mjs   # boots over stdio, lists tools, verifies the confirm guards fire
node scripts/selftest.mjs     # live connectivity

The classifier tests carry real rejection strings from KPN, MailChannels, Cisco IronPort, Orange, Rackspace, Gmail, Comcast and Cloudflare. Each one was misclassified at some point; they're pinned so it stays fixed.

Contributions welcome, and one kind especially: if your MTA gets told something this classifier mishandles, open an issue with the raw rejection string. That's the highest-value contribution to this project and it costs you a copy-paste.


Limitations

  • Developed and tested against one Mailcow install (2026-07a). Container naming, log formats and API surface vary across versions.

  • Log parsing assumes standard Postfix format from the postfix-mailcow container.

  • Complaint rate is invisible. The recipient hitting "spam" never reaches your MTA. For volume sending, Google Postmaster Tools is mandatory alongside this — it measures the one threshold nothing here can see.

  • The provider map covers major consumer mailbox providers; anything unmapped groups by domain.

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables natural language SSH server management via Claude Code, allowing users to read logs, check services, run commands, and transfer files across multiple servers.
    6
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables Claude AI to execute commands across multiple remote servers via SSH, with TOTP 2FA authentication and 29 built-in MCP tools for server management.
    -
  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables Claude to manage a Mailcow mail server and analyze sending reputation by reading Postfix logs, providing tools for administration, reputation snapshots, and deliverability insights.
    -