Skip to main content
Glama
unlaboredlabs

agentinbox

Official

agentinbox

CI npm License: MIT

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_events and 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 listen wakes 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.md teaches 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-cli

The 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:

  1. Confirm the public URL agents and Google will use to reach agentinbox.

  2. Create a Google Cloud project, paste its project ID, and enable the Gmail API (and the Cloud Pub/Sub API if you want push).

  3. 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.com accounts (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.

  4. 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 a localhost redirect URI and, when connecting, asks you to paste the URL you land on.

  5. Connect accounts. Each one goes through Google's consent screen and grants gmail.modify plus openid email profile, nothing more.

  6. 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 extra pubsub scope (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 stdout

Every 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_events and ack_events while a session is running. The server keeps each agent's position. To start an agent when mail arrives, use agentinbox 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 2xx within 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 bad

A 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 status

The 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

read (search, read, attachments, events), draft, send, organize (labels, archive, read state), trash

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

draft_only (default: a Gmail draft for a human to send), approval (queued in the console; approving sends it) or direct

Allowed recipients

Exact addresses or globs such as *@example.com

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 install

Installs 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/agentinbox

Use 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.1

  • set TRUST_PROXY=1, so lockouts apply per client and cookies are marked Secure on https. The client address is taken from the right end of X-Forwarded-For; with more than one proxy in front, set TRUST_PROXY_HOPS to 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 in TRUST_PROXY_FROM (for example TRUST_PROXY_FROM=10.0.0.5 or a CIDR like 10.0.0.0/24; /0 is 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 address

  • set 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.md

Flag

Env

Default

--port

PORT

8425

Port to listen on

--host

HOST

0.0.0.0

Bind address. Use 127.0.0.1 behind a reverse proxy or SSH tunnel

--data

AGENTINBOX_DATA

~/.agentinbox

Data directory

AGENTINBOX_MASTER_KEY

Base64 32-byte key for encrypting secrets, instead of <data>/master.key

TRUST_PROXY

1 behind a reverse proxy: client IPs from X-Forwarded-For, https from X-Forwarded-Proto

TRUST_PROXY_HOPS

1

Number of proxies in front of agentinbox (which X-Forwarded-For entry, from the right, is the client)

TRUST_PROXY_FROM

loopback

Other proxy addresses whose forwarding headers are believed: comma-separated IPs or CIDRs (IPv4 or IPv6)

AGENTINBOX_LOG_LEVEL

info

debug, info, warn or error

--url

AGENTINBOX_URL

http://localhost:8425

skill commands only: the server URL written into SKILL.md

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

agentinbox.db

SQLite database (plus -wal and -shm files): accounts, encrypted tokens, agents, grants, events, deliveries, approvals, audit log

master.key

Encrypts Google tokens, webhook secrets and the OAuth client secret (mode 0600)

setup-token

The one-time setup token, until the instance is claimed

agentinbox.lock

Marks the running instance (pid and port)

service.env

Environment the service was installed with, possibly including AGENTINBOX_MASTER_KEY (mode 0600; service install only)

agentinbox.log

Service output on macOS (launchd); on Linux use journalctl --user -u agentinbox (or without --user for a system service)

app/

The service's own install (service install only)

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 dev

pnpm 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 build

Stack: 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

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to manage Gmail emails, including sending, searching, and organizing with labels and attachments via OAuth2.
    32 npm
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to manage Gmail through natural language interactions, including sending, reading, searching emails, and managing labels with auto authentication support.
    17,624 npm
    1
    MIT