5
0
Fork
You've already forked email-reply-agent
0
route emails to your chosen LLM agent to reply
  • Shell 100%
Hugo O'Connor c1a5b7a7dd
docs: close install gaps for an agent following the README
- quick start now clones withdone and runs install-himalaya explicitly,
 instead of pointing WITHDONE at a path that doesn't exist yet
- add "Create your first profile" (cp -r an example + edit the 3 files)
- describe what a successful pass logs and how to confirm in draft mode
- fix stale Run-unattended anchor after the macOS heading rename
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026年06月30日 11:36:26 +10:00
docs docs: close install gaps for an agent following the README 2026年06月30日 11:36:26 +10:00
profiles
specs docs: schedule via launchd on macOS (cron can't reach login keychain) 2026年06月26日 21:44:16 +10:00
users/operator
.gitignore feat: X-DMARC-Status gate + courtesy reject notices 2026年06月26日 16:35:43 +10:00
install-himalaya.sh chore: rename project email-agent -> email-reply-agent 2026年06月24日 14:47:50 +10:00
LICENSE chore: add Apache 2.0 license 2026年06月24日 21:22:40 +10:00
README.md docs: close install gaps for an agent following the README 2026年06月30日 11:36:26 +10:00
reply-bot.sh feat: X-DMARC-Status gate + courtesy reject notices 2026年06月26日 16:35:43 +10:00

email-reply-agent

Answer routine mail automatically: for each unread message from a known correspondent, a coding-agent CLI drafts a reply in the voice of the workflow that owns the address, and the script — never the agent — sends it and marks the message read. Strangers are never answered.

Specified in specs/SPEC-001-email-reply-agent.md under the USDD Agent Protocol (PROTO-001). Status: implementing.

🛑 Read docs/SECURITY.md before deploying

This tool acts on instructions derived from email, and email From: is forgeable. A spoofed sender that passes your allowlist runs the agent on the attacker's instructions. The allowlist is worth nothing unless your receiving domain enforces anti-spoofing DNS (DMARC p=reject). The authentication gate is the only thing protecting you — one mistake is host compromise. This is not optional reading.

How it works

A mailroom with named desks. Each desk is a profile (a directory under profiles/) with its own clerk persona, its own allowlist of correspondents it will answer, and its own send/draft policy. The router drops each unread letter on the first matching desk; the clerk (the agent) drafts; the mailroom (the script) posts it.

himalaya IMAP ┌─────────── reply-bot.sh (effectful shell) ────────────┐
──unread──────────────────────▶│ Router ──▶ Approved? ──▶ Agent (claude|codex) │
envelope list -o json │ (profile gate under withdone, drafts a │
 │ match) reply file + sentinel) │
 │ │ drafts │
 │ himalaya template send ◀────────┘ (header check) │
 └──────────────────── flag add seen ────────────────────┘
trust boundary: every email body + every himalaya JSON is UNTRUSTED input

The single irreversible action (send) stays in deterministic shell, so no email body can induce a send — the agent has no send capability (ADR-001 / NFR-004).

Quick start

Prerequisites (resolved via PATH; cron-hardened in the script):

  • himalaya — IMAP/SMTP CLI; run ./install-himalaya.sh to install and configure an account
  • jq — JSON parsing
  • withdone — the headless agent launcher + completion sentinel (sibling repo)
  • A coding-agent CLI per profile: claude (Claude Code) and/or codex
  • coreutils for timeout/gtimeout (brew install coreutils) — so a wedged agent can't hold the lock
# 1. Fetch the withdone launcher. The default WITHDONE path is ../withdone/withdone,
# so cloning it as a sibling means no env var is needed. (See withdone's own
# README for any install step; point elsewhere with `export WITHDONE=/path/to/withdone`.)
git clone https://codeberg.org/anuna/withdone ../withdone
# 2. Install himalaya and configure a mailbox (prompts for address/host/password).
./install-himalaya.sh # see docs/CONFIGURATION.md → Set up a mailbox
# 3. Stay on your Claude subscription, not the metered API (ADR-005).
unset ANTHROPIC_API_KEY
export CLAUDE_CODE_OAUTH_TOKEN="$(claude setup-token)" # long-lived, TTY-free
# 4. Create at least one profile (next step), then run one pass.
./reply-bot.sh

Define a profile in docs/CONFIGURATION.md — the fastest start is cp -r profiles/example-support profiles/<name> and edit the three required files. Each profile must set SEND_MODE — start with draft (saves replies to your Drafts folder for review) and switch to send once you trust it.

A successful pass logs Loaded N profile(s): ..., one line per message (drafting replySEND_MODE=draft: saved to Drafts), and ends with Done. — all on stderr. In draft mode it never sends; confirm the result in your Drafts folder before switching to send. With no matching/approved mail it simply logs the skips and exits cleanly.

⚠️ Running unattended on macOS: use launchd, not cron. A crontab job runs outside your GUI login session and fails on permissions — it can't reach the login keychain (himalaya's password lookup returns exit 44) and is subject to TCC restrictions. Schedule with a LaunchAgent instead; see docs/CONFIGURATION.md.

Documentation

File What's in it
docs/SECURITY.md Threat model, required DNS hardening, per-message DMARC gate, sender-DNS audit. Read first.
docs/CONFIGURATION.md Mailbox setup, profile layout & profile.conf keys, environment overrides, running unattended (cron / launchd).
specs/SPEC-001-... The reply-mode spec: requirements, contracts, ADRs, verification.
specs/SPEC-002-... The planned act-on-codebase mode (draft status) and its threat model.

Layout

  • reply-bot.sh — the effectful shell: discovers unread mail per scan target, routes, gates on the allowlist, launches the agent under withdone, verifies the agent preserved the reply headers (From/To/Subject/In-Reply-To) byte-for-byte before sending (REQ-005 / CON-004), sends or drafts, then marks read only on success (REQ-007).
  • install-himalaya.sh — installs himalaya and writes one IMAP/SMTP account (keyring auth).
  • profiles/ — one directory per workflow, co-locating persona, allowlist, skills, knowledge.
  • specs/ — the zetl spec vault (requirements, contracts, ADRs).

Development

bash -n reply-bot.sh # syntax check
shellcheck reply-bot.sh # lint (if installed)
zetl check --dead-links # spec vault hygiene

The header-preservation and lock-liveness logic are pure helpers (header_value, verify_headers, acquire_lock, touch_lock), unit-testable in isolation against fixtures — see the spec's Verification section (TEST-001..012, TEST-injection). A committed tests/ harness and live-mailbox verification remain the open gates before status moves to implemented.

License

Licensed under the Apache License 2.0. Copyright 2026 Hugo O'Connor. The warranty disclaimer and liability limit (sections 7–8) are load-bearing given the security model — you run this at your own risk. See docs/SECURITY.md.