agentinbox
OfficialReads, searches, drafts, sends and organizes Gmail and Google Workspace mail on behalf of AI agents. Exposes MCP tools such as search_threads, get_thread, reply, send_message, modify_labels and wait_for_events, delivers new-mail/reply/label-change events through Gmail push via Pub/Sub or polling, and enforces per-agent per-mailbox guardrails (capabilities, label scope, send mode, recipient allowlists, reply-only, send caps) with an audit log.
Uses the user's own Google Cloud project and OAuth client for Gmail access, and integrates with Cloud Pub/Sub to provision a topic, grant Gmail's push service publish rights, and create a pull subscription so new mail events arrive within seconds without requiring a public URL.
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., "@agentinboxsearch my inbox for unread emails from today"
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.
agentinbox
A self-hosted Gmail broker for AI agents. One small daemon holds your Gmail credentials; every agent gets its own scoped API key and reaches mail through MCP tools (read, search, draft, send, organize) and signed webhooks or an MCP event feed for new mail. Guardrails decide what each agent may do per mailbox, every action lands in an audit log, and a web console makes setup and management painless. It runs anywhere Node does, against your own Google Cloud project, with no cloud service in between.
Features
One place for credentials. Connect Gmail and Google Workspace accounts once. Refresh tokens are encrypted at rest. Agents only ever see an
aib_…key.MCP for every agent. Streamable HTTP at
/mcp:search_threads,get_thread,reply,send_message,modify_labels,wait_for_eventsand more. Each agent sees only the tools its grants allow.Real-time events. New mail, replies to threads you sent into, and label changes arrive within seconds through Gmail push via a Pub/Sub pull subscription (no public URL needed), or by polling with zero extra setup.
agentinbox listenwakes agents on a laptop behind NAT.Webhooks. Filter by account, label, sender, recipient, subject or attachments. Payloads are signed per Standard Webhooks, retried with backoff, logged, and replayable.
Guardrails that don't degrade content. Per agent and mailbox: capabilities, label scope, send mode (direct, draft-only or human approval), recipient allowlists, reply-only and send caps. Email content is cleaned, not censored.
A console you'll actually enjoy. Guided Google setup, connect snippets for Claude Code, Codex and Cursor, an approvals inbox, live events, delivery logs and the audit trail. Dark mode included.
A ready-made agent skill.
SKILL.mdteaches agents to use the tools well and to treat email as data.
Related MCP server: Gmail MCP Server
Quick start
Requires Node.js 22.13 or newer.
npx agentinbox-cliThe npm package is agentinbox-cli; the command it provides is agentinbox. Use npx agentinbox-cli … for one-off
runs, or npm i -g agentinbox-cli and then agentinbox ….
agentinbox starts on port 8425 and prints a one-time setup link (and opens it if it can):
agentinbox v0.1.0 · data in /home/you/.agentinbox
First run: open this link to claim the instance and finish setup:
http://localhost:8425/setup?token=…Open it, choose an admin password, and the setup wizard walks you through the rest in about ten minutes.
Setting up Google
agentinbox uses your own Google Cloud project and OAuth client. Gmail's read scopes are restricted, so a shared client isn't an option, and this way your mail never passes through anyone else's app. The wizard shows each step with the exact values to paste; in short:
Confirm the public URL agents and Google will use to reach agentinbox.
Create a Google Cloud project, paste its project ID, and enable the Gmail API (and the Cloud Pub/Sub API if you want push).
Configure the OAuth consent screen (Google Auth Platform) and add the scopes the console lists:
Internal if every mailbox is in your Google Workspace organization. No review, no expiry, nothing else to do.
External for personal
@gmail.comaccounts (or mailboxes outside your org). Then publish the app to "In production". Left in Testing, Google expires refresh tokens after 7 days and your accounts silently disconnect. You don't need Google's verification for your own use: on consent, click Advanced → Go to (app name) past the "unverified app" warning.
Create an OAuth client of type Web application, add the redirect URI the console shows (
<public URL>/oauth/callback), and paste the client ID and secret into the console. If the public URL is plain HTTP on a non-localhost address, Google won't redirect to it: the console then shows alocalhostredirect URI and, when connecting, asks you to paste the URL you land on.Connect accounts. Each one goes through Google's consent screen and grants
gmail.modifyplusopenid email profile, nothing more.Choose how new mail arrives:
Pub/Sub (recommended): one click creates the topic, grants Gmail's push service (
gmail-api-push@system.gserviceaccount.com) permission to publish, and creates a pull subscription. It asks one connected account for the extrapubsubscope (only then); that account needs Owner or Pub/Sub Admin on the project (Editor can't change topic permissions). Events arrive in seconds, with no inbound connections. If a Workspace organization policy (domain-restricted sharing) blocks the grant, the console says so; use polling.Polling: no extra setup; checks each mailbox every 30 seconds by default.
Then create an agent and give it a grant on a mailbox.
Connecting agents
Create an agent in the console (Agents → New agent) to get its API key; the key is shown once. The console has copy-paste snippets with your URL and key filled in, and a Test connection button. By hand:
Claude Code
claude mcp add --transport http --scope user agentinbox http://your-host:8425/mcp \
--header "Authorization: Bearer $AGENTINBOX_API_KEY"Codex (~/.codex/config.toml, with export AGENTINBOX_API_KEY="aib_…" in the environment Codex runs in)
[mcp_servers.agentinbox]
url = "http://your-host:8425/mcp"
bearer_token_env_var = "AGENTINBOX_API_KEY"Cursor (~/.cursor/mcp.json, or .cursor/mcp.json in a project) and most other MCP clients
{
"mcpServers": {
"agentinbox": {
"url": "http://your-host:8425/mcp",
"headers": { "Authorization": "Bearer aib_…" }
}
}
}Cursor also accepts "Bearer ${env:AGENTINBOX_API_KEY}" in headers, so the key can stay out of the file. Any MCP
client that speaks streamable HTTP works: endpoint /mcp, header Authorization: Bearer <key>.
The agent skill
SKILL.md teaches an agent the tools, Gmail search syntax, reply etiquette, how sends and denials work, the event loop,
and that email content is data, never instructions. Install it on the machine where the agent runs (no running
agentinbox needed there):
npx agentinbox-cli skill install --url https://mail.example.com # ~/.claude/skills/agentinbox/SKILL.md
npx agentinbox-cli skill install --target codex --url https://mail.example.com
npx agentinbox-cli skill install --target ./skills --url https://mail.example.com
npx agentinbox-cli skill print --url https://mail.example.com # to stdoutEvery instance also serves it at /skill.md with its public URL filled in.
Events and webhooks
Event types: message.received, message.sent, thread.reply_received (a reply in a thread the account sent into),
label.added, label.removed, account.auth_failed and sync.gap. Agents only receive events for mailboxes and
labels their grants let them read.
Agents without an HTTP endpoint (most coding agents) use the MCP tools
wait_for_events(long poll),list_eventsandack_eventswhile a session is running. The server keeps each agent's position. To start an agent when mail arrives, useagentinbox listen.Agents with an endpoint get webhooks: create one under Webhooks, pick event types and filters, and optionally include the cleaned message body. A delivery succeeds on a
2xxwithin 10 seconds (redirects aren't followed); failures are retried 8 times with growing delays (30 s up to 12 h, about a day in total) before they're marked dead, and any delivery can be replayed from the console.
Each webhook request carries webhook-id, webhook-timestamp and webhook-signature headers (v1, + base64
HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}). Verify them with any
Standard Webhooks library and the whsec_… secret shown when
you create the webhook:
import { Webhook } from "standardwebhooks";
const wh = new Webhook(process.env.AGENTINBOX_WEBHOOK_SECRET);
const payload = wh.verify(rawBody, request.headers); // throws if the signature or timestamp is badA dependency-free Node version is in the skill. webhook-id is the delivery id: it
stays the same across retries and replays of one webhook. To dedupe across webhooks, use the event id data.id.
Webhooks may target public https URLs and, through the default allowlist (localhost, 127.0.0.0/8, ::1),
loopback. Other private addresses (for example a LAN host or n8n.internal) must be added to the webhook allowlist in
Settings as a hostname, *.suffix, IP or CIDR. Plain http works only for loopback and allowlisted hosts.
Wake local agents: agentinbox listen
MCP events only reach an agent while a session is running, and webhooks need a reachable endpoint. agentinbox listen
runs on the agent's own machine, holds an outbound event stream to agentinbox (no open ports or tunnels), and runs a
command or POSTs to a localhost URL for each new event:
# Pair this machine's agent: prints a code to approve in the agentinbox console, then registers the MCP server
npx agentinbox-cli connect https://mail.example.com --tool claude-code --setup-mcp
# Run the ready-made Claude Code recipe once per event (event JSON on stdin) as a background service
npx agentinbox-cli listen service install --exec "$(npx agentinbox-cli listen --print-recipe claude-code)"
npx agentinbox-cli listen statusThe approver picks the mailboxes and capabilities: the approval dialog defaults to Read only, and once send access
is granted, draft_only is the default send mode. The key goes into a profile file (mode 0600) and is never printed.
Listener commands run in a private working directory, so no stray CLAUDE.md, .mcp.json or hooks apply. Delivery is
at-least-once with retries and a dead-letter list. Recipes for Codex, Cursor and Hermes (--print-recipe <tool>),
forward mode, filters and the security model are in docs/listen.md.
Security model
agentinbox assumes agents can be manipulated by the mail they read. So it keeps content intact and puts the hard limits on actions.
Content is cleaned, not censored. Agents get full-fidelity mail converted to tidy Markdown, with links and tables.
Only what a human reader couldn't see is removed: hidden CSS (display:none, zero-size, transparent or
same-colour text), HTML comments, scripts and styles, zero-width characters, Unicode tag characters and bidi overrides.
Each body is wrapped in one <email_content … untrusted> delimiter and comes with signals: SPF/DKIM/DMARC results, an
external-sender flag, whether hidden content was removed, and phrases typical of prompt injection. Signals are
informational; nothing is withheld because of them. Very long bodies are cut at 20,000 characters by default (Settings),
with a note saying so.
Actions are strictly controlled. A new agent can do nothing. Each grant (agent × mailbox) sets:
Control | |
Capabilities |
|
Label scope | Confine the agent to threads carrying certain labels. Searches are rewritten and fetches by id are checked; anything outside looks "not found". |
Send mode |
|
Allowed recipients | Exact addresses or globs such as |
Reply only | No new messages or forwards; replies only, inside threads the agent can see, to people already in the conversation. The reply goes to the message's Reply-To or From, which the sender chooses: use Allowed recipients to limit who can be written to. |
Send caps | Per rolling hour and 24 hours, counting sends that became drafts or approvals too |
Everything is audited. Every tool call and every console change is recorded with its outcome, and denials name the guardrail that blocked them.
The console is protected by an admin password (set through the one-time setup link), HttpOnly session cookies that expire after 30 days of inactivity (90 at most), a CSRF header on every API change, request size limits, a Content Security Policy that allows only the console's own scripts, and a 15-minute lockout after five wrong passwords (or 30 attempts) from one address. When failures pile up across many addresses, every sign-in attempt is slowed down instead. It never allows being framed. The data directory and database are readable only by their owner.
Recommendations: split agents that read untrusted mail from agents that can send; prefer draft_only or approval;
use allowlists for anything that sends directly.
Running it
As a service
npx agentinbox-cli service installInstalls a background service that starts at boot: systemd on Linux (a user service with lingering enabled, or a system
service when run as root) and launchd on macOS. The service runs its own copy of agentinbox (the latest npm release),
installed in the data directory, so it never depends on the npx cache; if that install fails, the running service is
left as it was. Re-run service install to update; service status and service uninstall do what you'd expect, and
uninstalling keeps your data. --port, --host and --data aren't remembered: pass the same ones each time.
AGENTINBOX_MASTER_KEY, TRUST_PROXY, TRUST_PROXY_HOPS, TRUST_PROXY_FROM and AGENTINBOX_LOG_LEVEL are taken from
your shell and remembered in <data>/service.env, so later installs keep them (set one to an empty value to remove it).
Only one agentinbox runs per data directory; a second one refuses to start.
When a newer version is published, the console shows Update available with the exact command to run, flags included.
The unit runs the Node.js binary you installed it with. If you remove or replace that Node, run service install again.
If the service doesn't come up, service install rolls back and prints where to look (systemctl --user status agentinbox, journalctl --user -u agentinbox). Over su or sudo -u there is often no systemd user session: log
in as that user directly, or enable lingering and set XDG_RUNTIME_DIR=/run/user/$(id -u) first.
A service that hasn't been claimed yet doesn't print its setup token to the journal. Get the link with
npx agentinbox-cli reset-password --data <dir>, or read the token from <data>/setup-token.
With your own supervisor
On other systems, or to manage it yourself (Docker, runit, supervisord, pm2), install the command and run it in the foreground:
npm i -g agentinbox-cli
agentinbox start --no-open --data /var/lib/agentinboxUse the installed agentinbox binary rather than npx: npx doesn't pass stop signals on, so agentinbox wouldn't shut
down cleanly.
Behind a reverse proxy
agentinbox serves plain HTTP. To expose it beyond your network, put it behind a TLS-terminating proxy (Caddy, nginx, Tailscale Funnel) and:
bind it to localhost:
--host 127.0.0.1set
TRUST_PROXY=1, so lockouts apply per client and cookies are markedSecureon https. The client address is taken from the right end ofX-Forwarded-For; with more than one proxy in front, setTRUST_PROXY_HOPSto their number. Forwarding headers are believed only from a proxy on the same host (loopback); if the proxy runs on another machine, list its address inTRUST_PROXY_FROM(for exampleTRUST_PROXY_FROM=10.0.0.5or a CIDR like10.0.0.0/24;/0is refused). The proxy must append to (or overwrite)X-Forwarded-For; a tunnel or proxy that passes the header through unchanged lets clients choose their own addressset Settings → Public URL to the https address, so MCP snippets, the skill and the OAuth redirect use it
GET /health (or /api/health) needs no auth and suits uptime monitors: 200 when healthy, 503 when degraded, for
example when an account needs to be reconnected or ingest is failing. It reports only a status, the version and
whether setup is complete.
Options
With a global install (npm i -g agentinbox-cli); otherwise prefix npx agentinbox-cli instead of agentinbox.
agentinbox [start] [--port 8425] [--host 0.0.0.0] [--data ~/.agentinbox] [--no-open] [--dev]
agentinbox service install [--port …] [--host …] [--data …]
agentinbox service uninstall [--data …]
agentinbox service status
agentinbox skill install [--target claude|codex|agents|<dir>] [--url <public url>]
agentinbox skill print [--url <public url>]
agentinbox reset-password [--data …]
agentinbox connect <url> … / listen … / listen service install … # agent machines, see docs/listen.mdFlag | Env | Default | |
|
|
| Port to listen on |
|
|
| Bind address. Use |
|
|
| Data directory |
| Base64 32-byte key for encrypting secrets, instead of | ||
|
| ||
|
| Number of proxies in front of agentinbox (which | |
| loopback | Other proxy addresses whose forwarding headers are believed: comma-separated IPs or CIDRs (IPv4 or IPv6) | |
|
|
| |
|
|
|
|
Forgot the admin password? Run npx agentinbox-cli reset-password on the server, as the user agentinbox runs as (sudo for a
system service) and with the same --data. It signs out all sessions and prints a new setup link; a running instance
picks it up without a restart.
Data and backups
Everything lives in the data directory (~/.agentinbox by default):
File | |
| SQLite database (plus |
| Encrypts Google tokens, webhook secrets and the OAuth client secret (mode 0600) |
| The one-time setup token, until the instance is claimed |
| Marks the running instance (pid and port) |
| Environment the service was installed with, possibly including |
| Service output on macOS (launchd); on Linux use |
| The service's own install ( |
Back up agentinbox.db and master.key together: without the key, stored secrets can't be decrypted, and agentinbox
refuses to start rather than create a new one. Keep the key somewhere safer than the database backup, or supply it via
AGENTINBOX_MASTER_KEY. The database runs in WAL mode, so don't just copy the file while agentinbox is running: stop
it first (systemctl --user stop agentinbox), or take a consistent copy with
sqlite3 ~/.agentinbox/agentinbox.db ".backup /path/to/backup.db".
Received mail bodies are never stored; agentinbox keeps metadata (ids, headers, labels, snippets) and fetches content from Gmail on demand. The only bodies in the database are those of sends queued for approval. Events, deliveries, audit entries and decided approvals (with their bodies) are pruned after the retention period set in Settings (30 days by default).
Release channel
Every push to main (a quick second merge can supersede the first) is published to npm as
agentinbox-cli@<version>-nightly.<n> under the latest tag, built by GitHub Actions with npm trusted publishing and
provenance.
npx agentinbox-cli therefore runs the newest build of main, and service install always installs latest. To
stay on one build, run it pinned in the foreground: npx agentinbox-cli@<version>.
Development
pnpm install
AGENTINBOX_DATA=/tmp/agentinbox-dev pnpm devpnpm dev runs the server on :8425 in watch mode (API, MCP and OAuth only) and the console on Vite at :5173, which
proxies to it. Open the Vite URL; the printed setup link already points there.
pnpm typecheck && pnpm lint && pnpm test
pnpm build # console → dist/web, server → dist/cli.js
node dist/cli.js # run the buildStack: TypeScript on Node 22+, Hono on @hono/node-server, node:sqlite, the MCP TypeScript SDK, and thin fetch-based
Google clients (no googleapis, so npx stays fast). The console is Vite, React 19, TanStack Router / Query / Table /
Form and shadcn/ui on Tailwind v4.
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Gmail, Outlook, iCloud, Fastmail and any IMAP mailbox for your agent. Scoped OAuth, no send tool.
Gmail, Outlook, Drive, OneDrive and calendars for AI agents. Many accounts, one endpoint, audit log.
Email inboxes for AI agents: send, receive, search, drafts with owner approval, and webhooks.
Email inboxes and calendars for AI agents: send, receive, search, draft and schedule.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceEnables AI assistants to interact with Gmail through natural language, supporting email sending/reading, searching, draft management, label organization, and batch operations with secure OAuth authentication.-
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to read, search, organize, and draft emails in Gmail inboxes with support for multiple accounts, OAuth authentication, and 26 comprehensive tools for email management.181 npm3MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to manage Gmail emails, including sending, searching, and organizing with labels and attachments via OAuth2.32 npm2MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage Gmail through natural language interactions, including sending, reading, searching emails, and managing labels with auto authentication support.17,624 npm1MIT