Skip to main content
Glama
thunderbird-mcp

thunderbird-mcp

by jamessi1989

thunderbird-mcp

Search and reference your Thunderbird mail from coding agents.

Mail MCP checks Demo · Installation guide · Releases · Report your installation

Find a phrase inside a PDF or Office attachment, carry a mail reference into another project, and prepare a reply from Claude Code, Codex, OpenCode, Terminalman, or a normal shell. The local index searches downloaded mail by keywords or meaning; live IMAP search finds mail that Thunderbird has not downloaded yet. Sending uses saved drafts, SMTP, and credentials kept in the system keyring.

Synthetic demo: search an invoice attachment, reuse a mail reference, and prepare an unsent reply

Start using it

You need Python 3.10+ and uv. Clone the project and try the demo before connecting your mailbox:

git clone https://github.com/jamessi1989/thunderbirdmcp.git ~/projects/thunderbirdmcp
cd ~/projects/thunderbirdmcp
uv sync --locked
uv run python scripts/demo.py

The demo uses temporary synthetic mail and an invoice PDF. It prepares an unsent reply; it needs no mail credentials or AI model. To use your own mailbox, configure Thunderbird to download messages in mbox format, then run:

uv run thunderbird-mcp-index
uv run thunderbird-mcp-setup --apply

The installation guide covers credentials, custom profiles, installing a release wheel without a checkout, and troubleshooting. Linux is the primary tested desktop; Linux and macOS run the synthetic suite and package installation checks in CI. Native Windows installation is not yet verified.

Setup installs tbmail, thunderbird-mail, thunderbird-mcp, and thunderbird-mcp-setup in ~/.local/bin. It registers the MCP in the global configuration of installed Claude Code, Codex, OpenCode, Antigravity, and Gemini CLI clients. Use --clients claude codex to select clients, or --clients none to install only commands. Run setup without --apply to inspect changes. Existing settings are preserved and changed files receive private timestamped backups. Repeating setup is safe. MCP launchers use absolute paths and preserve your current directory.

Existing Terminalman sessions can load the tools using the client reload steps below. The Terminalman server does not need a restart. tbmail is immediately available through the shell, including from an already running agent.

Setup uses these default client configuration files:

Client (--clients name)

Global configuration

Claude Code (claude)

~/.claude.json

Codex (codex)

~/.codex/config.toml

OpenCode, including GLM (opencode)

~/.config/opencode/opencode.jsonc if present, otherwise opencode.json in the same directory

Antigravity (agy)

~/.gemini/config/mcp_config.json

Gemini CLI (gemini)

~/.gemini/settings.json

Setup respects CLAUDE_CONFIG_DIR, CODEX_HOME, and XDG_CONFIG_HOME for their respective clients. OpenCode uses the same MCP registration for all models; no separate GLM server configuration is needed.

You can then ask:

  • “Search my mail for the cloud-provider invoice from May.”

  • “Check live IMAP for new unread mail in INBOX.”

  • “Find the agreement mentioned inside an attachment and read that attachment.”

  • “Read that conversation and give me a reference I can use in another session.”

  • “Prepare a reply to this mail saying the migration is complete.”

  • “Send draft <draft-id>.”

Related MCP server: Email MCP Server

Check Codex, Claude, and OpenCode

Check the real mailbox and each client's registration:

tbmail status --network
codex mcp get thunderbird
claude mcp get thunderbird
opencode mcp list

After connecting or reloading, ask: “Use the thunderbird MCP to search my mail for invoices, read the most relevant result, and give me its stable reference.” Codex and Claude also show active MCP connections with /mcp. OpenCode's MCP status is available through Toggle MCPs in the command palette.

Reload an existing session

After updating the checkout and running uv sync --locked and uv run thunderbird-mcp-setup --apply, refresh each running client's connection:

Client

Reload without losing the conversation

Claude Code

Run /mcp, select thunderbird, and choose Reconnect. Open /mcp again to check that it is connected and lists 23 tools.

OpenCode

Press Ctrl+P, choose Toggle MCPs, select thunderbird, then press Space to disable it and Space again to enable it. Check that it shows connected.

Codex

An existing app-server connection can reload its loaded threads using the protocol below. For a CLI session without that connection, wait until idle, use /exit, then run codex resume --last from the same project and check /mcp.

Claude and OpenCode reconnects and Codex app-server reloads were verified in running Terminalman sessions on 2026-11-02; the conversations were preserved.

Verified with Codex CLI 0.166.0. A host connected to the existing app-server can send these requests after the initialize / initialized handshake. The verified connection enabled capabilities.experimentalApi during initialization. Use the server that owns the active thread; a separate server has separate connections.

{"id":1,"method":"config/mcpServer/reload","params":null}
{"id":2,"method":"mcpServerStatus/list","params":{"threadId":"<active-thread-id>","serverName":"thunderbird","detail":"toolsAndAuthOnly"}}

Reload reads configuration from disk and queues a refresh for loaded threads. Check the status response again after the refresh completes; the thunderbird tool inventory should contain 23 tools. The Codex app-server protocol documents the connection transport, handshake, reload request, and status query. The Codex CLI reference documents /mcp and conversation resumption.

Test actual client tool calls

An opt-in test checks actual model tool calls in all three clients:

uv run python scripts/test_clients.py

It creates a temporary synthetic mailbox and exposes only search_email, get_email, and get_email_reference. A client passes only after successful tool calls return a hidden verification code and the correct reference. The test uses your configured model accounts and may consume model credits. It does not read your real mail or send anything. To select one client/model:

uv run python scripts/test_clients.py --clients opencode --opencode-model provider/model

Use --report-dir /tmp/mail-client-check to retain private troubleshooting logs. Client flags can change between releases; this script is an opt-in integration check, separate from the offline unit tests.

Terminal commands

tbmail status --network                 # Verify TLS/login; sends no mail
tbmail accounts                         # Account IDs and identities; no credentials
tbmail search 'from:alex storage' --limit 5
tbmail search 'folder:INBOX after:2026-11-01' --keyword
tbmail search --live --unread --limit 5  # Fresh IMAP mail, default INBOX
tbmail search 'folder:Archive receipt' --live --account account2
tbmail search 'has:attachment agreement' --keyword
tbmail search --attachment report.pdf --account account2
tbmail read 12345
tbmail thread 12345 --bodies
tbmail reference 12345                  # Markdown citation with a durable URI
tbmail attachments 12345
tbmail attachment-text 12345 1          # Read document text without exporting
tbmail export 12345 ./message.eml
tbmail export-attachment 12345 1 ./attachment.pdf
tbmail reindex                         # Refresh downloaded mail
tbmail --json search 'invoice'          # Structured output for scripts/agents
tbmail folders --account account2       # Folders in the local index
tbmail folders --live --account account2

Pass a returned thunderbird://message/... reference in place of a numeric ID to read, thread, reference, reply, attachment, and export commands. Stable references use Message-ID, with a content hash fallback, and survive reindexing and folder changes. Integer IDs also stay stable during ordinary refreshes, but use the URI for references saved in another project or conversation. Paste it into another Terminalman conversation and ask the agent to read it with the thunderbird MCP. The search web app has a Copy mail reference button.

Live searches return thunderbird://imap/... references. The same read, thread, reference, reply, attachment, and export commands accept them without a local index. These references bind the account endpoint, folder, UIDVALIDITY and UID. They expire when the message moves or the folder/account is replaced; a new live search is then required. Use a local message reference for citations that must survive folder moves.

For rare messages without Message-ID, the fallback hashes the indexed headers and text. Messages with identical indexed text but different attachments can share that fallback reference. A numeric ID selects the indexed row, but source recovery after mailbox compaction can still be ambiguous. Use Thunderbird to inspect the original copies in that case.

tbmail draft --to colleague@example.org --subject 'Migration update' \
  --body-file ./reply.txt --attach ./report.pdf
tbmail draft --account account2 --to colleague@example.org --subject 'Update' --body 'Ready.'
tbmail reply 'thunderbird://message/...' --body 'Thanks, I will check.'
tbmail drafts
tbmail drafts <draft-id>
tbmail send <draft-id>                  # Show the saved preview
tbmail send <draft-id> --confirm        # Submit exactly that saved draft

Drafts support To/Cc/Bcc and captured local attachments. Reply drafts preserve In-Reply-To and References, honor Reply-To, and exclude your address from reply-all. Drafts are local to this application; they do not appear in Thunderbird Drafts. --body-file - reads standard input. Use tbmail --help for all commands. Replies infer the original account when it can be identified; use --account to select it explicitly. Each saved draft retains its original account and sender even if you later change Thunderbird's default account.

Sending and retry behavior

Each draft stores its exact MIME message, envelope, and account settings in a private outbox database. Delivery atomically claims that draft. Concurrent or repeated sends cannot submit it again. SMTP acceptance is recorded before the Sent copy is saved, so failure to save the copy never causes another delivery.

Results distinguish prepared, sending, sent, partially_sent, failed, and uncertain. An interrupted or uncertain send needs a delivery check; do not create another draft automatically. send_draft(retry_failed=True) permits a retry only after a known failure before delivery. Direct send_email defaults to a preview and accepts an idempotency_key for programmatic retries. Prefer the saved-draft workflow. Sending tests use a mocked SMTP server; live diagnostics authenticate and issue NOOP without sending anything.

MCP interface

The server exposes 23 tools, 3 resource URI patterns, and 2 prompts.

Workflow

Tools

Health and search

mail_status, list_accounts, search_email, search_live_email, list_folders, reindex

Read and cite

get_email, get_thread, get_email_reference

Original mail and attachments

list_attachments, get_attachment_text, export_email, export_attachment

Prepare and send

draft_email, draft_reply, get_draft, list_drafts, send_draft, send_email

Organize

imap_list_folders, create_folder, move_emails, undo_move

Resources: thunderbird://status, thunderbird://message/{key}, and thunderbird://imap/{account}/{fingerprint}/{folder}/{uidvalidity}/{uid}. Prompts: find_mail(query) and reply_to_mail(reference, instructions). Tools have read/write annotations. Mail and attachment content is treated as untrusted data. The server uses stdio; protocol stdout contains only MCP messages.

Search and indexing

Local search combines FTS5 keywords, common-word typo correction, multilingual local embeddings, recency, and optional conversation deduplication. Filters include from:, to:, folder:, after:YYYY-MM-DD, before:YYYY-MM-DD, has:attachment, and attachment:filename; before: is exclusive. The separate since/until parameters accept dates, with until including that whole day. Empty search lists recent mail. semantic=False/--keyword uses only keywords; search diagnostics explain when semantic search is unavailable. The initial thunderbird-mcp-index command builds the keyword index. Run the embedding command separately to enable semantic retrieval; its first run may download the embedding model.

uv run thunderbird-mcp-embed            # Incremental semantic vectors
uv run thunderbird-mail-web            # Default: http://127.0.0.1:3458

The local index reads Thunderbird's downloaded mbox mail. Thunderbird must sync new messages and folders before local search can find them. Refreshing this index does not fetch mail from IMAP. Local exports need the local original; live references fetch their original from IMAP. Reads do not change read/unread flags. Bodies are indexed up to 50,000 characters, with truncation reported. Export original .eml files for complete local content. Exports do not overwrite existing files or write inside Thunderbird mail storage.

search_live_email / tbmail search --live searches one server folder, default INBOX. Words and quoted phrases use IMAP text matching and combine with AND; header/date filters and --unread/--read are supported. Live dates require YYYY-MM-DD and use the message header's calendar day; timestamps are rejected. Results are headers, ordered by newest UID, and report omitted matches. Live threads follow reply headers within that folder, bounded to 100 candidates and 40 searches. Each live message and the combined raw MIME downloaded for a thread, including attachments, are capped at 25 MiB. Live search does not provide semantic ranking or extracted document text search; those use the local index.

Attachment text search supports TXT/CSV/JSON and other text formats, HTML/XML, PDF, DOCX, XLSX, and PPTX. Filenames remain searchable when text extraction is unsupported. get_attachment_text / tbmail attachment-text reads a selected attachment, including from live mail. Extraction reports extracted, empty, unsupported, encrypted, too_large, or error, plus truncation. Indexing also reports too_many when the attachment-count limit is exceeded. Scanned PDFs need OCR, which is not included.

Extraction limits are 8 MiB and 25,000 text characters per attachment. Indexing also caps each message at 24 MiB decoded bytes, 64 attachments, and 100,000 text characters. get_attachment_text extracts only the requested attachment. Office archives are limited to 16 MiB expanded and 2,048 entries. PDFs are limited to 50 pages and a ten-second child process; POSIX systems also apply a CPU limit, and Linux caps process address space at 512 MiB. Macros and external links are never executed by extraction. No document content is uploaded for indexing. Mail text returned to a hosted AI client is sent to that client's model provider.

Schema upgrades retain existing messages. Refreshes preserve IDs, skip expunged messages, and commit each folder atomically; if Thunderbird changes a folder during parsing, its previous index is kept for the next refresh. Threads use Message-ID/References/In-Reply-To relationships rather than merging unrelated messages with the same subject.

Upgrading to v0.3 rebuilds the derived FTS index once and reparses existing folders to extract attachment text. Run tbmail reindex --embeddings after uv sync --locked, or let the installed indexing service finish the backfill. tbmail status reports pending extraction and format statuses. Existing mail references remain valid. Large full refreshes or embedding rebuilds can exceed an AI client's MCP tool timeout; run those through tbmail reindex --embeddings or the background service. Ordinary local searches remain available while backfilling.

Systemd units in systemd/ refresh the index and embeddings ten minutes after each completed run and run the existing authenticated web app. The web app loads no external page assets and renders mail as plain text. It has keyboard navigation (/, j, k, o) and optional cross-encoder reranking. Web credentials use the thunderbird-mail-web keyring service. The supplied service binds to loopback. To use a private network address, set MAIL_WEB_HOST in a local systemd override. Adjust WorkingDirectory if the checkout is outside ~/projects/thunderbirdmcp.

Configuration

Thunderbird's selected profile and default account supply hostnames, ports, identity, TLS mode, and Sent folder. Passwords stay in the system keyring:

tbmail accounts
tbmail password-set --account account1 --protocol imap
tbmail password-set --account account1 --protocol smtp

Passwords are entered through a hidden prompt, or one line through stdin with --stdin. Each stored credential is scoped to account, host, port, username, and protocol. The previous thunderbird-mcp username keyring entry still works for an unambiguous default account. Secondary accounts use scoped credentials. No passwords or OAuth tokens are placed in MCP client configurations.

Environment overrides:

Variable

Meaning

TB_PROFILE, TB_ACCOUNT

Thunderbird profile path and account ID

TB_MAIL_INDEX_DB

Index path; checkout default mail-index.sqlite, installed-package default in the user data directory (paths)

TB_OUTBOX_DB

Draft/delivery database; default beside the index

TB_FROM

Sending identity, e.g. Name <you@example.org>

TB_IMAP_HOST, TB_IMAP_PORT, TB_IMAP_USER

IMAP connection

TB_SMTP_HOST, TB_SMTP_PORT, TB_SMTP_USER

SMTP connection

TB_IMAP_TLS, TB_SMTP_TLS

ssl or starttls, with certificate verification

TB_IMAP_KEYRING_SERVICE, TB_SMTP_KEYRING_SERVICE

Keyring service names; default thunderbird-mcp

TB_IMAP_PASSWORD, TB_SMTP_PASSWORD

Optional environment credentials; prefer keyring

TB_IMAP_AUTH, TB_SMTP_AUTH

password or oauth2; follows Thunderbird's OAuth setting when present

TB_SENT_FOLDER, TB_IMAP_ACCOUNT_DIR

Sent folder and local account-directory mapping

TB_NETWORK_TIMEOUT

Connection timeout in seconds; default 15

TB_EMBED_THREADS

Local model CPU threads per process; default 2

MAIL_WEB_HOST, MAIL_WEB_PORT

Web bind address and port

Global TB_IMAP_*, TB_SMTP_*, and identity overrides apply to the default account. Scope an override to another account with TB_ACCOUNT_<ID>_<SETTING>, for example TB_ACCOUNT_ACCOUNT2_SMTP_AUTH=oauth2. Explicit account selection does not inherit another account's host, sender, or credentials.

Sending supports international display names and ASCII mailbox addresses. Outgoing attachments are capped at 25 MiB total; local MIME export at 100 MiB. Account selectors accept a Thunderbird account ID or a unique primary identity email configured in Thunderbird. Use the account ID when overriding the sending address. Local account filtering uses its Thunderbird storage directory. Never put credentials, exported mail, or index/outbox databases in source control.

OAuth accounts

OAuth2 uses XOAUTH2 for IMAP/SMTP, with account-scoped keyring storage and automatic refresh over verified HTTPS. This release imports tokens obtained through your own authorized OAuth application; it does not provide a browser consent flow or reuse Thunderbird's OAuth client registration.

tbmail auth-import --account account2 --protocol imap --token-file /private/imap-token.json
tbmail auth-import --account account2 --protocol smtp --token-file /private/smtp-token.json
tbmail auth-status --account account2 --protocol imap
tbmail status --account account2 --network

Use --token-file - to read JSON from stdin. Accepted fields are access_token, refresh_token, expires_in or expires_at, client_id, client_secret, token_url, scope, and token_type (Bearer). Refresh needs a refresh token, client ID, and HTTPS token endpoint; provider settings determine whether a client secret is needed. Default endpoints are provided for Gmail and Microsoft mail. Set the selected account's authentication mode to oauth2 if Thunderbird has not already done so. IMAP and SMTP tokens are stored separately; authorize the appropriate protocol scopes with your provider.

Environment tokens use TB_IMAP_OAUTH_ACCESS_TOKEN and corresponding REFRESH_TOKEN, EXPIRES_AT, CLIENT_ID, CLIENT_SECRET, TOKEN_URL, SCOPE suffixes, or TB_SMTP_OAUTH_*; account-prefixed forms work too. Keyring storage is preferable to long-lived environment secrets. Token values never appear in status responses. Protocol and refresh behavior have synthetic tests; provider consent and organization policies still require your own authorized account setup.

Provider references: Gmail XOAUTH2 and Microsoft IMAP/SMTP OAuth.

Development and verification

uv sync --locked
uv run python -m unittest discover -s tests -v
uv run python -m compileall -q thunderbird_mcp tests
uv run python scripts/benchmark_search.py --require-recall 1 --require-precision 1 --require-no-false-positives

The tests use synthetic mailboxes and mocked mail transports. They exercise schema upgrades, reference stability, live UID validation and unchanged read flags, account isolation, OAuth refresh, reply headers, search behavior, attachment exports, exact draft delivery, duplicate/ambiguous-send prevention, client-config preservation, and a real MCP stdio client/server handshake. No tests send mail.

GitHub Actions runs the synthetic suite, keyword benchmark, package build, and clean wheel installation checks on Linux (Python 3.10–3.12) and macOS (Python 3.12). The installation check uses a fresh virtual environment and temporary home, checks client setup and launchers, and calls the installed MCP from an unrelated working directory. Live credentials and paid AI clients are not used by CI. These checks do not establish real desktop/keyring integration on every platform. Windows deployment is not yet verified.

For v0.3.0, all 162 tests, the keyword benchmark, and package builds passed on the full matrix in GitHub Actions. Local verification on 2026-11-02 also covered Codex, Claude Code, and OpenCode with GLM calling the tools against synthetic mail, session reconnects, read-only live IMAP access with unchanged read flags, and attachment text retrieval. These checks did not send mail.

The benchmark generates 78 messages with 13 labeled query cases and distractors outside each filter's scope. It reports recall@k, returned-result precision, reciprocal rank, top-result accuracy, negative-query results, and latency. It is a small regression corpus, not evidence that this MCP outperforms another system. Optional --semantic also evaluates local embeddings and may download the model. To make a comparison on identical inputs:

uv run python scripts/benchmark_search.py --export-corpus /tmp/synthetic-mail-benchmark
# Index its profile/ mbox files in another system; run queries.json cases.
# Save ranked Message-IDs as {"case-id": ["<message-id>", ...], ...}.
uv run python scripts/benchmark_search.py --score-results /tmp/other-system-results.json

The dependency lock is checked in and the MCP SDK is constrained to its supported v1 API. Keep backups/, exports/, private retrieval evaluation data, logs, and private SQLite files out of Git. Published tests contain synthetic mail only.

Feedback and contributing

Installation successes and failures are both useful. Use the installation feedback form to share your platform, versions, and which checks worked. Independent installation reports are still needed; automated checks and maintainer smoke tests are tracked separately. See CONTRIBUTING.md for development and verification, and CHANGELOG.md for release changes.

Released under the MIT licence.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to search and read a local, provider-independent email archive, reconstruct contacts and interactions, and prepare draft responses without sending anything.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to read, search, send, schedule, and organize email across multiple IMAP/SMTP accounts, including drafts, provider-aware labels, real-time inbox watching, verification-code capture, and analytics. Supports OAuth2, calendar extraction, and both local stdio and networked Streamable HTTP access.
    18 npm
    ISC
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to read, search, send, manage, schedule, and analyze mail across multiple IMAP/SMTP accounts, with real-time inbox watching, verification-code capture, calendar extraction, and provider-aware labeling. It also exposes drafts, templates, bulk operations, analytics, and OAuth2 support over stdio or Streamable HTTP.
    18 npm
    ISC
  • A
    license
    A
    quality
    B
    maintenance
    Enables sandboxed AI assistants to query a local SQLite/FTS5 mail archive through read tools and to export attachments, draft replies, and send approved drafts.
    9
    2
    MIT