# Email Agent — System Overview

## Purpose

The Email Agent triages the family inbox. All mail lands in one Gmail account
(`samuelrolfe@gmail.com`). PurelyMail forwards every domain address there.

Sam opens one page. He clears the inbox in a few minutes. He knows that nothing
important was missed.

The build spec is `AGENTS.md`. The outcome statement is in `AGENTS.md` section 1.

## Architecture

```
Sam (phone or desktop browser)
  -> Caddy on .35 (mail.lab.audasmedia.com.au)
  -> Email Agent container on .13 :8790
       |- HTMX dashboard (Go, server-rendered HTML)
       |- SQLite (/data/email_agent.db, own volume)
       |- IMAP read  -> imap.gmail.com:993 (IPv4 only)
       |- SMTP send  -> smtp.gmail.com:587 (IPv4 only, STARTTLS)
       |- JEV        -> https://api.typesafe.ai (classification)
       |- OmniRoute  -> http://192.168.20.13:20129/v1 (reply drafts)
       |- Apprise    -> http://192.168.20.35:8210 (daily summary email)
       \- Hub API    -> read-only /ask, /capabilities, /notify
Hub on .13 :8788 asks this service with a shared bearer token.
n8n runs the CLI jobs (-fetch, -classify, -summary-send) on a schedule.
```

## Services and connections

| Service | Host | Address | Role |
| :--- | :--- | :--- | :--- |
| `email-agent` | `.13` | `:8790` | Web app, API, and CLI jobs. |
| Caddy | `.35` | `:443` | TLS reverse proxy for `mail.lab.audasmedia.com.au`. |
| Gmail IMAP | cloud | `imap.gmail.com:993` | Read mail. Force IPv4. |
| Gmail SMTP | cloud | `smtp.gmail.com:587` | Send replies. Force IPv4. |
| JEV | cloud | `api.typesafe.ai` | Classify each message. |
| OmniRoute | `.13` | `:20129` | Draft reply replies. Not JEV. |
| Apprise | `.35` | `:8210` | Deliver the daily summary by email. |
| Central AI Hub | `.13` | `:8788` | Ask questions. Read-only. |
| n8n | `.13` | — | Trigger the CLI jobs. |

## Functional requirements in use

| ID | Requirement | Where |
| :--- | :--- | :--- |
| FR-1 | Fetch mail with the Gmail path on a schedule. | `-fetch`, `/actions/fetch` |
| FR-2 | Record the source address from `X-Pm-Original-To`. | `internal/mailfetch` |
| FR-3 | Classify with JEV: category, importance, action, confidence. | `internal/classify` |
| FR-4 | Apply user rules over the classification. | `internal/classify/rules.go` |
| FR-5 | Show a triage list grouped by category. | `internal/server/inbox.go` |
| FR-6 | Bulk delete, move, and mark. | `internal/server/actions.go` |
| FR-7 | Detect unsubscribe links. Keep an ignore list. | `internal/unsubscribe` |
| FR-8 | Draft a reply with the LLM. Edit, then send. | `internal/reply`, `internal/mailops` |
| FR-9 | Move mail to Gmail labels. Deletes are recoverable. | `internal/mailops` |
| FR-10 | Build a daily summary and send it by email. | `internal/summary`, `internal/apprise` |
| FR-11 | Edit rules as plain text. Save and version. | `/rules`, `internal/server/rules.go` |
| FR-12 | Expose `GET /capabilities`, `POST /ask`, `POST /notify`. | `internal/hub` |
| FR-13 | Log every action. | `internal/store` `actions_log` |
| FR-14 | Edit the category list in the UI. | `/categories` |
| FR-15 | Send as the address the mail arrived at. | `internal/reply`, FR-2 value |

## Build phase status

Phases 1 to 7 are complete and deployed. Phase 8 documents the project.

| Phase | Scope | State |
| :--- | :--- | :--- |
| 1 | Go skeleton, SQLite, session login, Docker, Caddy. | Done. |
| 2 | Gmail IMAP read, FR-2 source address. | Done. |
| 3 | JEV classification, rules table, editable categories. | Done. |
| 4 | Triage actions: delete, move, mark, bulk select. | Done. |
| 5 | Reply drafts, edit, send. | Done. |
| 6 | Unsubscribe detection and the rules editor. | Done. |
| 7 | Daily summary by email and the read-only Hub API. | Done. |
| 8 | Documentation across Gitea, Obsidian, Outline, Vikunja, and the map. | Done. |

## Key rules

- Force IPv4 for every Gmail connection. IPv6 to Gmail failed from `.13`.
- The source address header is `X-Pm-Original-To`. Never use `Delivered-To`.
- `/ask` is read-only. It never changes state.
- Spam is recommended, never auto-deleted.
- Deletes are recoverable. The app uses a Delete label.
- Sending a reply needs Sam's action in the UI. No auto-send.
- Every action is logged.
- Message bodies are kept for one month. The `-retain` job clears older bodies
  and caps `actions_log`. Metadata stays.

## Related documents

- `AGENTS.md` — the build spec and the phase plan.
- `README.md` — the technical repository README.
- `docs/phase7-summary-hub.md` — the Phase 7 evidence.
- `docs/outline/` — these AI-context documents.
- `docs/email-agent-map.html` — the interactive architecture map.
