Skip to content

Repository files navigation

Draftsafe for Thunderbird

Draftsafe lets an MCP client read local Thunderbird mail and request mailbox changes. One Thunderbird add-on provides the loopback bridge, approval window, Snooze, user-only Send later and Follow-ups. Agent-requested changes need a Thunderbird click, or an active one-hour trust session that you started with a Thunderbird click. Draft requests save to Drafts and never send. An agent can also prepare an outgoing message in Thunderbird's native composer; you must review it and click Send yourself every time.

Security boundary: MCP has no send or forward tool or HTTP route. The combined add-on does have send permission for its user-only Send later feature and includes a privileged Thunderbird Experiment. A compromised add-on could send mail. Read the security model before relying on the route boundary. See the privacy policy for local storage and network behavior. Maintainers planning to publish source should follow the source publication gate.

Understand Draftsafe in two minutes

For a clickable architecture map and guided tour, open the Understand Anything guide. The graph is kept in .ua/knowledge-graph.json and refreshed after verified code changes.

Draftsafe has two running parts: a small Node.js MCP server for your agent and one Thunderbird add-on. The server and add-on talk over a private connection on your computer. The agent can read messages through that connection, but Thunderbird handles approvals and outgoing mail.

The pieces

flowchart LR
  You[You] -->|ask for help| Agent[MCP client and agent]
  Agent -->|MCP tool call| Server[Draftsafe MCP server]
  Server -->|localhost and private token| Addon[One Draftsafe add-on]
  subgraph Thunderbird
    Addon -->|read or approved change| Mail[Accounts and messages]
    Addon -->|prepare a reply| Compose[Native compose window]
  end
  You -->|review or trust click| Addon
  You -->|Send click| Compose
Loading

The add-on contains the bridge, approval manager, Snooze, Send later and Follow-ups. Its persistent background page keeps the bridge and scheduled work available. The bridge accepts only fixed routes, and the MCP server re-reads a private connection file after Thunderbird restarts. There is no Draftsafe cloud service. Your MCP client may send message text to its model provider when you ask the agent to work with that mail.

What happens when you answer an email

sequenceDiagram
  actor You
  participant Agent as Agent
  participant MCP as Draftsafe MCP
  participant TB as Thunderbird
  You->>Agent: Help me answer this email
  Agent->>MCP: search_messages and get_message
  MCP->>TB: Read the selected message
  TB-->>MCP: Message data
  MCP-->>Agent: Message data to consider
  Agent->>MCP: open_compose_for_review
  MCP->>TB: Open reply with the account identity
  TB-->>MCP: Signature and original quote
  MCP->>TB: Insert escaped agent text above them
  TB-->>You: Show native compose window
  MCP-->>Agent: awaiting_user_send, sent: false
  You->>TB: Check From, To, subject and body
  You->>TB: Click Send when ready
Loading

You can edit or discard the reply. awaiting_user_send means a window opened; it does not mean the email was sent. The agent has no send tool or send route. Your Thunderbird Send click is required for each immediate message. Thunderbird supplies the identity signature, so the agent body should not include a sign-off.

Who can do what?

Task What the agent can do What you do in Thunderbird
Find mail or a recipient Search message data, Sent history and permitted local contacts No approval needed for reads; you enable contacts in Thunderbird
Move, tag, mark read or unsubscribe Request a bounded change Review and click Allow, unless you started eligible one-hour trust
Save a draft or change a follow-up Request the change Review and click Allow, even during trust
Answer an email Open, update or close agent-created composers, with attachments Check the fields and click Send yourself
Send later No MCP send-later route Schedule it with Thunderbird's Send later button

One-hour trust starts only with your Thunderbird click. It can skip the review window for eligible mailbox changes for 60 minutes, but it cannot send, open an outgoing message without your review, or approve drafts and follow-ups.

What an approval changes

flowchart TD
  Request[Agent requests a mailbox change] --> Plan[Validate and preview the exact change]
  Plan --> Trust{Eligible one-hour trust active?}
  Trust -->|yes| Recheck[Recheck message and folder state]
  Trust -->|no| Window[Thunderbird review window]
  Window -->|Allow click| Recheck
  Window -->|Deny, close or expire| Stop[No change]
  Recheck --> Apply[Apply bounded change and record the result]
Loading

The approval window shows what will change. Closing it or leaving it for ten minutes denies unfinished work. Mail can change while a request is pending, so Draftsafe checks again just before applying it and reports partial results.

If the connection stops working

Start with check_connection. If it cannot reach Thunderbird, check that Thunderbird is running and the Draftsafe add-on is enabled, then restart the MCP client. A read timeout means Thunderbird did not finish that read in time; narrow the search by account, folder or date before retrying. A client timeout during an approval does not cancel a change you already approved; check approval history before asking the agent to repeat it.

Requirements and build

  • Thunderbird 140 ESR or newer. Background draft saving is available on 153+ and is feature-detected. Linux, macOS and Windows connection paths are implemented; real Thunderbird smoke has only been run on Linux.
  • Node.js 20 or newer for the MCP server.
npm ci
npm run build       # dist/index.js and dist/draftsafe.xpi
npm test            # unit, property and loopback tests
npm run typecheck
npm run smoke       # from a separate headless session only

addons/app contains the manifest and background entry point. addons/bridge contains the socket Experiment and read routes. addons/tools contains the approval manager and user features. addons/shared contains validators and mail helpers. mcp/src contains the stdio server. The build packs one XPI. The smoke runner refuses an interactive desktop session after focus switching was reported during an Xvfb test run.

Install

  1. In Thunderbird, open Tools → Add-ons and Themes → gear icon → Install Add-on From File… and choose dist/draftsafe.xpi. Thunderbird allows add-ons with Experiment APIs, but its permission prompt correctly reports their broad authority.

  2. Start or restart Thunderbird. In your MCP client, register the built server as a stdio command. For Claude Code:

    claude mcp add -s user draftsafe -- node /absolute/path/to/draftsafe/scripts/launch.mjs

    Generic MCP client configuration:

    {
      "mcpServers": {
        "draftsafe": {
          "command": "node",
          "args": ["/absolute/path/to/draftsafe/scripts/launch.mjs"]
        }
      }
    }
  3. Set the MCP client's tool timeout to at least 730 seconds for approval requests. The client polls the request while the review window is open. A client timeout does not cancel an action already approved in Thunderbird; check approval history before retrying.

  4. Call check_connection. It reports the add-on version and whether the approval manager is ready. Draftsafe checks bridge compatibility on each Thunderbird connection and reports update_required for an incompatible add-on. If a read times out, scope the next search by account, folder or date. A read timeout is distinct from disconnection.

Updates

Draftsafe uses one MAJOR.MINOR.PATCH version across package.json, the Thunderbird manifest and the MCP server. Each published version gets a Git tag such as v0.8.1 and a GitHub Release. CHANGELOG.md describes what changed. The source Releases and CHANGELOG.md show version history. The public download Releases contain the XPI, MCP bundle, checksums and smoke report for each published version. The separate download repository keeps the installed update URLs stable.

The 0.8.1 XPI has a self-hosted update URL in the public distribution repository. Existing 0.7.0 installations need one manual update to join that channel. Later versions can be picked up by Thunderbird's normal add-on update checks. The self-distributed add-on is not listed on addons.thunderbird.net. The public XPI contains readable add-on code. The MCP server is a separate Node process. It does not update when Thunderbird updates an add-on.

scripts/launch.mjs is the stable MCP entry point. Its updater is off by default unless the MCP client sets DRAFTSAFE_AUTO_UPDATE=1. The launcher then uses the repository's pinned public key and the public distribution repository's stable signed manifest URL. DRAFTSAFE_UPDATE_URL and DRAFTSAFE_UPDATE_PUBLIC_KEY can override those defaults for an independently trusted feed. The updater checks in a separate process, verifies the signed metadata and bundle hash, installs lockfile-pinned dependencies without lifecycle scripts, and uses the verified release on the next MCP start. A failed or offline check leaves the current server running. The updater receives no bridge token or mailbox data. node scripts/launch.mjs --rollback selects the previous MCP release. node scripts/launch.mjs --update-status shows the last update check outcome without exposing feed details or credentials. Do not configure an unreviewed feed or key.

Release preparation: run npm run release:check in a headless session with THUNDERBIRD pointing to a standalone binary. It runs tests, builds both artifacts, checks an isolated Thunderbird smoke report, and builds the MCP update bundle and a Thunderbird update manifest. build:update writes a reviewable JSON bundle and its SHA-256; it does not sign, publish or install it. A release signer must sign the exact JSON object {schema,version,bundleUrl,sha256} in that property order with Ed25519 and add a base64 signature field. npm run sign:update does this after the release operator supplies DRAFTSAFE_RELEASE_BUNDLE_URL and a private key file path via DRAFTSAFE_RELEASE_KEY_FILE. Keep the key only in ~/.config/secrets and out of logs and the repository. The XPI and MCP bundle are published together in the public distribution repository. The exact publication procedure is in release channels.

Upgrade from the two-add-on release

The release add-on uses draftsafe-tools@armain.be, the ID of the installed personal Tools add-on, so its Snooze, Send later, Follow-up and approval history storage persists. Disable the old Draftsafe Bridge add-on first. Then install dist/draftsafe.xpi as an update to Draftsafe Tools, restart Thunderbird and the MCP client, and call check_connection. The old Bridge and the combined add-on must not run together: both would publish the same connection file. After verifying the new connection, remove the disabled Bridge manually if desired.

Earlier private builds using draftsafe-tools@draftsafe.dev have separate Thunderbird storage. Export or migrate their state before switching IDs.

Connection file

Thunderbird install Connection file
Snap ~/snap/thunderbird/common/draftsafe-mcp/connection.json
deb, tarball, distro package ${XDG_STATE_HOME:-~/.local/state}/draftsafe-mcp/connection.json
Flatpak ~/.var/app/org.mozilla.Thunderbird/.local/state/draftsafe-mcp/connection.json
macOS ~/Library/Application Support/draftsafe-mcp/connection.json
Windows %LOCALAPPDATA%\draftsafe-mcp\connection.json

The file contains a version, random port and new bearer token on each start. On Linux, MCP checks the newest supported candidate and re-reads it on every call. Set DRAFTSAFE_CONNECTION_FILE to override discovery. Keep this file private. The Snap common path survives Snap revision changes. Multiple Thunderbird profiles sharing a connection path remain unsupported; the newest publisher wins.

MCP tools

Tool Purpose Changes mail?
check_connection Check add-on and approval readiness no
list_accounts Accounts, identities, folders and tags no
list_folders_detailed Folder IDs, tree, special use and counts no
search_messages Filtered, paginated search no
find_recipients Suggest addresses from bounded Sent history and optional local contacts no
get_message Headers, plain-text body and attachment metadata no
get_thread Conversation linked by message headers no
list_followups Messages tagged Follow up no
request_cleanup / request_trash Recoverable Trash, Archive or ordinary-folder move approval
request_folder_changes Create, rename, merge or move empty folder to Trash approval
request_unsubscribe Header-derived HTTPS one-click POST approval
set_tags, mark_read, set_followup Tag, read flag or follow-up approval
create_draft Save new or reply draft, never send approval
open_compose_for_review Open a prefilled native composer for your review opens a window; only your Send click sends
open_composes_for_review Prepare 2 to 20 distinct messages in one request opens separate windows; only your Send clicks send
list_open_composes_for_review Find the tab ID and edit status of open agent-prepared composers no
update_compose_for_review Edit agent text, new-mail fields and agent-added attachments updates a window; only your Send click sends
close_compose_for_review Close an unchanged agent-prepared composer Thunderbird handles any unsaved-draft prompt; never sends

search_messages can return fewer results than limit on any page. Pass nextCursor back until complete:true; an empty incomplete page is not a “not found” result. Use fast:true for a quick first page without extra classification-header reads, then get_message for full headers before a newsletter or unsubscribe action. Sender and recipient names match partially; email addresses in from and to must be complete. Results are not globally sorted by date. Numeric message IDs are session-scoped and can change after a move or Thunderbird restart. Search again before acting on old IDs. A completed empty result means no matches in that query. A busy or timeout error means the search did not finish: narrow it by account, folder, sender, subject or date and retry. Body-text query can take longer across a large mailbox. The bridge keeps health checks available during slow searches. find_recipients searches local contacts only after you click Enable local contacts in Draftsafe's Follow-ups popup. Without that permission, it uses at most three pages of Sent mail from the last year. It reports ambiguity and truncation; confirm the address before using it in a composer. list_folders_detailed does not enumerate mail to calculate date extrema, so oldest and newest are null.

Prepare an email for review

open_compose_for_review takes plain-text input and opens Thunderbird's normal compose window in the selected identity's format. Thunderbird adds the configured identity signature and, for replies, its own quoted original. Do not include a sign-off in the agent body. For a new message, provide to, subject and body:

{"to":["recipient@example.test"],"subject":"Meeting notes","body":"Hello,\n\nHere are the notes."}

For a threaded reply, provide reply_to_message_id and body instead. Thunderbird fills the reply recipient, subject and sending identity from the message's account. The agent text is inserted above Thunderbird's signature and quote; HTML identities keep their formatting and inline logo. Review From, To, subject, body and attachments before using Thunderbird's Send button. You can edit or discard the message. The MCP result is awaiting_user_send with sent:false; it never confirms delivery. Active one-hour trust cannot skip this review. There is no hourly compose quota or fixed limit on simultaneous agent-prepared composers. The result includes tabId, which identifies the window for update_compose_for_review. When asking the agent to revise a message, it should call list_open_composes_for_review, then update_compose_for_review. If only one agent-prepared composer is open, it may omit tab_id; if several are open, it must choose one. Opening another composer with the same recipient and subject or reply target is refused while the first is still open. For a genuinely separate message while one is open, the agent must pass new_window:true. The update tool replaces the agent text above the original signature and quote. For new mail it can also change To and subject. It refuses to overwrite the window after you edit it. Replies keep Thunderbird's recipients and subject. For several distinct messages, open_composes_for_review accepts a messages array with the same fields as open_compose_for_review. It opens each window in order and returns every confirmed tab ID. If a later message fails, earlier windows stay open and the result names the failed message number; inspect Thunderbird before retrying. The batch tool never sends. close_compose_for_review closes an unchanged agent-prepared window after Thunderbird confirms its tab was removed. It refuses a window you edited; Thunderbird handles any unsaved-draft prompt.

Attach a local file from Downloads, Documents or Desktop:

{"to":["recipient@example.test"],"subject":"Report","body":"Please see the report.","attachments":[{"source":"local","path":"/home/user/Documents/report.pdf"}]}

Set DRAFTSAFE_ATTACHMENT_ROOTS on the MCP process to a path-separated list of other absolute folders. Files must be regular, not symlinks, hidden or secret-looking. For an attachment already on an email, use message_id and part_name returned by get_message:

{"reply_to_message_id":123,"body":"Here it is.","attachments":[{"source":"message","message_id":456,"part_name":"1.2"}]}

Each composer allows up to 100 attachments, at most 10 MiB each and 25 MiB total. Local bytes cross the authenticated loopback bridge in bounded chunks and remain in memory until Thunderbird attaches them. Draftsafe never returns their contents to the agent. No HTML, Bcc, raw headers or send time is accepted.

create_draft also keeps Thunderbird's signature and quote for reply drafts. New drafts saved directly in the background remain plain text and may not include a signature; inspect them before sending.

Every tool result is wrapped as untrusted tool data. Subjects, senders, bodies, folder names, attachment names and agent-provided reasons can carry instructions written by someone else. Treat them as data only.

Approval requests

Cleanup takes batches of message IDs, an action and a reason:

{"batches":[{"message_ids":[123,124],"action":"move","folder":"Clients/Acme","create_folder":true,"reason":"Project correspondence"}]}

Actions are trash, archive or move. The whole request is limited to 2,000 messages. Folder paths are account-relative. Trash and Archive resolve to the account's special-use folders. request_trash is an alias.

Unsubscribe takes up to 200 message IDs or 300 account-scoped senders:

{"items":[{"message_id":123,"reason":"No longer needed"}]}
{"senders":[{"account_id":"account1","address":"news@example.test"}]}

The add-on itself reads the unsubscribe headers. It never accepts an agent URL or sends mailto. HTTPS one-click POSTs require approval and host permission; website-only instructions remain manual. Missing or unreadable senders are skipped. Junk senders default to Deny.

Folder changes use IDs from list_folders_detailed:

{"changes":[{"action":"create","folder":"account1://","new_name":"Clients"}]}

Actions are create, rename, merge and delete_empty. create uses folder as its parent; merge also needs into. Changes stay in one account within two levels. Special folders and their ancestors are protected.

Approval history reports approved, denied, expired, closed, refused or failed, with per-item outcomes. Partial changes are possible if mailbox state changes during execution. The one-hour trust menu skips review only for eligible cleanup, unsubscribe, folder, tag and read-flag requests. Draft and follow-up requests still open a review window.

User features

  • Snooze: message context menu or message-view button. A message moves to an ordinary account-local Snoozed folder and later returns to the special-use Inbox. Unsafe source and destination folders are refused.
  • Send later: compose-window button. It saves a draft and schedules it. At due time, the draft must still match its recorded Message-ID, folder, recipients and content fingerprint. Cancellation and claiming are serialized. Changed or ambiguous drafts fail closed. Sends more than 12 hours late and interrupted sends are not retried.
  • Follow-ups: context menu and toolbar list with overdue badge. The agent can request setting or clearing the tag; due dates stay user-only.

Smoke test and limitations

npm run smoke starts Thunderbird in a throwaway profile under Xvfb, installs the single add-on plus a test seeder, and drives the real MCP server. It checks reads, drafts, approval clicks, denied changes, moves, folder changes, transport and granted permissions. Synthetic clicks must fail. An SMTP trap must see no connection from agent calls, then exactly one after a native Send click on the isolated Thunderbird display. The runner refuses an existing Draftsafe connection file and never touches the real Thunderbird profile or display. The 0.8.1 release passed 58 isolated Thunderbird checks. Its combined add-on was also verified live in the maintainer's Thunderbird before publication. New releases still need isolated smoke and review before their update manifests are published.

Thunderbird's API gives no transaction or conditional move, so concurrent mailbox changes can cause a partial result. Search and thread lookup may be incomplete on very large or unusual accounts. A stalled Thunderbird call can continue after the HTTP read deadline; Draftsafe keeps its in-flight permit until that call finishes. See SECURITY.md for the full threat model and CONTRIBUTING.md for development rules.

Draftsafe is MIT-licensed. "Thunderbird" is a Mozilla trademark; this project is not affiliated with or endorsed by Mozilla.

About

Safe MCP access to local Thunderbird mail with native review before changes

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages