Stalwart MCP

MCP server for Stalwart mail server via JMAP — mailboxes, search, send, and admin

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 --release

Binary is output to target/release/mcp-server-stalwart.

Configuration

The server connects via stdio and is configured through environment variables.

Required

VariableDescription
JMAP_SESSION_URLJMAP session endpoint (e.g. https://mail.example.com/jmap/session)
JMAP_USERNAMEJMAP account email address
JMAP_PASSWORDJMAP account password

Optional (admin API)

VariableDescription
STALWART_ADMIN_URLAdmin API base URL — https://mail.example.com or https://mail.example.com/api (both accepted; /api is normalized)
STALWART_ADMIN_USERAdmin username (default: admin)
STALWART_ADMIN_PASSWORDAdmin password (different principal from mailbox passwords)

Password gotcha (learned the hard way)

SecretUsed forNOT used for
JMAP_PASSWORD / mailbox passwordSMTP submission (smtp://user:pass@host:587), JMAP, IMAP for that accountAdmin API
STALWART_ADMIN_PASSWORDAdmin 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",
        "STALWART_ADMIN_URL": "https://mail.example.com",
        "STALWART_ADMIN_PASSWORD": "admin-password"
      }
    }
  }
}

Tools

get_mailboxes

List all mailboxes/folders with message counts.

create_mailbox

Create a new mailbox/folder.

ParameterTypeRequiredDescription
namestringyesMailbox name
parent_idstringnoParent mailbox ID for nesting (top-level if omitted)
rolestringnoStandard role: archive, drafts, inbox, junk, sent, trash

search_emails

Search emails with filters. Returns email IDs -- use get_emails to read full content.

ParameterTypeRequiredDescription
querystringnoText to search across subject, body, from, to
fromstringnoFilter by sender address
tostringnoFilter by recipient address
subjectstringnoFilter by subject text
mailbox_idstringnoRestrict to a specific mailbox
positionnumbernoPagination offset (default 0)
limitnumbernoMax results (default 10, max 50)

get_emails

Get full email content by IDs. Returns subject, from, to, date, body text, and metadata.

ParameterTypeRequiredDescription
idsstring[]yesList of email IDs to retrieve

delete_emails

Permanently delete emails by ID. Cannot be undone.

ParameterTypeRequiredDescription
idsstring[]yesList of email IDs to delete

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.

ParameterTypeRequiredDescription
tostring[]yesRecipient email addresses
subjectstringyesEmail subject
bodystringyesPlain text body
html_bodystringnoHTML body. When provided, email is sent as multipart (text/plain + text/html)
ccstring[]noCC recipients
bccstring[]noBCC recipients
attachmentsobject[]noFile attachments (see below)

Attachment object:

FieldTypeRequiredDescription
pathstringyesAbsolute path to the file on disk
filenamestringyesFilename for the attachment
content_typestringnoMIME type (auto-detected from extension if omitted)

download_attachments

Download all attachments from an email to a local directory.

ParameterTypeRequiredDescription
email_idstringyesEmail ID to download attachments from
download_dirstringyesDirectory path to save attachments to

create_account (admin)

Create a new email account on the server. Requires admin API configuration.

ParameterTypeRequiredDescription
emailstringyesPrimary email address
passwordstringyesAccount password
descriptionstringnoDisplay name
quotanumbernoDisk quota in bytes (0 for unlimited)
permissionsstring[]noPermissions 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.

ParameterTypeRequiredDescription
namestringnoAccount name for details. If omitted, lists all accounts.

manage_aliases (admin)

Add or remove an email alias on an account. Requires admin API configuration.

ParameterTypeRequiredDescription
accountstringyesAccount name
actionstringyesadd or remove
aliasstringyesAlias 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.

ParameterTypeRequiredDescription
accountstringyesTarget account name
actionstringnoset (replace list, default), add (grant), or remove (revoke)
permissionsstring[]yesPermission 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.

ParameterTypeRequiredDescription
accountstringyesTarget account name
passwordstringnoNew 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.

ParameterTypeRequiredDescription
accountsstring[]yesEmail 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?"
ParameterTypeRequiredDescription
tostringnoRecipient email or domain (client-side substring). Prefer this for contact-form checks.
fromstringnoSender email or domain (client-side substring).
filterstringnoExtra client-side substring (e.g. queueId).
sincestringnoRFC3339 lower bound on event timestamps.
scan_limitnumbernoNewest log rows to fetch (default 500, max 5000). Raise if the send is older than the window.
use_server_filterboolnoDefault 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.

ParameterTypeRequiredDescription
usernamestringyesAccount email (e.g. hello@codechap.com)
passwordstringyesCandidate password (mailbox secret, not admin)