← Help

Connecting an AI agent

Who's this for? Anyone using an AI chat client that speaks MCP — Claude Desktop, Claude Code, Codex, Copilot, or your own. Connect once and your agent can read, search, summarise, and reply to mail in your inboxy inbox — and import old mail from elsewhere so it can search that too.

The short setup. Most chat clients only need one URL: https://mcp.inboxy.net. Paste it into your client's MCP config, restart, sign in once via OAuth. Skip to Connecting Claude Desktop for the click-through, or read on for the lay of the land.

What changes after you connect: your agent gains a set of tools grouped by prefix — mail_* for inbox, feed_* and article_* for RSS, address_* for your aliases, account_* for your plan, digest_* for your daily digest. Read tools are unlimited; the four send-mail tools are scoped so a leaked credential can't be turned into a spam cannon (full rules under What an agent can send).

What the agent can do

Tools are grouped by prefix; most chat clients render them under headings.

mail_* — your inbox

Tool Purpose
mail_list Recent mail, filterable by classification, unread, address, since
mail_count { total, unread } matching a filter — single round-trip
mail_get One message: metadata + attachment list
mail_get_body Parsed text body + headers
mail_show Render an email as Markdown with inline images (proxied)
mail_summarise One-line summary of one message, generated server-side and cached so later reads are free. mail_summarize is an identical alias for the American spelling
mail_forward Forward to your verified personal_email (recipient locked server-side, idempotent). Does not consume mail_send_quota
mail_reply Reply to the original sender — recipient + From locked server-side, 5/hour cap, spam-classified messages refused, 30-day age window, every reply audit-logged. Does not consume mail_send_quota
mail_mark_read / mail_mark_unread Single-message read state
mail_mark_all_read Bulk; optional address_id / classification / before filter
mail_keep / mail_unkeep Pin/unpin a message so the retention sweep won't delete it. Cap: 10 currently kept
mail_unsubscribe Surface the unsubscribe methods for a message — List-Unsubscribe header (mailto + URL), RFC 8058 one-click POST flag, and body links matching /unsubscribe|opt-out/. Read-only: returns the URL, never visits or POSTs it
mail_send Send a brand-new message from one of your @inboxy.net aliases to any recipient (external or @inboxy.net). Debits one unit of mail_send_quota per recipient — the same counter the web compose UI debits. Refusal quota_exhausted when the bucket is empty. Internal @inboxy.net → @inboxy.net is DB-only (no SMTP, no footer); external recipients go via the sender worker and get the inboxy attribution footer. Self-loops and reserved @inboxy.net local-parts are refused
mail_invite Invite a friend to sign up for inboxy. to is any external email. Capped at 10 per rolling 30 days per user — independent counter from mail_send_quota, refusal invite_cap_reached. Caller supplies personal_note ≤ 500 chars; subject + body template are operator-controlled. Sent from the mail. transactional subdomain. If the invitee signs up via the included one-time link, the inviter is recorded for a future reward feature. Refused if the address already belongs to an inboxy user (recipient_already_user)
mail_attach_inline Upload one attachment for a later mail_reply by passing its bytes inline as base64 — no separate HTTP round-trip. For files too large to inline, use mail_create_upload
mail_attach_url Upload one attachment for a later mail_reply by giving a URL that we fetch server-side. Accepts https://, http://, and data:. SSRF-blocked: loopback, link-local, RFC1918, and Cloudflare-internal hosts are refused
mail_create_upload Reserve a short-lived signed PUT URL for one large attachment; the PUT returns an id for mail_reply.attachment_ids
mail_get_attachment Attachment bytes (inline for small images) + signed download URL
mail_search Semantic search across your messages
mail_import Import one historic .eml (base64, ≤6 MB) onto one of your aliases so search and reads cover it. Archive write only: never forwarded, never digested, stored as read, retained from the import date. Mail you sent yourself is accepted and flagged sent_by_user (mail_reply refuses it). Dedup on Message-ID — re-sending is a no-op. 500 per calendar month on Early Adopter (5000 paid); bulk backfills are easier over REST POST /v1/mail/import (10 MB, raw bytes). See Importing old mail
mail_delete Permanently delete a message and its embedding. Refused for kept messages

outbox_* — sent replies

Tool Purpose
outbox_list List replies you have sent via mail_reply, newest first. Filter by status / kind / in_reply_to_message_id
outbox_get One sent reply — header block + full body_text and body_html
outbox_count { total } for a status / kind filter — single round-trip
outbox_delete Permanently delete one sent reply. Refused if the row is kept

Status reflects the send pipeline: queued (in-flight), sent (delivered to the recipient's MX), rejected (refused by the sender worker — see status_reason), suppressed (recipient on the hard-bounce list), failed (queue exhausted retries).

feed_* — RSS feeds

Tool Purpose
feed_list List subscriptions with status + per-feed { total, unread } counts
feed_add Subscribe to a feed URL
feed_remove Unsubscribe
feed_get Detail for one subscription
feed_refresh Force a re-poll of one feed (benefits every subscriber)
feed_summarise One-paragraph digest of one feed, generated server-side

article_* — feed articles

Tool Purpose
article_list Articles across your feeds, newest first
article_count { total, unread } across your subscriptions or one feed
article_get One article — link-rewritten HTML + plain text
article_mark_read / article_mark_unread Per-article state
article_mark_all_read Bulk; optional feed_id / before filter
article_summarise One-line summary of one article, generated server-side

address_* — your aliases

Tool Purpose
address_list Your @inboxy.net aliases with per-alias { total, unread } counts (include_retired opt-in)
address_mint Mint a new alias (adjective-noun or opaque scheme)
address_retire Stop accepting mail on an alias
address_record_attribution Record that an alias was given to a particular site, so you can see which alias you handed to whom. Upserts per (alias, site) — calling again just refreshes the timestamp

account_* — plan + quota

Tool Purpose
account_get One blob: user, subscription (name / since / renews_at), quota (used vs limit, including kept_messages, mail_send, mail_import, invites_remaining), rate-limits, retention

digest_* — daily digest

Tool Purpose
digest_get Your digest schedule: whether it's on, the local delivery time, its timezone, and the next send instant. null if no schedule exists
digest_set_enabled Pause or resume the daily digest. While paused nothing is sent

mark_* — cross-domain bulk

Tool Purpose
mark_all_read Zero both unread mail AND unread articles in one call. Optional before ISO-8601 scopes the cut-off. Returns { mail_marked, articles_marked }

What an agent CAN send

inboxy is a "give your AI an inbox" service, not "give your AI your Gmail". Every outbound surface is scoped server-side so a leaked credential can't be turned into a spam cannon. (mail_import is the one write that isn't a send: it only stores mail you hand it, and inboxy never reads your other mailboxes itself.)

Surface Recipient rule Quota Footer? Where it ends up
mail_reply Locked to the original sender of the message you're replying to. Own 5/hour cap; no mail_send_quota debit. Yes (external). The sender's MX, via the outbound queue.
mail_forward Locked to your verified personal_email. Idempotent; no mail_send_quota debit. Yes (external). Your verified inbox.
mail_send Any address, external or @inboxy.net. Refused if it resolves to your own personal_email / own alias / reserved local-part. Debits mail_send_quota (one per recipient). Shared with the web compose UI. Refusal quota_exhausted. Yes for external; no for @inboxy.net. Recipient's mailbox (DB-only for @inboxy.net).
mail_invite Any external address that isn't already an inboxy user (recipient_already_user). 10 per rolling 30 days, independent counter. Refusal invite_cap_reached. Yes (the friend should know who's inviting them). Recipient's mailbox, via the mail. transactional subdomain. Signup link is one-time and binds the inviter→invitee relationship in the invites table for a future reward.

The internal-vs-external rule: an @inboxy.net → @inboxy.net send (whether between two aliases on your account or across users) is delivered in-network — a messages row on the recipient with the raw MIME stored in our EU R2 bucket, fed into the same classifier pipeline that handles inbound MX mail. No SMTP egress, no Cloudflare Email Sending binding involved. Any other recipient is queued onto outbound-send and the sender worker emits SMTP via the apex inboxy.net domain — that's where the attribution footer lands.

Why a footer at all? When inboxy is acting as your agentic sender to a third party, that third party deserves to know what they're replying to. The footer is one line of plain text plus a small HTML block; the sender worker is the single place it's injected, so all four tools above behave identically.

Drop-in skill file

If your client supports user-installed skills (e.g. Claude Code's ~/.claude/skills/), grab the inboxy skill from https://inboxy.net/docs/skill (raw markdown) and drop it at ~/.claude/skills/inboxy/SKILL.md. The rendered version lives at https://inboxy.net/help/skill. The skill briefs the agent on every tool plus the workflow patterns we recommend.

Available prompts

If your chat client supports MCP prompts (e.g. Claude Desktop's / menu), inboxy publishes the following templated prompts. Selecting one seeds a complete playbook into the conversation:

Prompt What it does
triage_unread Walks your unread inbox, marks spam-ish stuff read, proposes drafts
find_receipts_this_month Extracts vendor / amount / date from this month's receipts
summarise_feed 24-hour digest of one feed (asks you for a feed_id)
draft_unsubscribe_list Finds dead newsletters, surfaces unsubscribe URLs
daily_brief One paragraph: urgent mail, headlines, account warnings
verify_login_codes Lists OTPs / login links from the last hour

REST callers can read the same prompt bodies via the MCP prompts/get endpoint or hard-code the equivalent system message in their own client.

Connecting Claude Desktop

The MCP server speaks OAuth — no API key needed for chat clients.

  1. Open Claude Desktop → Settings → Developer → Edit Config.

  2. Add:

    {
      "mcpServers": {
        "inboxy": {
          "url": "https://mcp.inboxy.net"
        }
      }
    }
    
  3. Restart Claude Desktop. On first use, it will redirect to inboxy.net to sign you in via OAuth.

Connecting Claude Code / other MCP clients

Same URL: https://mcp.inboxy.net. The client handles OAuth itself.

Two things to get right whatever the client:

  • The endpoint is the bare originhttps://mcp.inboxy.net, with no /mcp path. Lots of MCP examples in the wild end in /mcp; ours returns 404 {"error":"not_found"} if you copy that habit.
  • If your client can't do the browser sign-in (Codex, CI scripts, server-side automations), you don't have to drop down to REST. mcp.inboxy.net accepts a static API key as Authorization: Bearer ik_… on the same endpoint — mint one under Account → API keys and see Connecting Codex below. The REST API at https://api.inboxy.net is there if you'd rather not speak MCP at all; it exposes the same data.

Connecting OpenClaw (and other unattended agents)

OpenClaw — and similar third-party agent runners (Codex, Microsoft Scout, Hermes Agent, …) — should be wired against a dedicated alias so the credential, the inbound mailbox, and the outbound From: are all scoped to that one agent. If the key leaks, blast radius is one alias.

1. Mint an alias for the agent

  1. Account → Addresses → New address. Pick the adjective-noun scheme (memorable) or opaque (un-guessable).
  2. Label it for the agent ("openclaw", "codex", etc.) so the inbox view stays legible.

2. Mint an address-scoped API key

  1. Account → API keys → New key.
  2. Set Scope = manage and Address = <your-alias>@inboxy.net (the dropdown lists your active aliases).
  3. Copy the token — it's shown once. Store it in OpenClaw's secret manager (never in repo).

A key scoped to an address can only act on that one alias: mail_list / mail_search only see that alias's messages, and mail_send (or mail_reply / mail_forward) auto-pin from_local_part to the bound alias — a caller-supplied value is ignored. Unscoped or wrong-kind keys get a 403.

3. Wire the credential into OpenClaw

mcp.inboxy.net accepts either an OAuth grant (Claude Desktop, Claude Code) or an Authorization: Bearer ik_... API key. For unattended agents like OpenClaw, the bearer path is the right one — it skips the browser sign-in and carries your key's address scope through every tool call.

One-shot install (from your OpenClaw shell):

export INBOXY_KEY=ik_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
openclaw mcp add inboxy \
  --url https://mcp.inboxy.net \
  --header "Authorization=Bearer $INBOXY_KEY"

A couple of OpenClaw-specific quirks to know:

  • --header parses as Name=Value, not Name: Value. Using a colon produces "Authorization: Bearer …" is an invalid header name.
  • Set INBOXY_KEY in your shell or secret store first so the token never lands in OpenClaw's ~/.openclaw/openclaw.json (where --header values are persisted) or your shell history. Treat ~/.openclaw/openclaw.json like a credential file.

If your OpenClaw build prefers a JSON config block instead of the CLI, the equivalent is:

{
  "mcpServers": {
    "inboxy": {
      "url": "https://mcp.inboxy.net",
      "headers": { "Authorization": "Bearer ik_…" }
    }
  }
}

3b. Wire the credential into Hermes Agent

Hermes (Nous Research) reads its MCP server list from ~/.hermes/config.yaml. Add an inboxy entry under mcp_servers using the bearer-token form:

mcp_servers:
  inboxy:
    url: "https://mcp.inboxy.net"
    headers:
      Authorization: "Bearer ${INBOXY_KEY}"

Hermes resolves ${VAR} placeholders against ~/.hermes/.env, so drop the token there rather than committing it inline:

# ~/.hermes/.env
INBOXY_KEY=ik_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Hermes-specific quirks vs OpenClaw:

  • Headers use colon syntax (Authorization: "Bearer …"), not Name=Value. That's the opposite of OpenClaw's CLI quirk — if you copy-paste between the two, watch the separator.
  • The config is YAML, indentation matters; mis-indenting headers: puts the token on the wrong server entry.
  • Hermes also supports auth: oauth instead of static headers, which triggers the same OAuth 2.1 + DCR flow Claude Desktop uses. We recommend the bearer path for unattended setups because address-scoping carries through every tool call.

3c. Wire the credential into Codex (OpenAI)

Codex reads its MCP servers from ~/.codex/config.toml (%USERPROFILE%\.codex\config.toml on Windows). Two paths work; pick by whether anyone is at the keyboard.

OAuth — attended, nothing to store. Add the server, then sign in once:

[mcp_servers.inboxy]
url = "https://mcp.inboxy.net"
codex mcp login inboxy

That runs the same OAuth 2.1 + PKCE + dynamic-registration flow Claude Desktop uses, and the grant shows up under Account → MCP / OAuth clients.

Bearer key — unattended, and the only path that carries address scoping. Codex's bearer_token_env_var takes the name of an environment variable, not the key itself. Put the key in the environment, and name the variable in the config:

[mcp_servers.inboxy]
enabled = true
url = "https://mcp.inboxy.net"
bearer_token_env_var = "INBOXY_API_KEY"
# macOS / Linux — in your shell profile or secret manager
export INBOXY_API_KEY=ik_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Windows — a *user* variable, so it survives reboots
setx INBOXY_API_KEY "ik_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

codex mcp add writes the same TOML for you:

codex mcp add inboxy \
  --url https://mcp.inboxy.net \
  --bearer-token-env-var INBOXY_API_KEY

Codex-specific quirks, in the order people hit them:

  • bearer_token_env_var is a variable name, never the key. Pasting ik_… there makes Codex look for an environment variable literally called ik_…; it finds nothing, and the connection ends up unauthenticated. The 401 you get back reads like inboxy rejected your key, when nothing ever reached us. Codex adds the Bearer prefix itself, so the variable holds the bare ik_… value.
  • Restart Codex fully after setting the variable. A running Codex — and the VS Code extension host — keeps the environment it started with. On Windows a setx / System Properties variable only reaches processes launched afterwards. Quit the app; opening a new chat is not enough.
  • No /mcp suffix. https://mcp.inboxy.net/mcp is a 404. Use the bare origin.
  • The key must be a live inboxy key. We recognise the ik_ prefix; anything else falls through to the OAuth path and comes back as 401 invalid_token rather than "bad key". A revoked key gives the same 401 — mint a fresh one rather than debugging the config.
  • If you'd rather use raw headers, Codex also has http_headers (literal values) and env_http_headers (values pulled from named environment variables — that variable must then hold the whole Bearer ik_… string, prefix included). bearer_token_env_var is the simpler of the three.
  • Back up config.toml before editing. Codex rewrites the file when you use codex mcp add/remove, and a malformed TOML table takes out every other server in it, not just inboxy.

The same alias-plus-scoped-key pattern from steps 1 and 2 applies: give Codex its own alias and a manage key bound to it, and a leak costs you one alias. If Codex only needs to read, mint the key with manage unticked — the send tools then refuse with a 403.

Credential syntax by client

Three clients, three separators — this is the most common copy-paste failure:

Client Config file Credential form
OpenClaw ~/.openclaw/openclaw.json --header "Authorization=Bearer $INBOXY_KEY"Name=Value, no colon
Hermes ~/.hermes/config.yaml Authorization: "Bearer ${INBOXY_KEY}"colon, ${VAR} read from ~/.hermes/.env
Codex ~/.codex/config.toml bearer_token_env_var = "INBOXY_API_KEY" — a variable name only: no colon, no Bearer, no key

4. Verify

From OpenClaw, ask the agent to call account_get — should return your plan + quota (including mail_send_remaining). Then address_list should return only the one alias (the key is scoped — if you see other addresses, the scope didn't take). Finally test outbound:

mail_send to=<your-personal-email> subject="hello" text="ping"

You'll see a row in Account → Outbox with source = mail_send and your mail_send_remaining decremented by 1.

Caps and limits to remember

  • mail_send_quota is per-user, not per-alias — the same counter the web compose UI debits. A leaked address-scoped key can still burn through your bucket, but only from one alias and only against the same per-user cap.
  • Reserved local-parts (abuse, security, support, privacy, dpo, dmarc-rua) are refused as recipients.
  • Self-loops are refused — the agent can't mail your own personal_email or any of your own aliases via mail_send. Use mail_forward (which is itself locked to your personal_email) if you want a copy of an inbox item delivered upstream.
  • Invites have their own counter: 10 per rolling 30 days, independent of mail_send_quota. Use mail_invite rather than mail_send when introducing a friend to inboxy.
  • Imports have their own counter too: 500 per calendar month (5000 paid), shown as quota.mail_import in account_get. An address-scoped key can only import onto its bound alias. Nothing is sent, so mail_send_quota is untouched.

Why attachment URLs don't need an API key

mail_get_attachment returns either inline bytes (small images) or a short-lived signed URL pointing at api.inboxy.net/v1/attachments/{token}/download. That URL does not require a separate Authorization header — the embedded HMAC token is the auth, validated locally by the api worker. The api bearer-auth middleware explicitly bypasses this path for that reason.

So an MCP client that has OAuth-authorised against mcp.inboxy.net can follow the returned URL with a plain fetch — no extra credentials, no separate API key.

The URL is single-attachment-scoped and expires (TTL 1 hour). If a token leaks, only that one attachment is exposed, and only until it expires.

Browser extensions

For Chrome / Firefox / Edge extensions that want to read inboxy mail without going through an agent, use the browser extension — it does device-bound (DPoP) auth, which doesn't need a long-lived bearer key.

Revoking access

  • Revoke an OAuth grant from Account → MCP / OAuth clients.
  • Revoke an API key from Account → API keys.

Revocation is immediate; the next request returns 401.


Still need help? support@inboxy.net

Add Inboxy to your Home Screen

  1. Tap the Share button in Safari’s toolbar.
  2. Scroll and tap Add to Home Screen.
  3. Tap Add in the top-right corner.