mcp-server-stalwart
MCP server for Stalwart mail server via JMAP
Documentation
mcp-server-stalwart
MCP server for Stalwart Mail Server. Provides email operations (search, read, send, delete) via JMAP and optional admin API access for server management.
Requirements
- Rust (2024 edition)
- A Stalwart mail server with JMAP enabled
Build
cargo build --releaseBinary is output to `target/release/mcp-server-stalwart`.
Configuration
The server connects via stdio and is configured through environment variables.
Required
| Variable | Description |
|---|---|
| `JMAP_SESSION_URL` | JMAP session endpoint (e.g. `https://mail.example.com/jmap/session`) |
| `JMAP_USERNAME` | JMAP account email address |
| `JMAP_PASSWORD` | JMAP account password |
Optional (other mailboxes)
Switch tools onto another mailbox with `account` (e.g. `hello@codechap.com`) without needing the admin API.
| Variable | Description |
|---|---|
| `JMAP_SECRETS_FILE` | Path to a mailman4 `secrets.toml` (`[passwords]` table of `"email" = "password"`) |
| `JMAP_ACCOUNTS` | Inline `email=password;other@host=password` list (overrides file on clash) |
Optional (admin API)
| Variable | Description |
|---|---|
| `STALWART_ADMIN_URL` | Admin API base URL — `https://mail.example.com` or `https://mail.example.com/api` (both accepted; `/api` is normalized) |
| `STALWART_ADMIN_USER` | Admin username (default: `admin`) |
| `STALWART_ADMIN_PASSWORD` | Admin password (different principal from mailbox passwords) |
Password gotcha (learned the hard way)
| Secret | Used for | NOT used for |
|---|---|---|
| `JMAP_PASSWORD` / mailbox password | SMTP submission (`smtp://user:pass@host:587`), JMAP, IMAP for that account | Admin API |
| `STALWART_ADMIN_PASSWORD` | Admin API (`/api/principal`, `/api/logs`, …) | App mailer DSNs |
If an app (invoice mailer, WordPress, etc.) is configured with the admin password as the SMTP secret, Stalwart returns `535 Authentication credentials invalid` and nothing is queued. `check_sent` will correctly show zero submissions. Use `verify_account_auth` to test credentials before chasing delivery.
Claude Code MCP config
{
"mcpServers": {
"stalwart": {
"command": "/path/to/mcp-server-stalwart",
"env": {
"JMAP_SESSION_URL": "https://mail.example.com/jmap/session",
"JMAP_USERNAME": "you@example.com",
"JMAP_PASSWORD": "your-password",
"JMAP_SECRETS_FILE": "/home/you/.local/share/mailman4/secrets.toml",
"STALWART_ADMIN_URL": "https://mail.example.com",
"STALWART_ADMIN_PASSWORD": "admin-password"
}
}
}
}Tools
get_mailboxes
List all mailboxes/folders with message counts.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `account` | string | no | Mailbox to list (e.g. `hello@codechap.com`) |
create_mailbox
Create a new mailbox/folder.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Mailbox name |
| `parent_id` | string | no | Parent mailbox ID for nesting (top-level if omitted) |
| `role` | string | no | Standard role: `archive`, `drafts`, `inbox`, `junk`, `sent`, `trash` |
search_emails
Search emails with filters. Returns email IDs -- use `get_emails` to read full content.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `query` | string | no | Text to search across subject, body, from, to |
| `from` | string | no | Filter by sender address |
| `to` | string | no | Filter by recipient address |
| `subject` | string | no | Filter by subject text |
| `mailbox_id` | string | no | Restrict to a specific mailbox |
| `position` | number | no | Pagination offset (default 0) |
| `limit` | number | no | Max results (default 10, max 50) |
| `account` | string | no | Mailbox to search (e.g. `hello@codechap.com`) |
get_emails
Get full email content by IDs. Returns subject, from, to, date, body text, and metadata.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `ids` | string[] | yes | List of email IDs to retrieve |
| `account` | string | no | Mailbox that owns these emails |
delete_emails
Permanently delete emails by ID. Cannot be undone.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `ids` | string[] | yes | List of email IDs to delete |
| `account` | string | no | Mailbox to delete from |
send_email
Send an email with optional HTML body and file attachments. When `html_body` is provided, the email is sent as multipart with both plain text and HTML parts -- the recipient's email client will choose which to display.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `to` | string[] | yes | Recipient email addresses |
| `subject` | string | yes | Email subject |
| `body` | string | yes | Plain text body |
| `html_body` | string | no | HTML body. When provided, email is sent as multipart (text/plain + text/html) |
| `cc` | string[] | no | CC recipients |
| `bcc` | string[] | no | BCC recipients |
| `attachments` | object[] | no | File attachments (see below) |
| `account` | string | no | Send as this mailbox (e.g. `hello@codechap.com`) instead of the default JMAP user |
Attachment object:
| Field | Type | Required | Description |
|---|---|---|---|
| `path` | string | yes | Absolute path to the file on disk |
| `filename` | string | yes | Filename for the attachment |
| `content_type` | string | no | MIME type (auto-detected from extension if omitted) |
download_attachments
Download all attachments from an email to a local directory.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `email_id` | string | yes | Email ID to download attachments from |
| `download_dir` | string | yes | Directory path to save attachments to |
create_account (admin)
Create a new email account on the server. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `email` | string | yes | Primary email address |
| `password` | string | yes | Account password |
| `description` | string | no | Display name |
| `quota` | number | no | Disk quota in bytes (0 for unlimited) |
| `permissions` | string[] | no | Permissions to grant at creation (e.g. `email-send`, `authenticate`, `imap-authenticate`). Without permissions, the account cannot authenticate or submit mail — either supply them here or call `update_account_permissions` afterwards. |
list_accounts (admin)
List all accounts, or get details for one. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | no | Account name for details. If omitted, lists all accounts. |
manage_aliases (admin)
Add or remove an email alias on an account. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `account` | string | yes | Account name |
| `action` | string | yes | `add` or `remove` |
| `alias` | string | yes | Alias email to add/remove |
update_account_permissions (admin)
Update an account's `enabledPermissions`. Newly-created principals start with no permissions and cannot authenticate, send, or receive mail until permissions are granted. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `account` | string | yes | Target account name |
| `action` | string | no | `set` (replace list, default), `add` (grant), or `remove` (revoke) |
| `permissions` | string[] | yes | Permission names (e.g. `email-send`, `authenticate`, `imap-authenticate`, `imap-append`) |
reset_password (admin)
Reset an account's password. If `password` is omitted, a strong 24-character random password is generated. The new password is returned in plaintext in the response so it can be delivered to the user. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `account` | string | yes | Target account name |
| `password` | string | no | New password. Auto-generated if omitted. |
get_dsn_accounts (admin)
List email addresses that have DSN (Delivery Status Notification) delivery reports enabled. Requires admin API configuration.
set_dsn_accounts (admin)
Set which email addresses receive DSN delivery reports (SUCCESS + FAILURE). Replaces the full list. Requires admin API configuration.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `accounts` | string[] | yes | Email addresses to enable delivery reports for |
check_sent (admin)
The first tool to reach for when verifying any outbound email — contact forms, WordPress `wp_mail()`, invoice/statement mailers, password resets, transactional mail — anything needing *"did this leave the server?"*.
Reads Stalwart's `/api/logs` (authoritative) and groups by `queueId`: submission → delivery attempt → final status (`delivery.delivered` / `delivery.dsn-success` / `delivery.failed`) plus upstream MX `code`/`hostname`.
Do NOT search mailboxes first — SMTP submissions are not auto-saved to Sent. Start here.
How the log fetch works (production lesson):
Stalwart's server-side `filter=` query often hangs on multi-GB daily log files. By default this tool fetches the newest `scan_limit` rows unfiltered and applies `to`/`from`/`filter` client-side (fast: ~300ms for 1000 rows). Pass `use_server_filter=true` only if you know you need it (e.g. a unique queueId on a quiet host).
Common use cases:
- "Did the invoice mailer / contact form send to `ap@client.com`?"
- "Was a transactional email delivered — what did Gmail return?"
- "Why bounce — remote SMTP code?"
| Parameter | Type | Required | Description |
|---|---|---|---|
| `to` | string | no | Recipient email or domain (client-side substring). Prefer this for contact-form checks. |
| `from` | string | no | Sender email or domain (client-side substring). |
| `filter` | string | no | Extra client-side substring (e.g. queueId). |
| `since` | string | no | RFC3339 lower bound on event timestamps. |
| `scan_limit` | number | no | Newest log rows to fetch (default 500, max 5000). Raise if the send is older than the window. |
| `use_server_filter` | bool | no | Default `false`. If `true`, pass filter to Stalwart (can timeout on busy hosts). |
Returns `messages_found`, `delivered_count`, `failed_count`, per-message timelines (`mx_code` / `mx_hostname`), plus `auth_events` (submission auth success/failure) and a `log_window`.
verify_account_auth
Test whether a username/password is accepted by Stalwart (same secret as SMTP port 587). Use when `check_sent` shows no submission — usually the app has the wrong password.
| Parameter | Type | Required | Description |
|---|---|---|---|
| `username` | string | yes | Account email (e.g. `hello@codechap.com`) |
| `password` | string | yes | Candidate password (mailbox secret, not admin) |
Frequently asked questions
What is mcp-server-stalwart?
mcp-server-stalwart is MCP server for Stalwart mail server via JMAP
How do I install mcp-server-stalwart?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is mcp-server-stalwart open source?
Yes — it is hosted on GitHub at https://github.com/codeChap/mcp-server-stalwart.
Related MCP tools
an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM
Search infrastructure for AI
YC (S26) | Open Computer History | Record your screen continuously locally and provide context to your agents (Claude, Codex, Openclaw, Hermes, Runner...)
The fastest and the most accurate file search SDK for AI agents, Neovim, Rust, C, Python, Bun and NodeJS
Semantic version control => entity-level diffs, blame, and impact analysis on top of git. 28 languages via tree-sitter. Built for coding agents.
Fast, local-first web content extraction for LLMs. Scrape, crawl, extract structured data — all from Rust. CLI, REST API, and MCP server.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP