Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MCP Server

AIMX includes a Model Context Protocol server that gives AI agents programmatic access to email. Agents can list, read, send, reply to, and manage email through standard MCP tool calls.

Overview

Running the MCP server

As this is a stdio MCP server, you do not need to “start a listening MCP server”. Your AI agents would spawn a server on demand via aimx mcp . Just to be clear, you aimx mcp is for your AI agents than for you to run.

The server reads from stdin and writes to stdout. To install it into a supported agent, see Agent Integration.

Per-user authorization

The MCP server inherits the uid of the user that launched the client. Every tool call routes through the daemon UDS, which authorizes against the caller’s uid via SO_PEERCRED: caller is root, or caller’s uid equals the target mailbox’s owner_uid. Tools acting on mailboxes the caller does not own return EACCES not authorized; hook_delete for an unowned hook collapses to Hook '<name>' not found so foreign mailbox names do not leak.

Root running the MCP server bypasses mailbox-ownership checks (and is logged at info level). See Security: Per-action authorization.

MCP tools

AIMX exposes 12 MCP tools organized into mailbox lifecycle, email operations, and hook management.

Mailbox tools

mailbox_list

List mailboxes you own.

Parameters: none

Returns: JSON array. One row per visible mailbox with these fields:

FieldTypeDescription
namestringMailbox name (the local part).
inbox_pathstringAbsolute path to the inbox directory (/var/lib/aimx/inbox/<name>).
sent_pathstringAbsolute path to the sent directory (/var/lib/aimx/sent/<name>).
totalnumberTotal emails in the inbox.
unreadnumberInbox emails with read = false.
sent_countnumberTotal emails in the sent folder.
registeredbooltrue for mailboxes in config.toml; false for stray on-disk dirs only.

The empty case returns []. Filtered to caller-owned mailboxes for non-root callers; root sees everything. The MCP process resolves the listing through the daemon over /run/aimx/aimx.sock, so it works without read access to root-owned config.toml.


mailbox_create

Provision a new mailbox owned by the calling agent’s uid.

ParameterTypeRequiredDescription
namestringyesMailbox name (the local part of the resulting address). Must match [a-z0-9._-]+, must not be the reserved literals catchall / aimx-catchall.

There is no owner parameter, by construction. The daemon synthesizes the owner from the MCP process’s uid via SO_PEERCRED — agents can only create mailboxes owned by themselves. To provision a mailbox owned by another uid (e.g. a service account), an operator must run sudo aimx mailboxes create <name> --owner <user> from the host CLI.

Returns: the new mailbox’s full address (<name>@<domain>) on success. Idempotent: re-running mailbox_create("foo") on a mailbox you already own returns the existing address with no side effects.

Errors: surfaces the daemon’s ErrCode + reason verbatim. Common cases:

  • Validation: reservedname was catchall or aimx-catchall.
  • Validation: ... — name matched a structural rule violation (empty, contains .., leading/trailing ., invalid character, etc.).
  • daemon must be running for non-root mailbox CRUD — daemon is offline; agents cannot fall back to a direct config edit.

mailbox_delete

Remove a mailbox the calling agent owns.

ParameterTypeRequiredDescription
namestringyesMailbox name to delete. Caller’s uid must equal the mailbox’s owner_uid.
forceboolnoDefault false. When true, the daemon wipes inbox/<name>/ and sent/<name>/ under per-mailbox lock + CONFIG_WRITE_LOCK before unregistering the mailbox.

Without force, the daemon refuses non-empty mailboxes with ERR NONEMPTY and the tool surfaces a hint pointing at the CLI’s interactive --force prompt. The catchall mailbox is refused with or without force.

Errors: surfaces the daemon’s ErrCode + reason verbatim. Common cases:

  • EACCES not authorized — caller’s uid does not own the target mailbox.
  • NONEMPTY: inbox=N sent=M — mailbox has files; pass force: true (or use the CLI’s interactive --force prompt) to wipe them.
  • daemon must be running for non-root mailbox CRUD — daemon is offline.
{"name": "mailbox_create", "arguments": {"name": "task-42"}}
{"name": "mailbox_delete", "arguments": {"name": "task-42", "force": true}}

Email tools

email_list

List a page of email metadata in a mailbox, sorted descending by filename (newest first). AIMX never scans on the agent’s behalf — agents page through the listing and filter client-side.

ParameterTypeRequiredDescription
mailboxstringyesMailbox name to list emails from. Must be owned by the caller.
folderstringno"inbox" (default) or "sent". Picks which side of the mailbox to list
limitu32noPage size; default 50, hard-capped at 200. Values above 200 silently clamp
offsetu32noNumber of newest rows to skip; default 0

Returns: A JSON array of metadata rows. Inbox rows carry { id, from, to, subject, date, read }. Sent rows carry { id, from, to, subject, date, delivery_status } — the read field is intentionally absent from sent rows, since agents do not mark sent mail. An empty mailbox returns the literal []. Returns EACCES not authorized if the caller does not own the target mailbox.


email_read

Read the full content of an email.

ParameterTypeRequiredDescription
mailboxstringyesMailbox name
idstringyesEmail ID, i.e. the filename stem (e.g. 2025-01-15-103000-meeting)
folderstringno"inbox" (default) or "sent"

Returns: Complete .md file content including frontmatter and body. Returns EACCES not authorized if the caller does not own the target mailbox.


email_send

Compose and send an email with DKIM signing.

ParameterTypeRequiredDescription
from_mailboxstringyesMailbox name to send from. Must be owned by the caller.
tostringyesRecipient email address
subjectstringyesEmail subject
bodystringyesEmail body. Interpreted as Markdown by default — rendered to HTML and shipped as multipart/alternative with the Markdown source as the text part.
text_onlyboolnoWhen true, ship the body verbatim as text/plain. No Markdown rendering, no HTML alternative. Mutually exclusive with html_body.
html_bodystringnoOperator-supplied HTML used verbatim as the text/html part; body becomes the text/plain fallback. Mutually exclusive with text_only.
attachmentsarray of stringsnoFile paths to attach
reply_tostringnoMessage-ID of the email being replied to. Sets the In-Reply-To header and (when references is omitted) builds the References chain automatically. Required to enable threading. Without reply_to, any references value is silently ignored and no threading headers are emitted
referencesstringnoFull References header chain (space-separated Message-IDs). Only applied when reply_to is also set. Supplied alone, it is silently ignored

The MCP server composes the RFC 5322 message and submits it to aimx serve over the local /run/aimx/aimx.sock UDS. aimx serve parses From: from the body, validates that the caller’s uid owns the resolved mailbox, DKIM-signs the message, and delivers it directly to the recipient’s MX server via SMTP. See Markdown Email for the rendering pipeline and the --text-only / --html-body semantics that mirror these MCP parameters.

For replies to a single sender, prefer email_reply. It handles threading headers and the Re: subject prefix automatically. Use email_send with reply_to / references only when you need to override the recipient list (e.g. reply-all) or build a custom threading chain.


email_reply

Reply to an email with correct threading.

ParameterTypeRequiredDescription
mailboxstringyesMailbox name containing the email to reply to. Must be owned by the caller.
idstringyesEmail ID to reply to (e.g. 2025-01-15-001)
bodystringyesReply body. Interpreted as Markdown by default — same semantics as email_send’s body.
text_onlyboolnoWhen true, ship the body verbatim as text/plain. Mutually exclusive with html_body.
html_bodystringnoOperator-supplied HTML used verbatim as the text/html part; body becomes the text/plain fallback. Mutually exclusive with text_only.

Automatically sets In-Reply-To and References headers from the original email for proper thread grouping in the recipient’s mail client.


email_mark_read

Mark an inbox email as read. Sent-mail mark has no agent use case and is not supported.

ParameterTypeRequiredDescription
mailboxstringyesMailbox name. Must be owned by the caller.
idstringyesEmail ID (filename stem, e.g. 2025-01-15-103000-meeting)

Updates read = true in the email’s frontmatter. The MCP server is non-root so it routes the write through aimx serve over the local UDS (/run/aimx/aimx.sock) rather than touching the root-owned mailbox file directly. If aimx serve is not running the tool returns an error hint to start the daemon.


email_mark_unread

Mark an inbox email as unread. Sent-mail mark has no agent use case and is not supported.

ParameterTypeRequiredDescription
mailboxstringyesMailbox name. Must be owned by the caller.
idstringyesEmail ID (filename stem, e.g. 2025-01-15-103000-meeting)

Updates read = false in the email’s frontmatter. Same daemon-mediated write path as email_mark_read. Requires a running aimx serve.


Hook tools

Three tools let agents self-configure hooks on mailboxes they own. See Hooks & Trust for the model and Hook Recipes for verified per-agent cmd argv.

hook_create

Create a new hook on a mailbox you own. The daemon validates the caller’s uid against the mailbox’s owner_uid via SO_PEERCRED and rejects with EACCES not authorized if the predicate fails.

ParameterTypeRequiredDescription
mailboxstringyesTarget mailbox name. Must be owned by the caller.
eventstringyes"on_receive" or "after_send"
cmdarray of stringsyesArgv exec’d directly when the hook fires. cmd[0] must be an absolute path.
namestringnoExplicit hook name. When omitted, a stable 12-hex-char name is derived from sha256(event + joined_argv + fire_on_untrusted).
timeout_secsintnoHard subprocess timeout in seconds. Default 60, range [1, 600].
fire_on_untrustedboolnoon_receive only: fire even when trusted != "true". Default false. Rejected on after_send.

The raw .md (frontmatter + body) is always piped to the hook’s stdin and the same path is exposed as $AIMX_FILEPATH. If your hook only needs the subject or sender, read $AIMX_SUBJECT / $AIMX_FROM and ignore stdin — the daemon writes the full email but does not require the child to consume it.

Returns: {effective_name} — the hook name the daemon wrote.

Example (Claude Code self-wiring):

{"name": "hook_create", "arguments": {
  "mailbox": "accounts",
  "event": "on_receive",
  "cmd": ["/usr/local/bin/claude", "-p", "Read the piped email and act on it via the aimx MCP server.", "--dangerously-skip-permissions"],
  "name": "accounts_claude"
}}

Error examples:

  • EACCES not authorized — caller’s uid does not own the target mailbox
  • mailbox-not-found: <name> — mailbox does not exist
  • hook has non-absolute cmd[0]cmd[0] must be an absolute path
  • fire_on_untrusted is on_receive only — flag set on an after_send hook
  • catchall does not support hooks — target was a catchall mailbox

hook_list

List hooks on mailboxes you own.

ParameterTypeRequiredDescription
mailboxstringnoFilter to one mailbox (must be owned by the caller); omit to list every owned mailbox

Returns: JSON array. Each entry has name, mailbox, event, cmd, timeout_secs, and fire_on_untrusted.

[
  {"name": "accounts_claude", "mailbox": "accounts", "event": "on_receive", "cmd": ["/usr/local/bin/claude", "-p", "...", "--dangerously-skip-permissions"], "timeout_secs": 60, "fire_on_untrusted": false}
]

hook_delete

Delete a hook by name. Caller must own the hook’s mailbox.

ParameterTypeRequiredDescription
namestringyesEffective hook name (explicit or derived)

Returns Hook '<name>' not found for hooks on mailboxes the caller does not own (the lookup is filtered before the existence check, so foreign mailbox names do not leak).


Frontmatter reference

Every email carries a TOML frontmatter block between +++ delimiters. See Mailboxes: Frontmatter fields for the full inbound schema and Mailboxes: Outbound frontmatter for the outbound additions.

Agent-facing documentation

Two reference documents help agents understand AIMX:

  • agents/common/aimx-primer.md — the canonical primer bundled into every agent plugin. Covers MCP tools, storage layout, frontmatter, trust model, workflows.
  • /var/lib/aimx/README.md — the runtime datadir guide written by aimx setup and refreshed on aimx serve startup. Covers on-disk layout, file naming, slug algorithm, bundle rules.

Example workflow

  1. Call email_list and filter rows where read == false.
  2. Call email_read with the mailbox and email ID, or read <inbox_path>/<id>.md from the filesystem.
  3. Process the content.
  4. Call email_reply with the response body.
  5. Call email_mark_read.

For automated processing without MCP, use hooks.

Ready to try AIMX?

One command, one box, one inbox.

Get started