fastmail-mcp
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., "@fastmail-mcp Show me my 5 most recent unread emails"
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.
fastmail-mcp
A Model Context Protocol (MCP) server for Fastmail built with FastMCP, httpx, and Pydantic.
Communicates with Fastmail via JMAP (RFC 8620 / 8621) — no IMAP, no Node, no forks.
Features
Area | Tools | JMAP methods |
Session |
|
|
Mailboxes |
|
|
Email (read) |
|
|
Submission |
|
|
Mutations |
|
|
Bulk mutations |
|
|
Attachments & stats |
|
|
Masked Email |
|
|
Vacation |
|
|
Sieve Scripts |
|
|
Related MCP server: Fastmail MCP Server
Setup
1. Create a Fastmail API token
Go to Fastmail → Settings → Privacy & Security → Integrations → API tokens (or see Fastmail developer docs).
Create a new API token with the scopes you need:
Scope | Required for |
| All tools (session discovery) |
| Mailboxes, email read/query, mutations, threads, attachments, stats |
| Sending email, draft lifecycle, reply, identities ( |
| Masked email tools |
| Vacation response tools |
| Sieve script tools (if available — see Filters & Rules note) |
2. Environment variables
Copy .env.example to .env and fill in your token:
# Required
FASTMAIL_API_TOKEN=fmu1-...
# Optional — override the JMAP session URL (default: Fastmail production)
# FASTMAIL_JMAP_SESSION_URL=https://api.fastmail.com/jmap/session
# HTTP transport only — see "Deployment" section
# FASTMAIL_MCP_HTTP_ALLOW_ENV_API_KEY=falseUsage
Local (stdio) — Cursor, Claude Desktop, etc.
Install from the repo (or a built wheel):
uv pip install -e .Run the MCP server over stdio:
fastmail-mcpCursor configuration
Add to your Cursor MCP settings (.cursor/mcp.json or global):
{
"mcpServers": {
"fastmail": {
"command": "uv",
"args": ["run", "--directory", "/path/to/fastmail-mcp", "fastmail-mcp"],
"env": {
"FASTMAIL_API_TOKEN": "fmu1-..."
}
}
}
}Claude Desktop configuration
{
"mcpServers": {
"fastmail": {
"command": "/path/to/fastmail-mcp/.venv/bin/fastmail-mcp",
"env": {
"FASTMAIL_API_TOKEN": "fmu1-..."
}
}
}
}Remote (HTTP) — hosted server
Start the HTTP transport:
fastmail-mcp-httpCallers authenticate per-request via:
Header (preferred):
X-Fastmail-Api-Token: fmu1-...Query parameter:
?fastmail_api_token=fmu1-...
Connect from Cursor or Claude Desktop using mcp-remote:
{
"mcpServers": {
"fastmail": {
"command": "npx",
"args": [
"mcp-remote",
"https://your-host.example.com/mcp",
"--header",
"X-Fastmail-Api-Token: fmu1-..."
]
}
}
}Sending email
The send_email, reply_email, and send_draft tools require a token with the urn:ietf:params:jmap:submission scope in addition to urn:ietf:params:jmap:mail.
Typical flow:
List identities —
list_identitiesshows which "from" addresses are available.Send directly —
send_emailcomposes and sends in one step (creates the email + submission as a single JMAP batch).Draft workflow —
create_draft→edit_draft(optional, can repeat) →send_draft.Reply —
reply_emailautomatically buildsRe:subject,In-Reply-To, andReferencesheaders. Usereply_all=trueto include all original recipients.
JMAP emails are immutable — edit_draft atomically creates a new draft and destroys the old one.
Deployment
Never store a shared user token in server environment on a hosted deployment. Set
FASTMAIL_MCP_HTTP_ALLOW_ENV_API_KEY=false(the default) so the server always requires callers to send their own token via header or query parameter.Never log tokens. The server uses
SecretStrfor the env token and never writes it to stdout/stderr.Never log message bodies or email addresses. Error messages from JMAP are surfaced but the server does not log email content.
download_attachmentreturns a URL only — it does not write files to the server filesystem, making it safe for hosted deployments.For single-user local setups,
FASTMAIL_API_TOKENin the process env is fine — it's only read when running over stdio, or whenFASTMAIL_MCP_HTTP_ALLOW_ENV_API_KEY=true.
Filters & Rules (web UI)
Fastmail's Settings → Filters & Rules (move-to-mailbox rules, snooze actions, blocked senders, etc.) are configured through the web UI. These rules are not part of the standard JMAP API exposed to API tokens.
If the JMAP session advertises
urn:ietf:params:jmap:sieve(RFC 9661), the Sieve script tools in this server can list, create, and manage server-side Sieve filter scripts.If the capability is absent, the Sieve tools will return a clear error message. In that case, manage your filters at https://app.fastmail.com/settings/rules.
Snooze-specific actions and UI-only rule types are not available via any public API.
Development
# Clone and install
git clone https://github.com/your-user/fastmail-mcp.git
cd fastmail-mcp
uv sync
# Run locally
uv run fastmail-mcp
# Lint
uv run ruff check .
uv run basedpyrightLinks
Available Tools
43 toolsadd_labelsA
Add one or more mailbox labels to an email (without removing existing ones).
Args: email_id: ID of the email. mailbox_ids: Comma-separated mailbox IDs to add.
Inputs: email_id (required), mailbox_ids (required). Returns: object
bulk_add_labelsA
Add mailbox labels to multiple emails.
Args: email_ids: Comma-separated email IDs (max 50). mailbox_ids: Comma-separated mailbox IDs to add.
Inputs: email_ids (required), mailbox_ids (required). Returns: object
bulk_deleteA
Move multiple emails to Trash.
Args: email_ids: Comma-separated email IDs (max 50).
Inputs: email_ids (required). Returns: object
bulk_mark_readA
Mark multiple emails as read or unread.
Args: email_ids: Comma-separated email IDs (max 50). read: True to mark as read, false for unread.
Inputs: email_ids (required), read. Returns: object
bulk_moveA
Move multiple emails to a mailbox (replaces existing mailbox assignments).
Args: email_ids: Comma-separated email IDs (max 50). mailbox_id: Target mailbox ID.
Inputs: email_ids (required), mailbox_id (required). Returns: object
bulk_pinA
Pin (flag) or unpin multiple emails.
Args: email_ids: Comma-separated email IDs (max 50). pinned: True to pin, false to unpin.
Inputs: email_ids (required), pinned. Returns: object
bulk_remove_labelsA
Remove mailbox labels from multiple emails.
Args: email_ids: Comma-separated email IDs (max 50). mailbox_ids: Comma-separated mailbox IDs to remove.
Inputs: email_ids (required), mailbox_ids (required). Returns: object
create_draftA
Save a new email as a draft (not sent).
At least one of to, subject, or a body field should be provided.
Args: to: Recipients (comma-separated). subject: Email subject line. text_body: Plain-text body content. html_body: HTML body content. cc: CC recipients (comma-separated). bcc: BCC recipients (comma-separated). in_reply_to: Message-ID being replied to. references: Space-separated Message-IDs for References header.
Inputs: to, subject, text_body, html_body, +4 more. Returns: object
create_mailboxA
Create a new mailbox (folder).
Args: name: Display name for the mailbox. parent_id: ID of the parent mailbox for nested folders (empty for top-level).
Inputs: name (required), parent_id. Returns: object
create_masked_emailA
Create a new masked (alias) email address.
Addresses created in "pending" state expire after 24 hours if not enabled.
Args: for_domain: Domain this masked email is associated with (e.g. "example.com"). description: Human-readable description. url: URL associated with this masked email. state: Initial state — "enabled" (default) or "pending".
Inputs: for_domain (required), description, url, state. Returns: object
create_sieve_scriptA
Create a new Sieve filter script.
Args: name: Script name. content: Sieve script source. is_active: Activate this script immediately (only one script can be active).
Inputs: name (required), content (required), is_active. Returns: object
delete_emailB
Move an email to the Trash mailbox.
Inputs: email_id (required). Returns: object
destroy_mailboxA
Destroy (delete) a mailbox. Fails if the mailbox has children or is a system role.
Inputs: mailbox_id (required). Returns: object
destroy_masked_emailB
Destroy (permanently delete) a masked email address.
Inputs: masked_email_id (required). Returns: object
destroy_sieve_scriptC
Destroy (delete) a Sieve script. Cannot destroy the currently active script.
Inputs: script_id (required). Returns: object
download_attachmentA
Get a download URL for an email attachment.
Returns the fully-resolved URL only — does not write to the filesystem. Safe for hosted deployments.
Args: blob_id: Blob ID of the attachment (from get_email_attachments). name: Filename for the download. content_type: MIME type for the download.
Inputs: blob_id (required), name, content_type. Returns: object
edit_draftA
Replace a draft with updated content.
JMAP emails are immutable — this creates a new draft and destroys the old one atomically. Empty fields keep the original value.
Args: draft_id: ID of the existing draft to replace. to: Recipients (comma-separated). Empty keeps original. subject: Email subject. Empty keeps original. text_body: Plain-text body. Empty keeps original. html_body: HTML body. Empty keeps original. cc: CC recipients. Empty keeps original. bcc: BCC recipients. Empty keeps original.
Inputs: draft_id (required), to, subject, text_body, +3 more. Returns: object
get_account_summaryA
Get an aggregate account summary: total emails, unread count, mailbox breakdown.
Inputs: no named parameters. Returns: object
get_email_attachmentsA
List attachments on an email (metadata only — use download_attachment for content).
Args: email_id: ID of the email.
Inputs: email_id (required). Returns: result
get_emailsA
Get full details for one or more emails by ID.
Args: email_ids: Comma-separated email IDs. fetch_body: Include text body content (default true). max_body_bytes: Max bytes per body part (default 10 000; protects LLM context).
Inputs: email_ids (required), fetch_body, max_body_bytes. Returns: result
get_jmap_sessionA
Return the JMAP session: capabilities, account IDs, username, and feature availability.
Use this to discover what features and scopes are available with your API token.
Inputs: no named parameters. Returns: object
get_mailbox_statsA
Get email count statistics for one or all mailboxes.
Args: mailbox_id: Specific mailbox ID (empty for all mailboxes).
Inputs: mailbox_id. Returns: result
get_recent_emailsA
Get the most recent emails in a mailbox (sorted by date, newest first).
Args: mailbox: Mailbox name or role (e.g. "inbox", "sent", "drafts", "trash", or a custom name). limit: Max results (1–50, default 25).
Inputs: mailbox, limit. Returns: object
get_sieve_scriptA
Get a Sieve script by ID, including its full content (downloaded from blob storage).
Inputs: script_id (required). Returns: object
get_threadA
Get all emails in a conversation thread.
Provide either an email_id (the thread is looked up automatically) or a thread_id directly.
Args: email_id: An email ID whose thread to fetch. thread_id: A thread ID to fetch directly. max_body_bytes: Max bytes per body part (default 5000).
Inputs: email_id, thread_id, max_body_bytes. Returns: object
get_vacation_responseA
Get the current vacation / out-of-office auto-reply configuration.
Requires the urn:ietf:params:jmap:vacationresponse capability
(Fastmail scope: Vacation responses).
Inputs: no named parameters. Returns: object
list_identitiesA
List sending identities (email addresses you can send from).
Requires the urn:ietf:params:jmap:submission scope.
Inputs: no named parameters. Returns: result
list_mailboxesA
List all mailboxes (folders) in the account.
Returns name, role (inbox/sent/drafts/trash/etc.), parent, email counts.
Inputs: no named parameters. Returns: result
list_masked_emailsA
List masked (alias) email addresses with pagination.
Returns a compact summary by default. Set verbose=True for full fields.
Args: state_filter: Filter by state (pending/enabled/disabled/deleted). Empty for all. limit: Max items to return (default 50, max 200). offset: Skip this many items (for pagination). verbose: If True, return all fields; if False, return compact summary.
Inputs: state_filter, limit, offset, verbose. Returns: object
list_sieve_scriptsA
List Sieve filter scripts (server-side mail routing rules).
Requires urn:ietf:params:jmap:sieve in the JMAP session.
If unavailable, manage filters via the Fastmail web UI (Settings → Filters & Rules).
Inputs: no named parameters. Returns: result
mark_email_readA
Mark an email as read or unread.
Args: email_id: ID of the email. read: True to mark as read (default), false to mark as unread.
Inputs: email_id (required), read. Returns: object
move_emailA
Move an email to a different mailbox (replaces all current mailbox assignments).
Args: email_id: ID of the email to move. mailbox_id: Target mailbox ID.
Inputs: email_id (required), mailbox_id (required). Returns: object
pin_emailA
Pin (flag) or unpin an email.
Args: email_id: ID of the email. pinned: True to pin/flag (default), false to unpin.
Inputs: email_id (required), pinned. Returns: object
query_emailsA
Search for emails with structured filters. Returns matching emails with preview.
Args: in_mailbox: Mailbox ID to search within. text: Full-text search across all fields (from, to, subject, body). from_addr: Filter by sender address or name. to_addr: Filter by recipient address or name. subject: Filter by subject text. after: Emails received after this date (ISO 8601, e.g. "2024-01-01T00:00:00Z"). before: Emails received before this date (ISO 8601). has_keyword: Require keyword/flag ("$flagged", "$seen", "$draft", "$answered"). not_keyword: Exclude emails with this keyword. has_attachment: Filter by attachment presence. filter_json: Raw JSON FilterCondition for complex queries (overrides other filter params). sort_property: Sort field — receivedAt, sentAt, from, to, subject, size. sort_ascending: Sort direction (default: newest first). limit: Max results (1–100, default 20).
Inputs: in_mailbox, text, from_addr, to_addr, +10 more. Returns: object
remove_labelsA
Remove one or more mailbox labels from an email.
Args: email_id: ID of the email. mailbox_ids: Comma-separated mailbox IDs to remove.
Inputs: email_id (required), mailbox_ids (required). Returns: object
reply_emailA
Reply to an email. Sends immediately by default; set send=false to save as draft.
Builds Re: subject, In-Reply-To, and References headers automatically.
Args: email_id: ID of the email to reply to. text_body: Plain-text reply body. html_body: HTML reply body. reply_all: If true, reply to all original recipients (not just sender). send: If true (default), send immediately; if false, save as draft. identity_id: Sending identity ID (empty for default).
Inputs: email_id (required), text_body, html_body, reply_all, +2 more. Returns: object
send_draftA
Send an existing draft email.
Args: draft_id: ID of the draft to send. identity_id: Sending identity ID (empty for default).
Inputs: draft_id (required), identity_id. Returns: object
send_emailA
Compose and send an email immediately.
Args: to: Recipients (comma-separated, e.g. "user@example.com, Name other@example.com"). subject: Email subject line. text_body: Plain-text body content. html_body: HTML body content (optional; both text and HTML can be provided). cc: CC recipients (comma-separated, same format as to). bcc: BCC recipients (comma-separated, same format as to). identity_id: Sending identity ID (use list_identities to see options; empty for default). in_reply_to: Message-ID of the email being replied to. references: Space-separated Message-IDs for the References header.
Inputs: to (required), subject (required), text_body, html_body, +5 more. Returns: object
set_vacation_responseA
Set or update the vacation / out-of-office auto-reply.
Args: is_enabled: Whether the vacation response is active. from_date: Start date (ISO 8601, e.g. "2024-12-20T00:00:00Z"). Empty to leave unchanged. to_date: End date (ISO 8601). Empty to leave unchanged. subject: Auto-reply subject line. Empty to leave unchanged. text_body: Plain-text auto-reply body. html_body: HTML auto-reply body (optional).
Inputs: is_enabled (required), from_date, to_date, subject, +2 more. Returns: object
update_mailboxA
Update a mailbox (rename or move under a different parent).
Args: mailbox_id: ID of the mailbox to update. name: New display name (empty to leave unchanged). parent_id: New parent mailbox ID (empty to leave unchanged; "null" moves to top-level).
Inputs: mailbox_id (required), name, parent_id. Returns: object
update_masked_emailA
Update a masked email address (change state, description, domain, or URL).
Args: masked_email_id: ID of the masked email to update. state: New state (enabled/disabled/deleted). Empty to leave unchanged. description: New description. Empty to leave unchanged. for_domain: New associated domain. Empty to leave unchanged. url: New associated URL. Empty to leave unchanged.
Inputs: masked_email_id (required), state, description, for_domain, +1 more. Returns: object
update_sieve_scriptA
Update an existing Sieve script (name, content, or active state).
Args: script_id: ID of the Sieve script to update. name: New name (empty to leave unchanged). content: New Sieve source (empty to leave unchanged). is_active: Set active state (null to leave unchanged).
Inputs: script_id (required), name, content, is_active. Returns: object
validate_sieve_scriptA
Validate Sieve script syntax without saving.
Args: content: The Sieve script source to validate.
Inputs: content (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 43 tools
Each tool targets a distinct operation (single vs bulk, different resources like emails, mailboxes, masked emails, sieve scripts). No two tools have overlapping purposes; descriptions clearly differentiate them.
All tools follow a consistent verb_noun pattern (e.g., create_mailbox, delete_email, list_identities). Bulk operations are prefixed with 'bulk_', and there are no mixed conventions or abbreviations.
With 43 tools, the server covers a wide domain (email, mailboxes, masked emails, siege scripts, vacation, identities). While each tool has its place, the count is on the higher side and could be streamlined by reducing redundant bulk variants.
The tool surface covers core email workflows (send, reply, draft, labels, move, delete, search) plus mailbox, masked email, and sieve script management. A notable gap is the inability to send emails with attachments (only download is supported).
Related MCP Connectors
Private, EU-hosted email for AI agents over JMAP: read, search, reply, draft, organize, send.
Email for AI agents: read, search and send from your Gmail, Outlook, Yahoo, iCloud or IMAP inbox.
Email inboxes and calendars for AI agents: send, receive, search, draft and schedule.
Email inboxes and calendars for AI agents: send, receive, search, draft and schedule.
Related MCP Servers
- AlicenseBqualityCmaintenanceEnables AI assistants to interact with Fastmail email, contacts, and calendars through JMAP API, supporting operations like sending emails, managing attachments, searching conversations, and organizing events.5276 npm131MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to securely access and search Fastmail emails via the JMAP API. Supports searching emails, retrieving full email content, and listing mailboxes with read-only access using Fastmail API tokens.MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI models to interact with Fastmail accounts via the JMAP API through the Model Context Protocol. It supports managing emails, accessing usage analytics, and performing account operations using secure token-based authentication.27 npm9MIT
- AlicenseAqualityDmaintenanceEnables AI assistants to interact with Fastmail services, providing tools for comprehensive email management, contact searching, and calendar event coordination via the JMAP API. It supports advanced features like threaded conversations, attachment handling, and bulk mailbox operations through natural language.28MIT