Skip to main content
Glama
Remedy92

mailmux

by Remedy92

Boxaide

Free, self-hosted multi-mailbox agentic inbox.
Connect any IMAP/SMTP mail. One unified inbox in the browser. One MCP surface for every agent.

On top of the inbox: a CRM derived from your own mail, scheduled agent automations, and outreach that no agent can send on its own. See Agent work platform.

No paid SaaS required for core receive + send. MIT licensed.

Formerly Sley, then Mailmux. The repo is Remedy92/boxaide.

Quick start

cd Projects/boxaide   # or your clone path
npm install
./scripts/start.sh --fixture
# equivalent: npm run dev -- --fixture

Open http://127.0.0.1:8787 — on localhost the UI auto-loads the bearer token.

Fixture mode seeds two demo mailboxes (personal, work) so you can try the UI and MCP without real credentials.

Real mail

npm run dev

In the UI: Connect mailbox → pick a preset (Gmail / Fastmail / Outlook / iCloud) or enter IMAP/SMTP hosts → use an app password where required.

Archiving

e in the list, or the archive button in the reader, moves the message to the account's Archive mailbox: the one your server advertises as \Archive, or Gmail's All Mail, which is what archiving means there. Nothing is deleted, and the toast offers an Undo that moves the message straight back to the folder it came from. A server that cannot name the message's new id after a move (no UIDPLUS) shows no Undo at all, rather than one that would fail.

An agent Boxaide launched can archive too, and every message it files is written down: the Agent view shows Claude archived 37 messages with one button that moves the run back. Any other move it wants to make, Trash included, waits for you to approve it first.

A mailbox whose server has no Archive folder says so instead of guessing: create one named Archive in your mail provider and it is picked up on the next archive. Archiving is one message per request: there is no bulk archive yet.

Deleting, and moving by hand

Right-click a message in the list for Archive, Delete and Start conversation about this email. # deletes the selected message, the way e archives it.

Delete is a move into the account's Trash mailbox, never an IMAP expunge: the message is where your own mail client expects it, the toast offers the same Undo, and emptying the trash stays something you do in your mail provider. A mailbox whose server has no Trash folder says so instead of guessing, exactly as it does for Archive.

To file a message anywhere else, use Move to folder in the command palette (⌘K) or in the reader's menu. Both list the folders of the message's own mailbox, so they work in the unified inbox too.

Production-ish start

npm run build
npm start
# or: node dist/cli.js serve

Related MCP server: mail-mcp

Using the hosted interface

Boxaide has one web interface: the Next.js app in apps/web, built as a static export. You can serve it from your own process or host it elsewhere. Either way it talks to the Boxaide server on your machine, and it never sends your mail or your token anywhere else.

The public site is https://boxaide.tech.

npm run build
npm start

npm run build compiles the server, builds apps/web, and copies the export to web-next/. Open the URL it prints, normally http://127.0.0.1:8787. Same origin as the API, so nothing else is needed.

To run it on its own during development:

cd apps/web && npm install && npm run dev     # http://localhost:3000
cd apps/web && npm run build && npm run serve # the real static export

Hosted (deployed somewhere else)

The page runs entirely in your browser and fetches mail directly from your machine.

Four routes exist in the export. Locally / is the inbox. On a deployment the difference is who has a server:

Route

What it is

/

The inbox. On a deployment apps/web/vercel.json redirects it to /install.

/install

The download page. A Mac dmg when one exists; Windows and Linux have no installer yet, so those buttons open the release page. Clone-and-run is a line at the bottom.

/app

The inbox again, at an address the redirect does not touch. Use this wherever this section says "the deployed page".

/tray/

The menu-bar popover the Mac app loads. Not a visitor page.

1. Deploy apps/web. On Vercel and equivalents, set the project's Root Directory to apps/web and leave the build and output commands on auto. That setting is what keeps the CLI's better-sqlite3 out of the front-end install; it lives in the dashboard and cannot be expressed in a file. Do not add a workspaces key to the root package.json.

No environment variable is required. One optional, non-secret variable exists:

Name

Value

Purpose

NEXT_PUBLIC_DEFAULT_API_BASE

http://127.0.0.1:8787

The pre-filled Server URL on a browser with nothing in localStorage. Public by definition — it is inlined into the bundle at build time. Omit it and the same default comes from apps/web/src/lib/constants.ts.

2. Allow the origin. On your machine, not on the host:

BOXAIDE_ALLOWED_ORIGINS=https://boxaide.tech boxaide serve

See the rules and the cost of doing this under Browser origins below.

3. Copy the token. boxaide serve prints it on first run; it is also in bearer.token inside your data directory, ~/.boxaide. An install made under the retired names is moved onto that one automatically the first time this version starts: ~/.sley becomes ~/.boxaide, ~/.sley-agents becomes ~/.boxaide-agents, and sley.db becomes boxaide.db. Nothing is overwritten — if ~/.boxaide already exists, the older folder is left exactly where it is.

4. Point the page at your server. Open the deployed page at /app, click Set up Boxaide, and enter the Server URL and the token. Both are stored in your browser's localStorage (boxaide.*) and are sent only to the server URL you entered. A first run still reads leftover sley.*, mailmux.* and mailmux_token keys once.

5. Allow local network access (Chrome, Edge, Brave). Since Chromium 142 the browser asks permission before a website may reach 127.0.0.1. Allow it when prompted; if you dismissed the prompt, re-enable it under Site settings → Apps on device.

Safari, and the mixed-content limit

A page served over https cannot reach an http address. For 127.0.0.1 and localhost Chromium and Firefox make an exception; WebKit does not, and there is no workaround (WebKit bug 171934, still open). This is also why BOXAIDE_ALLOWED_ORIGINS drops http:// entries: the configuration that would avoid the block is the one that makes the allowlist spoofable.

If your server is not on loopback, put it behind https or reach it over a tunnel. Otherwise use the local build — run boxaide serve and open http://127.0.0.1:8787 directly. It is the same interface.

What the host can see

Nothing. The deployed page has no server-side code: no API routes, no server actions, no proxy, no middleware. Your token, your mail credentials and every message body travel only between your browser and your own machine.

Desktop app

A window instead of a terminal, for people who do not want either. apps/desktop is an Electron shell: it starts the same server inside its own process, binds 127.0.0.1, and uses the same ~/.boxaide data directory, master key and bearer token. An account connected in the desktop app is the same account your agents reach over MCP.

On macOS the app also lives in the menu bar. Click the mark for a popover — recent mail, whether an agent is listening, one button into the app; the popover is the /tray/ route of the same web export. Right-click for a menu: open Boxaide, install the Claude connector (opens the bundled .mcpb in Claude Desktop), Start at login (packaged app only — it registers a macOS login item), quit. The menu bar icon stays as long as the app runs, including with the window closed.

npm run desktop

That compiles the server, installs Electron deps only when the lockfile changed, downloads the Electron binary once, copies dist/ and web-next/ into the app if they changed, and opens the window. A second run skips the install and the download. The web UI rebuilds only when apps/web/src no longer matches the last web:sync stamp.

npm run desktop:dist          # same prepare, then signed mac dmg in apps/desktop/release/

On mac, signing uses a Developer ID certificate pinned by hash in apps/desktop/scripts/sign-mac.sh (electron-builder's by-name signing is ambiguous when the keychain holds two same-named certificates). Notarization is a separate, credential-holding step; the commands are at the top of that script. The port follows BOXAIDE_PORT (default 8787); if something already holds it — boxaide serve in a terminal, or a second copy of the app — the window does not open and the app says so.

The install button serves GitHub releases/latest. CI does not publish a release. After a merge to master, from the main checkout (not a worktree):

./scripts/ship_status.sh   # is origin/master what a visitor downloads?
./scripts/ship.sh          # bump, pack, sign, publish latest
./scripts/install-hooks.sh # once: remind on pull/checkout of master

ship.sh is the only publisher. A git hook only prints the status; it never packs. Pass --dry-run to see the plan.

ship.sh defaults APPLE_KEYCHAIN_PROFILE to mailmux-notary and refuses to publish unless that profile works and the dmg carries a notarization ticket. A release is three files: boxaide-mac.dmg, boxaide-mac.zip, and latest-mac.yml. Mint the profile once. The keychain profile name is historical:

xcrun notarytool store-credentials mailmux-notary --apple-id <apple-id> --team-id 22DPQ7YCAS

Agent MCP (any client)

Claude Desktop — one click

With Boxaide running, open Connect agent in the UI and press Download for Claude Desktop, or fetch http://127.0.0.1:8787/boxaide.mcpb directly. Double-click the file; Claude Desktop installs it. Nothing to configure: the connector is a tiny stdio→HTTP proxy (apps/mcpb) that finds your local server and reads the token from ~/.boxaide/bearer.token itself. It is built into web-next/boxaide.mcpb by npm run build (npm run mcpb:build on its own).

HTTP MCP (Cursor / remote-capable clients)

{
  "mcpServers": {
    "boxaide": {
      "url": "http://127.0.0.1:8787/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN"
      }
    }
  }
}

Token lives in ~/.boxaide/bearer.token (or BOXAIDE_TOKEN).

stdio MCP (Claude Code / manual Claude Desktop)

npm run mcp
# or: npx tsx src/cli.ts mcp
{
  "mcpServers": {
    "boxaide": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/boxaide/src/cli.ts", "mcp"],
      "env": {
        "BOXAIDE_DATA_DIR": "/Users/you/.boxaide"
      }
    }
  }
}

Agents that speak TOML use [mcp_servers.boxaide]. Tool calls show up as mcp__boxaide__*.

Tools

Tool

Purpose

accounts_list

Connected aliases

messages_list

Inbox list (account: alias or all)

messages_search

Free-text search

message_get

Full body

message_mark_read

Set or clear the read flag

message_archive

Move one message to the account's Archive mailbox

message_move

Move one message to any folder (also the undo of an archive). A launched agent has to have it approved, because Trash is a folder

folders_list

Folders on one account

draft_create / draft_update / drafts_list / draft_delete

Drafts in the mailbox

message_send

Send now, or — for an agent Boxaide launched — ask you first. Not outreach approval.

chat_await_message

Wait for the user's next message in the Boxaide window

chat_say

Answer them there

chat_activity

Post a one-line "here is what I am doing"

chat_history

Re-read the conversation

The platform modules add their own tools. Full list in Agent work platform.

Group

Tools

CRM

crm_sync, crm_contacts_search, crm_contact_get, crm_contact_upsert, crm_contact_delete, crm_note_add, crm_org_upsert, crm_orgs_list, crm_interactions_list, crm_pipeline_get, crm_deal_upsert, crm_deal_move, crm_deal_delete

Automations

automation_create, automation_update, automation_delete, automations_list, automation_run_now, automation_runs_list

Outreach

outbox_queue_draft, outbox_list, suppression_add, suppression_list

Enrichment

enrich_find_email, enrich_verify_email, enrich_address_pattern, crm_contacts_import. enrich_address_pattern is free and needs no key: it works out how a company writes its addresses from the ones you already hold, and its candidate is an unverified guess until enrich_verify_email says otherwise.

Research

web_search, web_fetch

Prospecting

prospect_find_companies, prospect_find_people. Find companies and the people who work at them, by title, seniority, location, headcount and keyword. Needs an Apollo key. Falls back to a Hunter key, which finds companies and the people at a domain you name but cannot search a title across every company, and then to a plain web search, which finds companies only and returns search hits rather than records.

There is no tool that approves, rejects or sends an outbox row. That is a human action in the web UI.

Accounts are connected once in the web UI (or API). Agents reuse the same store — no per-agent OAuth.

Talking to your agent inside Boxaide

The Agent view is the app's first screen. Boxaide does not host a model. The agent is a local CLI you already have — Claude Code, Grok, Codex, Cursor, Claude Desktop — talking MCP. The four chat_* tools hold the conversation in the Boxaide window instead of in that client's terminal.

Start / Stop on the rail run or kill the installed CLI (src/agent/launcher.ts). For Claude Code, Boxaide holds the loop itself and the CLI is only asked to answer one message at a time — so the conversation cannot end because a model decided it was finished. The others are handed the kickoff prompt below and run the loop themselves. Either way you can still paste that loop into a client you launched yourself:

You are my Boxaide inbox agent. Use the Boxaide MCP tools.

Loop: call chat_await_message, do the work, post the answer with chat_say, then
call chat_await_message again. Keep going until I tell you to stop.

Everything I read appears in the Boxaide window, so every answer must go through
chat_say — do not answer here. A chat_await_message that returns no message is
normal; call it again. Use chat_activity for anything slow. Draft rather than
send unless I ask you to send.

Anything you type before an agent is listening is queued and delivered when one arrives.

Notes on what the UI claims. "Listening" means an agent is parked in an open chat_await_message — a request that is open right now, not an inference. It never says "connected", because a stateless POST /mcp cannot tell a configured client from one that was never started. Each message goes to exactly one agent, so do not point two at the same server. The conversation is stored in ~/.boxaide/boxaide.db, encrypted with the same master key as the account passwords, because an agent summarising an inbox puts mail content in those rows.

Agent work platform — CRM, automations, outreach

Three modules ship with the inbox. They are free, MIT, and run only on your machine. There is no sync, no tracking pixel, no click redirect, and no account anywhere else.

Module

What it is

Where you see it

CRM

Contacts, organisations, notes, an interaction timeline and a deal pipeline, all derived from mail you already have.

People and Pipeline views

Automations

Named prompts on a cron. Each run is a one-shot headless agent with the Boxaide tools and no user to talk to.

Automations view

Outreach

An approval queue for drafts an agent writes, plus the suppression list. Every draft waits for you.

Outreach view

CRM: derived, not entered

You do not type your contacts in. crm_sync walks INBOX and the Sent folder of each account and records who you actually mail with: contact per address, one interaction row per message, organisation per non-free email domain. It runs every 10 minutes while boxaide serve is up, and on demand from the tool or POST /api/crm/sync.

Free-provider domains (gmail.com, outlook.com, proton.me, …) never create an organisation. Automated senders (no-reply@, postmaster@, bounce addresses) are skipped. You can still add or correct anything by hand, or ask the agent to.

Automations are created by talking to the agent

The Automations view has no create form. This is deliberate: an automation is a prompt, and writing a good one is a conversation, not a text field.

Say what you want to whichever MCP client you already use:

Every weekday at 8, look at yesterday's unread mail, update the CRM, and queue a follow-up draft for anyone in the "warm" tag I have not mailed in two weeks.

The agent calls automation_create with a name, a 5-field cron and the prompt it just wrote for a future run of itself. The view then owns the automation: enable and disable it, see next and last run, run it now, read the log of any past run.

What a run may do:

Can

read mail, search, read and write CRM, save drafts, queue outreach into the outbox

Cannot

talk to you (no chat tools — there is no one at the window), send mail, create or cancel a meeting, approve anything

Can ask

a run may call message_send, meeting_create or meeting_cancel; nothing goes out, and the request is waiting for you in the Agent view in the morning

Limits

one run at a time, queued if another is going; 15-minute hard timeout, then killed

Run logs are stored encrypted, like everything else mail-derived.

Importing Claude Desktop scheduled tasks

Claude Desktop keeps each scheduled task as a folder under ~/.claude/scheduled-tasks/<name>/SKILL.md, with a name and description in front matter and the instructions in the body. Those are exactly the two things automation_create needs, minus a schedule.

Ask the agent to do the move:

Read ~/.claude/scheduled-tasks/*/SKILL.md and recreate each one as a Boxaide automation.

It reads the folder itself with its own file tools and calls automation_create per task: name from the front matter, prompt from the body. A SKILL.md does not carry a cron, so the agent asks you for the schedule of each one, or proposes one from the description. Nothing is imported silently, and nothing is deleted on the Claude Desktop side.

Why this is a conversation and not an importer: the two systems do not have the same permissions. A Claude Desktop task can talk to you and reach everything on your machine. A Boxaide automation cannot talk to anyone and works through the Boxaide tools. A task that assumed it could ask a question needs rewriting before it makes sense on a cron here, and the agent that reads it is the thing best placed to rewrite it.

No auto-send

No agent sends outreach. outbox_queue_draft lands as a pending row. The Outreach view shows each one — recipient, subject, body — with Approve, Edit or Reject. Approval is REST only, from the browser, by you. There is no MCP tool that approves, rejects or sends an outbox row.

That is the outreach path only. An external MCP client you wired up yourself can still call message_send and the mail leaves immediately — that client is you. Agents Boxaide launches (rail Start, and every automation run) get message_send, and calling it sends nothing: see Your agent asks, you answer below.

Edit does not rewrite the queued row. It opens the composer with that text; the queued copy is rejected after you send.

Step

Who

Write the draft

agent

Queue it into the outbox

agent

Read it, approve or reject it

you, in the browser

Put it on the wire

server, after approval

The rail badges the pending count, and the desktop app raises a notification and a dock badge when it rises. You are told about waiting drafts; you are never told after the fact about sent ones.

Sending is throttled server-side even after approval: at least 60 seconds between engine sends with jitter, and at most BOXAIDE_SEND_DAILY_CAP (default 50) per account per UTC day. Over the cap, an approved row simply goes out the next day.

Your agent asks, you answer

Sending mail, creating a meeting and cancelling one are the three things an agent does that another person sees straight away — and it decides to do them after reading mail that strangers wrote. So an agent Boxaide launched never does them. It asks.

The call is recorded exactly as the agent made it, and a card appears above the composer in the Agent view: who it goes to, the subject, and the whole message, not a preview. Send it carries it out. Don't drops it. Nothing leaves the machine until you click.

This is why a scheduled automation can ask too. There is nobody awake at 03:00 to answer a prompt, but the request keeps: the run ends, and the card is waiting in the window in the morning.

The agent is told its request is with you and told not to retry it. It cannot approve its own request, and there is no MCP tool that approves anything.

Suppression is a server rule, not a checkbox

suppression is a table of addresses that must not be mailed. The check lives inside MailService.sendMessage, so it applies to every path — outreach, a manual compose, an agent's message_send. A suppressed recipient fails the send with recipient suppressed: <email>.

Reason

How an address gets there

reply-stop

Someone replied "stop", "unsubscribe" or "opt out" to mail this app queued. Detected on the inbound message; the address is suppressed immediately.

manual

You added it in the Outreach view.

bounce

You or an agent recorded a hard bounce with suppression_add. A failed send does not add this on its own.

agent

An agent added it with suppression_add.

Only a human can override, and only through REST: POST /api/messages/send accepts overrideSuppression: true. The MCP message_send tool does not expose the flag, so no agent can override a suppression at all.

Every outreach step, including the first, ends with a plain-text opt-out line telling the recipient to reply with "stop". There are no open pixels and no click-tracking links — they conflict with the privacy posture and are out of scope on purpose.

Everything stays on your machine

Same store, same master key, same file as the rest of Boxaide: ~/.boxaide/boxaide.db.

Data

At rest

Note text, interaction subjects and snippets, outbox subjects and bodies, automation run logs

encrypted, AES-256-GCM, same master key as your mail passwords

Contact email and name, organisation name and domain, tags, deal titles, suppression addresses

plaintext — they are CRM identity, needed for UNIQUE and for search

Automation prompts

plaintext — you wrote them, they are not mail content

Mail, CRM, automations and outreach stay in that directory. There is no analytics SDK and no mail sync to a host. The process does poll GitHub for updates (GET /api/update, and electron-updater in the Mac app). The marketing UI at https://boxaide.tech is a static export and never sees your data. Back up ~/.boxaide and you have backed up the store.

Logs

~/.boxaide/logs/boxaide.log, beside the database, mode 0600. One JSON object per line:

{"t":"2026-08-24T09:14:02.113Z","level":"error","scope":"agent.launcher","msg":"chat exit","agent":"agy","pid":48122,"code":1,"reason":"exited","authRequired":false,"upMs":1840,"stderrTail":null}

What goes in it is what a stuck agent leaves behind: launches and exits with their exit code and the tail of the child's stderr, one line per driven turn with its duration and the CLI's own error string, and the desktop shell's failures, which a packaged app has no terminal to print to. What never goes in it is content: no message bodies, no mail, no tool results, and any credential shape found in a stderr tail is masked before the line is written.

It rotates at 5 MB and keeps three files (boxaide.log, .1, .2), so it costs at most 15 MB. Nothing reads it back, so deleting it is safe. An install with BOXAIDE_DATA_DIR set writes into that directory instead.

Install options

Method

Command

Dev

npm install && npm run dev

Fixture demo

npm run dev -- --fixture

Built

npm run build && npm start

Init data dir

npx tsx src/cli.ts init

Env

Each BOXAIDE_* name is preferred. Then SLEY_*, then MAILMUX_*.

Variable

Default

Meaning

BOXAIDE_DATA_DIR

~/.boxaide

SQLite + keys. A legacy ~/.sley or ~/.mailmux install is renamed onto this name on first start. Setting this variable points at a directory as-is and migrates nothing.

BOXAIDE_HOST

127.0.0.1

Bind address — see below

BOXAIDE_PORT

8787

Port

BOXAIDE_TOKEN

auto file

API/MCP bearer

BOXAIDE_MASTER_KEY

auto file

AES key for passwords — see below

BOXAIDE_FIXTURE

off

Demo provider

BOXAIDE_AGENT_ACCESS

workspace

full runs launched agents unconfined — they can read every file you can. Only set this if the sandbox is in your way.

BOXAIDE_ALLOWED_ORIGINS

empty

Extra browser origins allowed to call the API — see below

BOXAIDE_ATTACHMENT_DIRS

home directory

Comma-separated directories an attachment may be read from. Hidden files and directories are refused inside them either way

BOXAIDE_SEND_DAILY_CAP

50

Approved outreach sends per account per UTC day

BOXAIDE_GOOGLE_CLIENT_ID

empty

OAuth client for Google Calendar — see below

BOXAIDE_GOOGLE_CLIENT_SECRET

empty

Secret for that client

BOXAIDE_HUNTER_API_KEY

empty

Hunter key. Finds and verifies work email addresses, checks a recipient before an approved send, and answers the prospecting tools when there is no Apollo key

BOXAIDE_PROSPEO_API_KEY

empty

Prospeo key, tried after Hunter

BOXAIDE_PARALLEL_API_KEY

empty

Parallel key. Lets an agent search the web and read a page. Tried first: it is a seventh of Exa's price for the same search

BOXAIDE_EXA_API_KEY

empty

Exa key. A second index for the same job, used when Parallel has no key or when a tool call names Exa

BOXAIDE_APOLLO_API_KEY

empty

Apollo key. Lets an agent find companies and people it does not know yet

Those last five are also settable in Settings > Connectors, which stores them encrypted on the server and takes effect without a restart. A key saved there beats the environment variable, and the screen only ever shows the last four characters. Set no search key at all, in either place, and a launched agent keeps its own CLI's web search instead of Boxaide's.

Bind address (BOXAIDE_HOST)

The default binds to loopback, so only your own machine can reach the server. Change it and the server answers on the network, where the bearer token is the only thing between a stranger and your mail.

One behaviour changes on a non-loopback bind: /api/local-bootstrap, which exchanges the desktop app's one-time capability for the bearer token, answers 404 and hands out nothing. A normal browser never receives that capability, including on loopback. Paste the token in by hand instead; it is in ~/.boxaide/bearer.token.

Master key (BOXAIDE_MASTER_KEY)

This key encrypts your stored mail passwords. Leave it unset and Boxaide generates a random one in ~/.boxaide/master.key.

Set it to 64 hex characters — a full random 32-byte key:

openssl rand -hex 32

Any other value is treated as a passphrase and stretched with scrypt (N=2¹⁷, r=8 — 128 MB per attempt, about 0.2s once at startup). The salt is random per install and stored in ~/.boxaide/master.salt, so no precomputed table applies and the same passphrase on two machines produces two different keys. A passphrase still holds far less entropy than a random key, so prefer the hex form.

Back up master.salt with your data directory. Lose it and a passphrase no longer derives the key that encrypted your stored mail passwords.

Upgrading: passphrases used to be hashed once with SHA-256. The scrypt change means a passphrase set before this version derives a different key, and stored mail passwords no longer decrypt. Re-enter each account's password once, or keep the old key by setting BOXAIDE_MASTER_KEY to the hex of sha256(<your passphrase>).

Browser origins (BOXAIDE_ALLOWED_ORIGINS)

By default Boxaide accepts browser requests only from your own machine. A page on any other origin gets 403 {"error":"forbidden origin"}. Leave the variable unset and nothing changes.

Set it when you want a web interface hosted somewhere else — a deployment of apps/web, for example — to talk to your local server. The page still runs entirely in your browser and still fetches mail directly from your machine; the variable only tells your server which page origins it will answer. See Using the hosted interface for the full walkthrough.

BOXAIDE_ALLOWED_ORIGINS=https://boxaide.tech boxaide serve

Rules:

  • Comma-separated, exact origins. https://a.example.com,https://b.example.com.

  • Only https:// entries are kept. A plaintext origin is trivially spoofed on a hostile network, so http:// entries are dropped.

  • Path, query and case are stripped: https://A.App/x becomes https://a.app. A port must match exactly — https://a.app does not allow https://a.app:8443.

  • * is ignored on purpose. Boxaide holds your mail credentials; an any-origin allowlist would let any page you visit probe your server.

  • Loopback (127.0.0.1, localhost, ::1) always passes, so the self-hosted UI needs no configuration.

  • Requests with no Origin header (curl, MCP clients) are unaffected.

What enabling this costs you. The origin check is the last defence-in-depth layer in front of a service that holds decrypted IMAP passwords. Adding an origin means:

  • Anyone who can serve a page at that exact hostname can reach your server if they also have your bearer token. On shared hosting platforms that includes preview deployments and anyone with deploy access. Prefer a custom domain you control over a platform-assigned hostname.

  • It is the only remaining barrier against a DNS-rebinding page reaching your loopback service, so the list should stay as short as you can make it.

  • The token is still required on every request. Access-Control-Allow-Credentials is never sent — Boxaide authenticates by header, never by cookie — so no page can ride ambient credentials.

  • /api/local-bootstrap is not widened by this variable. It requires loopback plus the desktop shell's one-time capability. A browser page must have its token pasted in by a human.

Google Calendar client (BOXAIDE_GOOGLE_CLIENT_ID)

Connecting Google Calendar needs an OAuth client. Set both variables and Boxaide uses that one for every Google connection, so the user presses a single button. Leave them unset — the default, and what this repo ships — and the connect dialog asks for a client id and secret the user creates in the Google Cloud console themselves. Both paths store the same thing afterwards, and a client id sent with the connect request still wins over the configured pair.

The redirect URI Google must accept is http://127.0.0.1:8787/api/calendar/google/callback, with your BOXAIDE_HOST and BOXAIDE_PORT in it. Boxaide shows the exact string in the add-calendar dialog. A client shared across installs therefore has to be a Desktop app client, which wildcards loopback ports; a Web client only accepts the ports you registered.

No credentials are committed to this repository, and none are baked into a build.

Architecture

See docs/ARCHITECTURE.md.

One Node process:

  • / — web UI (the apps/web export, served from web-next/)

  • /api/* — REST (same mail core)

  • /mcp — JSON-RPC MCP

  • boxaide mcp — stdio MCP

IMAP via ImapFlow, SMTP via Nodemailer, secrets AES-256-GCM, state SQLite.

Tests

npm test

Tests call shipped MailService, crypto, HTTP app, and MCP handlers with an in-memory FixtureProvider — no live mail accounts required.

Security notes

  • Default bind is localhost.

  • Browser requests are loopback-only unless BOXAIDE_ALLOWED_ORIGINS names another origin. Default is closed.

  • Passwords encrypted at rest; master key in ~/.boxaide/master.key (mode 0600). A passphrase in BOXAIDE_MASTER_KEY is stretched with scrypt against ~/.boxaide/master.salt.

  • Prefer app passwords over primary account passwords.

  • Keep message_send behind agent confirmation.

  • Outreach cannot be sent by an agent. Approval is REST-only and human; no MCP tool approves, rejects or sends an outbox row.

  • Suppression is enforced inside MailService.sendMessage, so it covers every send path. Only REST can override it.

  • Mail-derived text in CRM, outreach and automation rows is encrypted with the same master key as your passwords.

License

MIT — free to use, modify, and redistribute.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that enables AI models to read, search, and send emails via IMAP and SMTP protocols. It supports various providers like Gmail and Outlook, allowing for tasks such as retrieving unread messages, searching by sender, and managing mailbox folders.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A generic IMAP and SMTP MCP server that enables AI agents to interact with email accounts for reading, searching, and sending messages. It provides high-level tools for managing email workflows like daily digests and folder organization across any standard email provider.
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    An open-source MCP server that provides AI agents with secure access to read, search, and manage emails via Microsoft 365 and Gmail. It features security-first defaults like recipient allowlists and markdown content conversion to facilitate safe agent interaction with mailboxes.
    5
    Apache 2.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    A self-hosted MCP server that gives AI agents full email superpowers.
    1
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Remedy92/boxaide'

If you have feedback or need assistance with the MCP directory API, please join our Discord server