Hook Recipes
Copy-paste aimx hooks create invocations for every supported AI agent. For the underlying mechanics, see Hooks & Trust; for installing an agent’s plugin so its MCP tools are discoverable, see Agent Integration.
The /var/log/aimx/<agent>.log paths below are user-chosen destinations for hook script output. They are not AIMX’s own logs — AIMX logs to journald (systemd) or the system logger (OpenRC). See Troubleshooting: Logs.
What counts as a hook recipe?
A hook recipe is a [[mailboxes.<name>.hooks]] block (or equivalent aimx hooks create invocation) whose cmd invokes an AI agent or HTTP endpoint non-interactively against an incoming email. AIMX writes the .md file, evaluates the trust gate, then execvps the argv as the mailbox owner with the raw .md piped to stdin and $AIMX_FILEPATH set. The agent reads the email and takes action (reply, file a ticket, update a calendar). Exit code is logged but never blocks delivery.
cmd is execvp’d directly: there is no /bin/sh between AIMX and the agent. User-controlled header fields like From: and Subject: can contain shell metacharacters ($(), backticks, ;, quotes), so AIMX delivers them as env vars and avoids the shell parser entirely. When you need shell expansion, spell out ["/bin/sh", "-c", "..."] so it is visible at the call site.
Two consumption shapes
The daemon always pipes the .md to stdin, but recipes split into two camps based on whether the agent reads stdin in headless mode:
- Native stdin (Claude Code, Codex, Gemini, Goose, webhook): the agent reads the piped email off stdin; the prompt only tells it what to do.
- Filepath only (OpenCode, Hermes): the agent does not read stdin in headless mode, so the prompt instructs it to read
$AIMX_FILEPATHvia its filesystem tool. argv is not shell-expanded — the literal$AIMX_FILEPATHtoken reaches the agent, which expands it at run time.
Hooks that only need the subject or sender can read $AIMX_SUBJECT / $AIMX_FROM and ignore stdin.
Absolute paths
Every recipe uses /usr/local/bin/<agent> for cmd[0]. Config::load rejects relative paths (hook has non-absolute cmd[0]). Adjust the path to match your install — which <agent> shows where each binary lives. The cmd[0] literal in your config.toml (or in the --cmd argv array passed to aimx hooks create) must point at the binary that exists on the box.
Self-loop reminder
The headless agent process runs as the mailbox owner’s uid. That user must have the agent binary installed at the cmd[0] path AND the AIMX skill installed (run aimx agents setup as that user and pick the agent). Without both, the agent starts without the skill loaded and does not know what to do with the email.
Claude Code
- Docs: https://docs.claude.com/en/docs/claude-code/cli-reference
- Stdin: native (
-preads from stdin whencmd[0]is fed via pipe). - Bypass permissions:
--dangerously-skip-permissions(alias--permission-mode bypassPermissions) is required for autonomous tool calls.
aimx hooks create \
--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
Optional: --mcp-config /path + --strict-mcp-config to pin which MCP config Claude reads, and --model sonnet (or --model opus) to tune the routing model.
Codex CLI
- Docs: https://github.com/openai/codex (run
codex exec --help). - Stdin: native (the trailing
-argument tells Codex to read stdin as the prompt — the email itself becomes Codex’s task). - Bypass:
--full-autois the safe sandboxed default;--dangerously-bypass-approvals-and-sandboxonly inside a contained host. - Required:
--skip-git-repo-checkbecause hooks fire from/var/lib/aimx/inbox/<mailbox>/, which is not a git repo.
aimx hooks create \
--mailbox triage \
--event on_receive \
--cmd '["/usr/local/bin/codex", "exec", "--skip-git-repo-check", "--full-auto", "-"]' \
--name triage_codex
OpenCode
- Docs: https://opencode.ai/docs/cli/
- Stdin: inline-only (
opencode rundoes not read stdin in headless mode). - Bypass:
--dangerously-skip-permissions.
aimx hooks create \
--mailbox research \
--event on_receive \
--cmd '["/usr/local/bin/opencode", "run", "--dangerously-skip-permissions", "Read the aimx email at the path in env var AIMX_FILEPATH and act on it via the aimx MCP server (e.g. email_reply)."]' \
--name research_opencode
The literal token $AIMX_FILEPATH is not shell-expanded (argv is not shell-parsed). The agent sees the literal string AIMX_FILEPATH (or $AIMX_FILEPATH if you write it that way) inside its prompt and uses its Bash/Read tool to read the env var at run time. Optional: --format json for a structured run log.
Gemini CLI
- Docs: https://github.com/google-gemini/gemini-cli
- Stdin: native (
-preads from stdin when piped). - Bypass:
--yoloauto-approves tool calls; required for unattended operation.
aimx hooks create \
--mailbox notes \
--event on_receive \
--cmd '["/usr/local/bin/gemini", "-p", "Read the piped email and file it into my notes via the aimx MCP server.", "--yolo"]' \
--name notes_gemini
Optional: -m gemini-2.5-flash to pin the routing model.
Goose
- Docs: https://block.github.io/goose/docs/guides/headless-goose
- Stdin: native via
--instructions -(reads instructions from stdin; the recipe form is preferred for production). - Two recipes shown: ad-hoc (instructions piped via stdin) and recipe-based (the inner recipe pre-binds the
aimx mcpextension).
Ad-hoc:
aimx hooks create \
--mailbox ops \
--event on_receive \
--cmd '["/usr/local/bin/goose", "run", "--instructions", "-", "--quiet"]' \
--name ops_goose
Recipe-based (preferred for production):
aimx hooks create \
--mailbox ops \
--event on_receive \
--cmd '["/usr/local/bin/goose", "run", "--recipe", "/etc/aimx/goose-aimx-hook.yaml"]' \
--name ops_goose_recipe
The referenced recipe pre-binds the aimx mcp extension and parameterizes the inbound email path. See Goose’s recipe documentation for the inner-recipe shape.
OpenClaw
OpenClaw does not document a one-shot prompt mode as of Apr 2026. Its surfaces are interactive (chat-channel, Control-UI dashboard) plus admin subcommands; there is no --prompt-style entry point that fits the headless hook pattern.
To use OpenClaw with AIMX, treat AIMX as a read source via the MCP server and trigger the agent through OpenClaw’s documented interactive entry points. For shell-side notifications on new mail (so OpenClaw operators know there is mail to look at), wire a simple non-agent on_receive hook:
aimx hooks create \
--mailbox openclaw \
--event on_receive \
--cmd '["/bin/sh", "-c", "ntfy pub openclaw-mail \"New email: $AIMX_SUBJECT from $AIMX_FROM\""]' \
--fire-on-untrusted \
--name openclaw_notify
When OpenClaw later publishes a one-shot CLI, this section will be updated; track upstream at https://docs.openclaw.ai/.
Hermes
- Docs: https://hermes-agent.nousresearch.com/
- Stdin: inline-only (stdin handling for
hermes chat -qis undocumented as of Apr 2026; this recipe ships the inline-only form). - Bypass:
--yoloskips dangerous-command approval;--ignore-user-config --ignore-rulesfor fully isolated runs.
aimx hooks create \
--mailbox hermes \
--event on_receive \
--cmd '["/usr/local/bin/hermes", "chat", "-q", "Read the aimx email at the path in env var AIMX_FILEPATH and act on it via the aimx MCP server.", "--yolo"]' \
--name hermes_chat
If a future Hermes release confirms that chat -q accepts piped stdin, shorten the inline prompt to “Read the piped email and act on it via the AIMX MCP server.” — the daemon already pipes the email regardless.
Webhook (POST to URL)
A pure-curl recipe that POSTs the raw .md to an HTTPS endpoint. No agent required.
aimx hooks create \
--mailbox alerts \
--event on_receive \
--cmd '["/usr/bin/curl", "-sS", "-X", "POST", "-H", "Content-Type: application/json", "--data-binary", "@-", "https://hooks.example.com/aimx"]' \
--name alerts_webhook
--data-binary @- tells curl to POST whatever lands on its stdin verbatim. Since the daemon always pipes the email to the hook, the receiver gets the raw .md (TOML frontmatter + body) as the request body. Use Content-Type: text/markdown instead if your receiver expects that explicitly.
after_send recipes
Send-side hooks run after AIMX resolves the MX delivery attempt. They cannot affect the send result (hooks are observability-only) but are ideal for audit logs, outbound notifications, or post-send bookkeeping.
Append to an audit log
aimx hooks create \
--mailbox alice \
--event after_send \
--cmd '["/bin/sh", "-c", "printf \"%s %s %s %s\\n\" \"$AIMX_SEND_STATUS\" \"$AIMX_TO\" \"$AIMX_SUBJECT\" \"$AIMX_HOOK_NAME\" >> /var/log/aimx/alice-sent.log"]' \
--name alice_audit
Page on failed sends
aimx hooks create \
--mailbox alerts \
--event after_send \
--cmd '["/bin/sh", "-c", "if [ \"$AIMX_SEND_STATUS\" != \"delivered\" ]; then ntfy pub on-call \"aimx send to $AIMX_TO $AIMX_SEND_STATUS: $AIMX_SUBJECT\"; fi"]' \
--name alerts_failed_page
Recipient-based filtering (shell guard)
after_send hooks have no built-in filter fields — do recipient/subject matching in the cmd itself with a shell guard.
aimx hooks create \
--mailbox marketing \
--event after_send \
--cmd '["/bin/sh", "-c", "case \"$AIMX_TO\" in *@customer-co.com) curl -fsS -X POST https://hooks.internal/marketing-sent -d \"to=$AIMX_TO&status=$AIMX_SEND_STATUS\" ;; esac"]' \
--name marketing_customer_notify
Operational tips
Logging
AIMX itself emits one structured log line per hook fire to journald:
hook_name=<name> event=<on_receive|after_send> mailbox=<m> owner=<u> exit_code=<n> duration_ms=<n>
Grep by hook_name=<name> (journalctl -u aimx | grep hook_name=accounts_claude) to trace every fire of a specific hook. Hook stdout / stderr are captured by the daemon (truncated at 64 KiB each) and surfaced as stderr_tail=... on the structured line.
If you want a separate per-agent log file too, wrap the agent invocation in ["/bin/sh", "-c", "<agent> ... >> /var/log/aimx/<agent>.log 2>&1"]. Most operators find the journald line sufficient.
Exit codes
A non-zero exit from the hook command is logged at warn but does not block delivery or the send. Email is always stored as a .md file. This is intentional: you do not want flaky agent CLIs to stall your mailbox.
Concurrent hooks
If two emails arrive in rapid succession, aimx serve fires two hook subprocesses in parallel. Agent CLIs that lock a resource (e.g. a per-repo Aider session) can collide. Serialise inside your own wrapper using flock:
aimx hooks create \
--mailbox bugs \
--event on_receive \
--cmd '["/usr/bin/flock", "/tmp/myapp.lock", "/usr/local/bin/claude", "-p", "Reproduce the reported bug.", "--dangerously-skip-permissions"]' \
--name bugs_serialised
Testing a recipe locally
Use the aimx ingest CLI to simulate a real delivery without a live SMTP exchange:
sudo aimx --data-dir /tmp/aimx-test ingest catchall@agent.example.com \
< tests/fixtures/plain.eml
Watch journald (aimx logs --follow) and confirm the agent ran with the expected env vars.
Ready to try AIMX?
One command, one box, one inbox.
Get started