thunderbird-cli-bridge
v1.1.0
Published
HTTP↔WebSocket bridge daemon between thunderbird-cli (or any HTTP client) and the Thunderbird WebExtension. Stateless proxy, localhost-only.
Maintainers
Readme
thunderbird-cli-bridge
Stateless HTTP↔WebSocket bridge daemon between thunderbird-cli (or any HTTP client) and the Thunderbird WebExtension.
Part of the thunderbird-cli project.
What it does
A small Node.js daemon (tb-bridge) that runs on your machine and forwards HTTP requests to the Thunderbird WebExtension over WebSocket. No business logic — pure proxy.
HTTP client (CLI / MCP / curl) ──HTTP→ tb-bridge ──WS→ Thunderbird Extension
:7700 :7701- Stateless — each request gets a UUID, response correlated, then forgotten
- Localhost-only — listens on
127.0.0.1(no external exposure). Note that this bounds remote access only: any process running as the same OS user can reach the bridge. SetTB_AUTH_TOKENto require a token — see Authentication - Auto-reconnect on the extension side — the extension retries after 3s, backing off to every 15s while the bridge is down, and immediately when the machine returns from idle
- Zero config — works out of the box with default ports
Install
npm install -g thunderbird-cli-bridgeRun
tb-bridgeYou should see:
[bridge] HTTP server on http://127.0.0.1:7700
[bridge] WebSocket server on ws://127.0.0.1:7701
[bridge] Auth: disabled — any local process can call this bridge (set TB_AUTH_TOKEN to require a token)
[bridge] Waiting for Thunderbird extension to connect...Keep it running. For background operation:
# pm2
pm2 start tb-bridge --name tb-bridge
pm2 save
# or simple background
nohup tb-bridge > ~/.tb-bridge.log 2>&1 &Authentication
Binding to 127.0.0.1 keeps the bridge off the network, but it is not access control: every
process running as the same OS user can reach it, including any local agent or script that was
never wired up to the CLI. Where that matters — a shared machine, or an AI agent that should not
have mailbox access — set a token:
TB_AUTH_TOKEN=$(openssl rand -hex 32) tb-bridgeThe startup banner then reports [bridge] Auth: enabled, and every HTTP request must carry it:
curl -H "Authorization: Bearer $TB_AUTH_TOKEN" http://127.0.0.1:7700/bridge/statusRequests with a missing, malformed, or incorrect token get 401 with a JSON error body. The
comparison is constant-time. tb and tb-mcp send the header automatically when TB_AUTH_TOKEN
is set in their environment or bridge.authToken in ~/.config/thunderbird-cli/config.json.
Two behaviours are deliberate:
- Unset
TB_AUTH_TOKENdisables authentication and the bridge says so at startup. This keeps the default zero-config setup working. - An empty
TB_AUTH_TOKENis refused at startup. Failing open on an empty value would leave the bridge unauthenticated with nothing in the log to say so. Unset the variable to run without auth.
Give the token only to the callers that should have mailbox access — putting it somewhere every local process can read it (a world-readable file, a shared shell profile) puts you back where you started.
The WebSocket listener on
:7701, which the Thunderbird extension connects to, is not covered byTB_AUTH_TOKEN.
Browser protections
Independently of the token, the bridge refuses traffic that can only come from a web page in the user's browser, so a hostile site cannot send mail, delete messages, or impersonate the extension:
- HTTP requests with an
Originheader not listed inTB_BRIDGE_CORS_ORIGINSget403(default allowlist: the bridge's ownhttp://127.0.0.1:<port>andhttp://localhost:<port>). - HTTP requests whose
Hostis not an IP literal,localhost,*.localhostor*.internal(e.g.host.docker.internal) get403— this blocks DNS rebinding. Add other names withTB_BRIDGE_ALLOWED_HOSTS=name1,name2. - WebSocket handshakes from
http:,https:,file:ornullorigins are refused.
tb, tb-mcp and curl send no Origin header and are unaffected.
The bridge also pings the extension every 30 s (TB_BRIDGE_WS_HEARTBEAT_MS) and drops sockets
that stop answering, so requests fail fast after the machine sleeps instead of hanging.
Endpoints
GET /bridge/status
Returns bridge state without requiring the extension. Use this to check the daemon is up.
Subject to authentication when TB_AUTH_TOKEN is set.
curl http://127.0.0.1:7700/bridge/status
# {"bridge":"running","extension":"connected","httpPort":7700,"wsPort":7701}Everything else
All other paths get forwarded to the Thunderbird extension via WebSocket and the response is returned as JSON. See SPEC.md for the full route list (43 routes).
Prerequisites
The bridge alone does nothing useful — you need:
- Mozilla Thunderbird 128+ with the thunderbird-ai-bridge extension installed
- Optional:
thunderbird-cliorthunderbird-cli-mcpas the HTTP client
See the main repo setup guide.
License
MIT
