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

FAQ

Common questions

Why does AIMX need port 25 open?

AIMX is your mail server. SMTP runs on port 25. It has been defined and set in RFC 821 since 1982. AIMX speaks SMTP directly to other mail servers, no third-party relays involved. Your mail stays truly private and secure.

Can I run AIMX on my home server?

Usually not. Home ISPs typically block port 25. To check if port 25 is open without installing AIMX, run curl -fsSL https://aimx.email/portcheck.sh | sh.

Can I switch AIMX to operate on another port?

No. SMTP is strictly port 25 as defined and set in RFC 821 since 1982. Other mail servers will only deliver to you on 25.

What AI models does AIMX support?

All of them. AIMX does not call AI models directly. It is a mail server with a stdio Model Context Protocol (MCP) server built-in, so your AI harnesses and agents can connect to it easily and effectively.

How do I set up email accounts on AIMX?

Once AIMX is set up, mailboxes can be created with the aimx mailboxes CLI, or via the mailbox_create MCP tool by simply instructing your AI harness in plain natural language, such as Create a receipt@ mailbox and file receipts for me when you receive them.

Can I run AIMX without owning a domain name?

No. You need a domain to define how emails are delivered to you (MX record) and verified (DKIM TXT record). Email specifications (RFC 5321 §5.1) require the MX record to point to a domain name, not an IP.

Why do I need AIMX when I can just use Gmail + MCP?

You can, if you do not mind your emails being stored and accessible on both Gmail servers AND third-party MCP servers. If you are on a free Gmail account, you might also be violating Gmail’s ToS. Besides, it is a lot of work to create multiple mailboxes for separate agentic use.

Why do I need AIMX when I can just use MCP-enabled AgentMail or LobsterMail?

You can, if you do not mind paying and do not mind your emails being stored and accessible on third-party servers.

How are emails stored on AIMX?

AIMX stores all incoming and outgoing emails as Markdown files with TOML frontmatter. This makes them trivially easy for AI agents, RAG pipelines, and LLMs to read and parse, no MIME decoding required. Attachments are extracted and stored on disk in native format.

Does AIMX have any automation? How does AIMX prevent prompt injection from incoming emails?

Yes, AIMX supports hooks that fire on incoming mail, but only from senders you trust. You define the trusted sender list. AIMX verifies every incoming message with DKIM and records the result in the frontmatter, so your agent always knows whether a message is authenticated. Mail that fails DKIM, or arrives from an unverified sender, will not trigger any hooks.

Deployment

What about ports 465 and 587?

Ports 465 and 587 are submission ports — used by mail clients to hand a message to a relay. AIMX is the MTA, not a client of one, so submission ports do not apply. Mail goes straight from AIMX to the recipient’s MX on port 25.

Can I run AIMX in Docker or behind NAT?

Docker works if you map port 25 and persist /etc/aimx, /var/lib/aimx, and /run/aimx on the host. Behind NAT you must port-forward 25/tcp both ways and the MX record must resolve to the public IP. AIMX learns the sender IP from the TCP peer, so any proxy in front of port 25 has to be transparent (PROXY protocol is not supported).

Can I run two AIMX instances on one host?

Only if each binds a different IP on port 25. Two listeners cannot share the same ip:25. Point each instance at its own AIMX_CONFIG_DIR and AIMX_DATA_DIR, run each from its own systemd unit, and give each its own UDS path (the default /run/aimx/aimx.sock is hard-coded today. A second instance needs a source patch).

How do I upgrade the binary without losing mail or breaking in-flight SMTP sessions?

Replace /usr/local/bin/aimx and systemctl restart aimx. aimx serve handles SIGTERM by draining both the SMTP and UDS accept loops, so in-flight sessions finish before the process exits. Mail on disk is format-stable; no migration step is required between patch releases.

How do I migrate to a new server or change the domain?

Same domain, new server: rsync -a /etc/aimx/ /var/lib/aimx/ to the new host, install the binary, sudo aimx setup <domain> (re-entrant, it reuses the existing DKIM key), then flip the A/MX record. Different domain: run a fresh aimx setup. The DKIM selector, SPF, and DMARC records all reference the domain and must be regenerated.

DNS and deliverability

What is PTR record? Do I actually need it?

PTR (Pointer Record) is a reverse-DNS record. It maps an IP back to a hostname, the opposite of an A/AAAA record. Setting one improves outbound deliverability and is usually configured at your hosting provider’s control panel rather than at your normal DNS registrar. Because AIMX is not meant for bulk sending, a PTR is optional. If you are only mailing a handful of targeted recipients (often yourself), having DKIM/SPF/DMARC pass, and if needed whitelisting the sender in your mail client, is usually enough.

How do I rotate the DKIM key without a delivery gap?

AIMX today supports one active selector at a time. To rotate without bounces:

  1. sudo aimx dkim-keygen --selector aimx2 (generates a new keypair under a second selector).
  2. Publish the new TXT record at aimx2._domainkey.<domain>, wait for propagation.
  3. Flip dkim_selector = "aimx2" in config.toml and systemctl restart aimx.
  4. Leave the old DNS record up for a few days so in-flight mail still verifies, then remove it.

Enabling enable_ipv6: what exactly changes?

Outbound delivery starts preferring AAAA records when the recipient publishes them. You need to (a) add an AAAA record for your MX hostname and (b) extend SPF with ip6:<your /64 or full v6>. If you leave SPF at the default ip4:YOUR_IP -all, every v6-delivered message will SPF-fail.

Sending

Can I send from *@domain (the catchall)?

No. The catchall is inbound-only. Outbound From must resolve to a concrete, non-wildcard mailbox in config.toml. The daemon parses the submitted From: header itself and rejects catchalls.

What happens on a deferred or failed MX delivery?

AIMX does not run a retry queue. A transient (4xx) failure returns Deferred to the client and is not persisted. The client (e.g. aimx send, an agent) is expected to retry. A permanent (5xx) failure is persisted to sent/<mailbox>/ with delivery_status = "failed" and the SMTP reason in delivery_details. AIMX does not generate DSNs. This keeps the delivery result visible to the calling agent in real time. No send-and-pray.

Can I send with attachments, a custom Reply-To, or a custom Message-Id?

Attachments: yes, repeat --attachment <path>. Custom Reply-To: header: not exposed on the CLI (the --reply-to flag sets In-Reply-To for threading, not the Reply-To header). Custom Message-Id: not exposed. The daemon generates one per send.

Storage

Is the mailbox tree safe to rsync or snapshot while aimx serve is running?

Yes for reads. rsync -a or a filesystem snapshot of /var/lib/aimx/ will produce a consistent per-file copy. Inbound ingest writes each .md atomically (temp file + rename) and mark-read rewrites are serialised under a per-mailbox lock. A snapshot taken mid-ingest may miss the newest message, never a half-written one.

How is thread_id computed, and will threading agree with Gmail?

thread_id is sha256(root)[..8] in hex, where root is the first Message-Id in In-Reply-To, else the first in References, else the email’s own Message-Id. This walks the same header chain Gmail uses, so replies thread correctly in both. Subject-based collapsing (Gmail’s fallback) is not replicated. If a conversation loses its References chain, the two systems can disagree.

Hooks

My on_receive hook didn’t fire. How do I tell why?

Check in this order:

  1. journalctl -u aimx | grep hook_name=<name>. Every fire emits one structured line. No line means the hook was gated.
  2. The target email’s frontmatter: trusted = "false" plus fire_on_untrusted unset is the most common cause. See the trust gate.
  3. If the line is there with a non-zero exit_code, it’s your cmd argv. Test the argv manually: sudo -u <owner> /path/to/cmd[0] cmd[1] ... against the saved .md.

What does mailbox ownership mean for security?

Every mailbox declares a single Linux owner. Storage is chowned <owner>:<owner> 0700. Hooks always exec as the owner uid (the daemon setuids before exec). CRUD over the UDS is gated on SO_PEERCRED matching the mailbox’s owner_uid, or root. A hook can do anything the owner could already do as that user — no more, no less. To run a hook as root, set mailbox.owner = "root" in /etc/aimx/config.toml (which already requires root). See Security: Per-action authorization.

Env var expansion: how does it work?

Hook cmd is exec’d directly — there is no shell. argv elements pass through verbatim. To get shell expansion of $AIMX_* env vars, wrap your cmd in ["/bin/sh", "-c", "..."] explicitly:

cmd = ["/bin/sh", "-c", 'echo "$AIMX_SUBJECT" >> /tmp/log']

Always expand env vars inside double quotes. Sender-controlled header values can contain $(), backticks, quotes, or newlines; the double-quoted form passes them through as literal bytes. The literal token $AIMX_FILEPATH (no shell wrapping) reaches argv unchanged — useful when the agent itself reads env vars (OpenCode, Hermes do this in inline-prompt mode).

Can an after_send hook distinguish a deferral from a permanent failure?

Yes. AIMX_SEND_STATUS is "delivered", "deferred", or "failed". Deferrals do not persist a sent file, so AIMX_FILEPATH is empty for them.

Trust

What does trust = "verified" actually check?

Two conditions: the sender address matches a glob in the effective trusted_senders list, AND the inbound DKIM result is pass. SPF and DMARC are recorded in frontmatter but are not part of the gate. Missing either of those two conditions yields trusted = "false".

Per-mailbox trusted_senders: does it merge with the global list?

It replaces. Setting trusted_senders under a mailbox fully overrides the top-level list for that mailbox. There is no merge, and an empty per-mailbox list means “nobody” for that mailbox.

When is fire_on_untrusted actually appropriate?

When the hook’s side effect is safe regardless of sender. A logger, a metric counter, a push notification with no email content in the payload. Never use it on a hook that hands the email body to an agent or to any shell command that quotes the body. Mailbox isolation (uid-scoped exec + uid-scoped storage) makes the flag a per-owner choice with bounded blast radius — even an adversarial fire on untrusted mail can do no more than what the mailbox owner could already do — but the trust gate is still the primary defense for irreversible side effects. The flag is illegal on after_send hooks and rejected at config load.

Security model

The full write-up lives at Security. The entries below cover the common questions.

Can I use AIMX in place of Postfix or Stalwart?

No. AIMX is a single-domain server for AI agents on a host you own, not a general-purpose MTA. It has no IMAP/POP3, no webmail, no SMTP AUTH, no LMTP, no virtual alias tables, and no submission port on 587. Each mailbox has exactly one Linux owner, and hooks always run as that owner — the boundary is per-mailbox, not per-server.

aimx.sock is mode 0666, why is that fine?

Any local user can connect, but the daemon authorizes every verb server-side via SO_PEERCRED (kernel-supplied peer uid). The DKIM private key never leaves the daemon. The socket is a signing oracle scoped to the caller’s owned mailboxes — never a free pass to forge mail under another mailbox.

The mailbox tree is per-owner, what does that buy me?

On a multi-user host, alice cannot read bob’s mail — inbox/<bob>/ is bob:bob 0700 and she cannot traverse the directory. Hooks on alice’s mailbox run as alice, so a prompt-injected agent stays scoped to alice’s filesystem perms.

Who can read the DKIM private key, and what happens if it leaks?

Only root, via /etc/aimx/dkim/private.key (mode 0600). A leak lets anyone sign mail as your domain until you rotate. Rotate with the selector swap above.

MCP

Can two agents share one aimx mcp process?

No. aimx mcp uses stdio transport. Each MCP client spawns and owns its own process. The filesystem is the shared resource. Concurrent MCP processes coordinate through the daemon (mark-read, mailbox CRUD) or through atomic file writes (ingest, send).

How do I scope an agent to a single mailbox?

Every MCP tool call is scoped to mailboxes the calling uid owns: mailbox_list filters; email_* and hook_* reject with EACCES not authorized for foreign mailboxes. To pin a single agent to a single mailbox, run that agent under a Linux user that owns only the one mailbox you want (sudo aimx mailboxes create <name> --owner <user>). The agent’s MCP server inherits the caller’s uid via stdio transport, so authorization derives entirely from “which Linux user is running aimx mcp.”

How do I update the installed agent plugin after upgrading AIMX?

Re-run aimx agents setup --force and re-select the agents you want to update from the picker. The plugin bundle is embedded in the binary at compile time, so the installed plugin is always in sync with the binary version. --force overwrites whatever is at the destination.

Operations

systemctl status aimx says start-limit-hit. What is it?

The unit caps restarts at StartLimitBurst=5 within StartLimitIntervalSec=60. sudo systemctl reset-failed aimx clears the counter. sudo systemctl start aimx retries. Investigate the crash in journalctl -u aimx -e first. A restart-loop is usually a config error the restart won’t fix.

Where do daemon logs go on OpenRC?

OpenRC does not have journald. AIMX writes nothing of its own. aimx logs tails /var/log/aimx/*.log if the init script redirects there, otherwise falls back to /var/log/messages. On systemd, aimx logs shells out to journalctl -u aimx.

How do I run a dry-run send without touching real MX servers?

Set AIMX_TEST_MAIL_DROP=/path/to/dir before starting aimx serve. Every outbound submission is written to that directory instead of delivered. See Configuration: Environment variables for the full set.

Verifier service

What is services/verifier?

A small companion service that exists purely to answer the question “is port 25 actually reachable from the public internet?”. aimx portcheck and aimx setup call it during setup. Nothing in the mail path depends on it. By default AIMX points at the hosted instance at check.aimx.email, so you do not need to run your own.

When would I self-host services/verifier/?

When you do not want your setup traffic to hit check.aimx.email, or when you are deploying AIMX in an air-gapped / regulated environment. The verifier is a small axum service plus a port-25 listener. See the verifier service README for the Docker Compose deploy. Point AIMX at it with verify_host in config.toml or --verify-host at the command line.

Ready to try AIMX?

One command, one box, one inbox.

Get started