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

CLI Reference

Every aimx subcommand and its flags. aimx <command> --help is authoritative; this page summarises.

Global flags

Accepted on every subcommand.

FlagEnv varDescription
--data-dir <path>AIMX_DATA_DIROverride the mailbox data directory (default /var/lib/aimx). The flag wins when both are set.

For the full set of environment variables (AIMX_DATA_DIR, AIMX_CONFIG_DIR, AIMX_TEST_MAIL_DROP, NO_COLOR), see Configuration: Environment variables.

Daemon and setup

aimx serve

Start the embedded SMTP listener daemon. Managed by systemd / OpenRC in normal operation.

FlagDefaultDescription
--bind <addr>0.0.0.0:25Bind address for the SMTP listener.
--tls-cert <path>(from setup)PEM file for the STARTTLS certificate.
--tls-key <path>(from setup)PEM file for the STARTTLS private key.

See Setup for service installation and Configuration for config file details.

aimx setup [domain]

Interactive setup wizard. Requires root. Generates STARTTLS cert and DKIM keys, writes /etc/aimx/config.toml, installs a systemd (or OpenRC) unit for aimx serve, and drives DNS verification. Re-entrant: running it on an existing install skips install and jumps to DNS verification.

FlagDescription
<domain> (positional)Domain to configure (e.g. agent.yourdomain.com). Prompted if omitted.
--verify-host <url>Override the verifier service host for this invocation.

See Setup for the full walkthrough.

aimx uninstall

Stop the daemon, remove the init-system service file, and delete the installed aimx binary itself so a subsequent install.sh run starts from a clean slate. Leaves /etc/aimx/ and /var/lib/aimx/ intact — wipe them manually with sudo rm -rf /etc/aimx /var/lib/aimx if you want a full purge.

FlagDescription
-y, --yesSkip the confirmation prompt.

aimx portcheck

Check port 25 connectivity (outbound EHLO + inbound EHLO probe). Requires root.

FlagDescription
--verify-host <url>Override the verifier service host for this invocation.

See Setup: End-to-end verification.

Diagnostics

aimx doctor

Print server health: config path, per-mailbox totals and unread counts, ownership status, DKIM key presence, SMTP service state, DNS record verification, and a pointer to aimx logs. Exits non-zero when any mailbox has an unresolvable owner so monitoring can detect orphans.

The Service section also prints Client version: (the CLI binary you just invoked) and Server version: (probed from the running daemon). When they differ, the on-disk binary is newer than the daemon — restart the service so it picks up the new build. See Troubleshooting: Version drift.

No flags.

aimx logs

Tail or follow the aimx serve service log. Wraps journalctl -u aimx on systemd and /var/log/aimx/*.log / /var/log/messages on OpenRC.

FlagDefaultDescription
-n, --lines <N>50Number of trailing lines to show.
-f, --followoffStream new lines as they arrive (like journalctl -f).

Mail operations

aimx send

Compose a message and submit it to aimx serve via /run/aimx/aimx.sock. Refuses root. The daemon handles Markdown rendering, DKIM signing, and MX delivery.

--body is interpreted as Markdown (CommonMark + GFM extensions: tables, strikethrough, autolinks, task lists, footnotes). The daemon renders it to HTML with an inlined stylesheet and ships a multipart/alternative message — the recipient sees rich text on Gmail / Outlook / Apple Mail and the Markdown source on text-only clients. Two escape hatches are available:

  • --text-only — ship the body verbatim as text/plain. No rendering. Use for OTPs, transactional one-liners, and existing scripts that must not change shape.
  • --html-body <html> — supply a custom HTML template for the text/html part. AIMX uses your HTML verbatim and uses --body as the text/plain fallback. Mutually exclusive with --text-only.

For an in-depth tour of the rendering pipeline and the inlined stylesheet, see Markdown Email.

The caller’s euid must own the mailbox resolved from the From: local part; sends from another owner’s mailbox are rejected with not authorized: <local_part>@<domain>. The catchall (*@domain) is inbound-only and is never accepted as an outbound sender. See Security: Per-action authorization.

FlagDescription
--from <addr>Sender address. Must resolve to an explicitly configured (non-wildcard) mailbox owned by the caller.
--to <addr>Recipient address.
--subject <text>Subject line.
--body <text>Body content. Interpreted as Markdown by default. With --text-only, shipped verbatim as text/plain. With --html-body, used as the text/plain fallback.
--text-onlyShip --body verbatim as text/plain. Skips Markdown rendering and the HTML alternative part. Mutually exclusive with --html-body.
--html-body <html>Custom HTML for the text/html part. Operator-supplied; bypasses the renderer. Use the shell pattern --html-body "$(cat template.html)" for templates that don’t fit on one command line. Mutually exclusive with --text-only.
--reply-to <msg-id>Sets the In-Reply-To header for threading.
--references <chain>Sets the full References header. Needed only for multi-step threads where In-Reply-To alone is insufficient.
--attachment <path>Attach a file. Repeatable. With Markdown / --html-body, attachments wrap the alternative part in a multipart/mixed.

Examples:

# Default: Markdown body → recipient sees rendered HTML inline.
aimx send --from alice@example.com --to bob@example.com \
  --subject "Daily briefing" \
  --body "# Daily briefing\n\n- item one\n- item two"

# Plain-text only (e.g. OTPs, scripts that must not change shape).
aimx send --from alice@example.com --to bob@example.com \
  --subject "Verification code" --body "Your code: 184293" --text-only

# Custom branded HTML layout — operator owns the rendering.
aimx send --from alice@example.com --to bob@example.com \
  --subject "Newsletter" --body "Plain-text fallback for text-only clients." \
  --html-body "$(cat newsletter.html)"

# Markdown body + attachment.
aimx send --from alice@example.com --to bob@example.com \
  --subject "Q4 report" --body "See attached PDF." --attachment ./report.pdf

If both --text-only and --html-body are supplied, clap rejects the invocation before any UDS round-trip. The same canonical error fires server-side on the MCP email_send / email_reply tools so operators see one consistent message regardless of the surface.

The sent record under sent/<mailbox>/ always stores the Markdown source (or, for --text-only, the literal text — or for --html-body, the --body text part). The recipient’s HTML view is recoverable by re-running the same renderer; AIMX does not duplicate the rendered HTML alongside the source. See Markdown Email: Sent storage.

See Mailboxes: Sending email.

aimx ingest <rcpt>

Read a raw .eml message from stdin, parse it, and write the Markdown frontmatter file to the mailbox that routes for <rcpt>. Called in-process by aimx serve; available as a CLI for manual ingestion and testing.

aimx ingest catchall@agent.yourdomain.com < message.eml

Mailbox management

Alias: aimx mailbox works identically to aimx mailboxes.

aimx mailboxes create <name>

Register <name>@<domain> and create inbox/<name>/ and sent/<name>/ chowned <owner>:<owner> 0700. Owner-gated, not root-gated: non-root callers create mailboxes owned by their own uid, root may pass --owner <user> to create one owned by another uid. The reserved literals catchall and aimx-catchall are rejected.

When aimx serve is running, the change hot-reloads with no restart. When the daemon is stopped, root falls back to a direct config.toml edit; non-root exits with code 2. See Troubleshooting: daemon must be running.

FlagDescription
--owner <user>Linux user that should own the mailbox’s storage and run hooks. Honored only when run as root. Non-root callers passing --owner <other> get a soft warning to stderr (--owner ignored for non-root callers; mailbox will be owned by <caller>) and the daemon synthesizes the correct owner from SO_PEERCRED. Under root, the CLI prompts when omitted (default <name> if such a user exists). The user must resolve via getpwnam(3) on this host.

aimx mailboxes list

List mailboxes you own. Prints addresses, total count, and unread count. Non-root callers see only mailboxes whose owner uid matches their euid; the catchall is filtered out unless the caller is root or aimx-catchall.

FlagDescription
--allRoot only. List every mailbox regardless of owner. Non-root callers passing --all get --all requires root.

aimx mailboxes show <name>

Print a mailbox’s address, owner, effective trust policy, trusted_senders, configured hooks grouped by event, and inbox / sent / unread counts. Non-root callers may only inspect mailboxes they own.

aimx mailboxes delete <name>

Delete a mailbox. Owner-gated: non-root callers may only delete mailboxes they own. Refuses non-empty mailboxes with ERR NONEMPTY unless --force is passed. catchall cannot be deleted with or without --force.

FlagDescription
-y, --yesSkip the confirmation prompt.
--forceRecursively wipe inbox/<name>/ and sent/<name>/ before deleting. Daemon-side wipe runs under per-mailbox lock + CONFIG_WRITE_LOCK, so the wipe and the config rewrite are atomic together. Prompts before wiping unless paired with --yes. Refuses catchall.

See Mailboxes: Managing mailboxes.

Hook management

Alias: aimx hook works identically to aimx hooks. Authorization: caller must own the target mailbox, or be root. When aimx serve is running, hook CRUD hot-swaps into the live config with no restart. See Security: Per-action authorization.

aimx hooks list

List hooks. Non-root callers see only hooks on mailboxes they own. Prints a table of NAME, MAILBOX, EVENT, CMD. Anonymous hooks (those without an explicit name =) appear under their derived 12-char hex name.

FlagDescription
--mailbox <name>Filter to one mailbox you own.
--allRoot only. List hooks on every mailbox.

aimx hooks create

Create a hook. --cmd takes the argv as a JSON array string; cmd[0] must be an absolute path.

FlagDescription
--mailbox <name>Owning mailbox. Must already exist and be owned by the caller (or caller is root).
--event <event>on_receive or after_send.
--cmd <json-array>Argv exec’d directly when the hook fires. Required. JSON array string with cmd[0] as an absolute path. No shell wrapping — wrap in ["/bin/sh", "-c", "..."] explicitly when you need shell expansion.
--timeout-secs <N>Hard subprocess timeout. Default 60, range [1, 600]. SIGTERM at the limit, SIGKILL 5s later.
--fire-on-untrustedFire even when trusted != "true". Only valid on --event on_receive. Rejected on --event after_send.
--name <name>Optional. Matches ^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$. Must be globally unique across all mailboxes. When omitted, a derived 12-char hex name is used.

The raw .md (frontmatter + body) is always piped to the hook’s stdin and the same path is also 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.

Example (as the mailbox owner — no sudo needed when the daemon is running):

aimx hooks create \
  --mailbox accounts \
  --event on_receive \
  --cmd '["/usr/local/bin/claude", "-p", "Read the piped email and act on it.", "--dangerously-skip-permissions"]' \
  --name accounts_claude

See Hook Recipes for verified per-agent invocations.

aimx hooks delete <name>

Delete a hook by name. Works for both explicit and derived names (as shown in aimx hooks list). Authorization is the same as create: caller must own the hook’s mailbox, or be root.

FlagDescription
-y, --yesSkip the confirmation prompt.

See Hooks & Trust.

Agent integration

aimx mcp

Start the MCP server in stdio mode. Launched on-demand by MCP clients, not a background service.

No flags. See MCP Server.

aimx agents setup [agent]

Install the AIMX skill for a supported agent into the current user’s config directory and (for Claude Code and Codex CLI) auto-register the AIMX MCP server via claude mcp add / codex mcp add. Refuses to run as root. Run with no arguments to launch the interactive checkbox TUI; pass --list (or call aimx agents list) to print the supported-agent registry and exit without installing.

The skill bundle teaches the agent how to call AIMX’s MCP tools and includes a “Wiring yourself up as a mailbox hook” section with the verified cmd argv to use with aimx hooks create.

When installing for claude-code, the installer also removes any pre-existing ~/.claude/plugins/aimx/ from the older plugin layout so the new skills install isn’t shadowed.

FlagDescription
<agent> (positional)Short name (e.g. claude-code, codex, opencode, gemini, goose, openclaw, hermes). Omit for the interactive TUI.
--listPrint the registry: agent name, destination path, activation hint. Same output as aimx agents list.
--no-interactiveSkip the TUI when no agent is named; print the same plain registry dump. Intended for scripting.
--dangerously-allow-rootBypass the root-refusal check and wire AIMX into /root’s home. Prefer sudo -u <user> aimx agents setup on any machine with a regular user.
--forceOverwrite existing destination files without prompting.
--printPrint the skill contents and activation hint to stdout instead of writing to disk or invoking any MCP CLI.

See Agent Integration for per-agent activation steps.

aimx agents list

Print the supported-agent registry as a plain table (agent name, destination path, activation hint).

aimx agents remove <agent>

Inverse of aimx agents setup. Removes the skill files under $HOME and prints an agent-specific cleanup hint pointing at any external command you still need to run (for example claude mcp remove aimx). Refuses to run as root.

FlagDescription
<agent> (positional)Short name; must match the agent previously passed to aimx agents setup.
--dangerously-allow-rootBypass the root-refusal check.

Utilities

aimx dkim-keygen

Generate a 2048-bit RSA DKIM keypair under /etc/aimx/dkim/ (private 0600, public 0644). Normally run automatically by aimx setup; use directly for key rotation.

FlagDefaultDescription
--selector <name>aimxDKIM selector name (controls the DNS record <selector>._domainkey.<domain>).
--forceoffOverwrite existing keys.

UDS protocol verbs

aimx serve exposes a small AIMX/1 request set on /run/aimx/aimx.sock (mode 0666). The CLI subcommands above and the aimx mcp tools all submit these verbs; the daemon resolves the caller’s uid via SO_PEERCRED and runs auth::authorize server-side. Operators do not normally speak the wire format directly.

VerbDirectionAuthorizationUsed by
SENDrequest + body (RFC 5322 message)caller uid must own the mailbox resolved from From:aimx send, email_send / email_reply MCP tools
MARK-READ / MARK-UNREADheader (mailbox + path)caller uid must own the mailboxemail_mark_read / email_mark_unread MCP tools
MAILBOX-CREATEheader + JSON bodycaller uid synthesized as owner from SO_PEERCRED (root may pass Owner: for cross-uid creates)aimx mailboxes create, mailbox_create MCP tool
MAILBOX-DELETEheader (mailbox)caller uid must own the mailboxaimx mailboxes delete (incl. --force), mailbox_delete MCP tool
MAILBOX-LISTrequest onlynone server-side; the response is filtered to caller-owned rows (root sees all)aimx mailboxes list, mailbox_list MCP tool, every other MCP tool’s resolution pre-flight
HOOK-CREATEheader + JSON bodycaller uid must own the hook’s mailboxaimx hooks create (UDS path), hook_create MCP tool
HOOK-DELETEheader (hook name)caller uid must own the hook’s mailbox; operator-origin hooks are CLI-onlyaimx hooks delete (UDS path), hook_delete MCP tool
HOOK-LISTrequest onlynone server-side; the response is filtered to hooks on caller-owned mailboxes (root sees all)hook_list MCP tool
VERSIONrequest onlynone — payload is daemon build metadata onlyaimx doctor’s Server version: line

Ready to try AIMX?

One command, one box, one inbox.

Get started