# Email Agent — Code Map ## Folder structure ``` email_agent/ ├── AGENTS.md Build spec (phases 1-8, requirements, rules). ├── README.md Technical repository README. ├── cmd/email-agent/ │ └── main.go Entry point. HTTP server and every CLI flag. ├── internal/ │ ├── apprise/ Apprise API client for the daily summary email. │ ├── classify/ JEV client, rule engine, classification orchestration. │ ├── config/ Environment-driven configuration. │ ├── hub/ Hub contract: capabilities, /ask answerer, notify. │ ├── mailfetch/ Stdlib-only IMAP client, MIME parse, FR-2 source address. │ ├── mailops/ Gmail label moves, flag changes, and SMTP sending. │ ├── reply/ OmniRoute LLM drafts and reply templates. │ ├── server/ HTTP routes, auth middleware, HTMX handlers. │ ├── session/ Server-side session store and signed cookie values. │ ├── store/ SQLite open, schema, migrations, all queries. │ ├── summary/ Deterministic daily-summary builder. │ ├── unsubscribe/ RFC 2369/8058 and body unsubscribe-link detection. │ └── version/ Build metadata (-ldflags). ├── web/ │ ├── embed.go Embeds the static and template files. │ ├── static/ tokens.css, app.css, Inter fonts, htmx.min.js. │ └── templates/ login, dashboard, inbox, message, categories, rules, │ unsubscribe, reports. ├── docs/ Phase evidence, AI context, map, diagrams. ├── Dockerfile Multi-stage, CGO-free build. ├── docker-compose.yml Own container, image, port, and volume. └── Makefile Build, test, and run targets. ``` ## Entry points | Entry point | Language | Role | | :--- | :--- | :--- | | `cmd/email-agent/main.go` | Go | Service and CLI. | | `internal/server/server.go` | Go | HTTP router and middleware. | | `internal/store/schema.sql` | SQL | The AGENTS.md section 7 tables. | ## CLI flags | Flag | Effect | | :--- | :--- | | `-version` | Print version, commit, and build time. | | `-healthcheck` | Probe the local `/health` endpoint and exit. | | `-fetch` | Run one Gmail IMAP scan and exit (FR-1). | | `-classify` | Classify pending messages with JEV and exit. | | `-classify-all` | Re-classify every stored message. | | `-check-smtp` | Verify SMTP IPv4, STARTTLS, and login. Sends no mail. | | `-check-thread ` | Confirm a stored thread has a sent reply. Read-only. | | `-summary` | Build today's summary, store it, and print it. Sends nothing. | | `-summary-send` | Build the summary and deliver it by email via Apprise. | | `-notify` | Send one notification to the Hub (`POST /notify`). | | `-retain` | Clear old message bodies and cap `actions_log` (AGENTS.md section 7). | ## HTTP routes | Method | Path | Auth | Purpose | | :--- | :--- | :--- | :--- | | `GET` | `/health` | public | Liveness and database check. | | `GET` | `/readyz` | public | Readiness. | | `GET` | `/login` | public | Sign-in form. | | `POST` | `/login` | public | CSRF-checked sign-in. | | `POST` | `/logout` | session | End the session. | | `GET` | `/` | session | Dashboard. | | `GET` | `/partials/inbox` | session | HTMX inbox fragment. | | `POST` | `/actions/fetch` | session + CSRF | One IMAP scan. | | `POST` | `/actions/classify` | session + CSRF | Classify pending messages. | | `POST` | `/actions/reclassify` | session + CSRF | Re-classify every message. | | `POST` | `/actions/bulk` | session + CSRF | Bulk delete, move, mark, restore. | | `GET` | `/partials/message` | session | Message detail fragment. | | `POST` | `/actions/draft` | session + CSRF | Generate and store a reply draft. | | `POST` | `/actions/draft/save` | session + CSRF | Save draft edits. | | `POST` | `/actions/send` | session + CSRF + confirm | Send a reply over SMTP. | | `GET` | `/categories` | session | Category editor (FR-14). | | `POST` | `/categories/add` | session + CSRF | Add a category. | | `POST` | `/categories/rename` | session + CSRF | Rename and cascade. | | `POST` | `/categories/remove` | session + CSRF | Remove and reassign. | | `GET` | `/rules` | session | Rules editor (FR-11). | | `POST` | `/rules/save` | session + CSRF | Save a new rules version. | | `POST` | `/rules/restore` | session + CSRF | Restore an older version. | | `POST` | `/rules/reclassify` | session + CSRF | Re-classify with current rules. | | `GET` | `/unsubscribe` | session | Unsubscribe links and ignore list (FR-7). | | `POST` | `/unsubscribe/ignore` | session + CSRF | Add a domain to the ignore list. | | `POST` | `/unsubscribe/unignore` | session + CSRF | Remove a domain record. | | `POST` | `/unsubscribe/mark` | session + CSRF + confirm | Record a domain. No link is followed. | | `GET` | `/reports` | session | Daily summary and delete-report. | | `GET` | `/capabilities` | Hub bearer | Hub capability list (FR-12). | | `POST` | `/ask` | Hub bearer | Read-only short answer. | | `GET` | `/static/...` | public | Embedded CSS, fonts, htmx. | ## Data model SQLite. The schema is in `internal/store/schema.sql`. Migrations run at start and are recorded in `schema_migrations`. | Table | Holds | | :--- | :--- | | `messages` | Gmail id, thread id, headers, from, to, subject, body, source address. | | `classifications` | Category, importance, action, confidence, model. | | `categories` | The editable category list (FR-14). | | `rules` | Plain-text rules, versioned (FR-11). | | `actions_log` | Append-only action log (FR-13). Capped by `-retain`. | | `unsubscribe` | Detected domains, status, and ignore list (FR-7). | | `drafts` | Reply draft body, template, and sent state (FR-8). | | `summaries` | Daily summary text and delivery target (FR-10). | Migrations: v2 adds IMAP UID fields. v3 seeds the first categories and rules. v4 adds `folder` and `mailbox`. v5 adds `message_id_header` and `drafts.sent_at`. v6 adds `list_unsubscribe` fields and `unsubscribe.url`. ## Key modules | Module | Role | | :--- | :--- | | `internal/mailfetch/imap.go` | Minimal IMAP client. Forces `tcp4`. | | `internal/mailfetch/message.go` | MIME parse and the FR-2 source address. | | `internal/mailfetch/imap_write.go` | IMAP MOVE, COPY, and flag changes. | | `internal/classify/jev.go` | JEV `/v1/systemone` client. Three Choice questions. | | `internal/classify/rules.go` | Rule parse and first-match-wins engine (FR-4). | | `internal/mailops/mailops.go` | App folder to Gmail label mapping. | | `internal/mailops/smtp.go` | SMTP send with IPv4 and STARTTLS. | | `internal/reply/draft.go` | Reply prompt and OmniRoute call. | | `internal/reply/templates.go` | Built-in reply templates. | | `internal/store/retention.go` | Body clearing and `actions_log` cap. | | `internal/hub/ask.go` | Deterministic, read-only answerer. | ## Tests All Go tests run in a container: ```sh timeout 600 docker run --rm -v "$PWD":/src -w /src golang:1.25-alpine go test ./... ``` | Package | Coverage | | :--- | :--- | | `internal/classify` | JEV client, rule engine, orchestration. | | `internal/hub` | Answer intents, auth, read-only proof, notify. | | `internal/mailfetch` | IMAP parse, MIME, FR-2, write ops. | | `internal/mailops` | Label mapping, SMTP. | | `internal/reply` | Draft prompts and templates. | | `internal/server` | Auth, routes, CSRF, handlers. | | `internal/store` | Schema, retention, triage, categories, summaries. | | `internal/summary` | Counts, ordering, text. | | `internal/unsubscribe` | Link detection. | | `internal/apprise`, `internal/config`, `internal/session` | Client and config. | ## Dependencies Go 1.25. The only database driver is `modernc.org/sqlite` (pure Go, no CGO). The mail path uses the Go standard library plus a minimal IMAP client. The UI uses vendored HTMX and the Family Home Lab `tokens.css`. No JavaScript build step.