# Email Agent > Build spec for Paperclip. Follow the phases in section 11 in order. Do not skip > a phase's Definition of Done. When something is unclear, ask Sam — do not guess. --- ## 1. Purpose and outcome A web app that triages the family inbox. All mail lands in one Gmail account (`samuelrolfe@gmail.com`); PurelyMail forwards every domain address there. The app sorts mail, recommends actions, performs them in bulk, drafts replies, and answers questions from the Hub. **Outcome (the single success statement):** > Sam opens one page, clears the inbox in a few minutes, and knows nothing > important was missed. ## 2. Scope **In scope** - Read mail from Gmail. - Classify each message: category, importance, suggested action. - Recommend: delete, unsubscribe, reply, file, keep. - Bulk actions: delete, move, mark. - AI reply drafts from templates and rules. - A rules editor (text) that the classifier uses. - A daily summary. - Answer the Hub. **Out of scope** - Sending marketing or bulk mail. - DNS, MX, SPF, DKIM. That is infrastructure, not this app. - The other domains' websites. - Any auto-send without review unless a rule explicitly allows it. ## 3. Users and roles - **One user: Sam.** No multi-user. A session login protects the app. - Adding a second user is out of scope. ## 4. Functional requirements Each item is testable. Numbering is stable; do not renumber. | ID | Requirement | |---|---| | FR-1 | Fetch new mail from Gmail with the Gmail API, on an n8n schedule. Store metadata and body. | | FR-2 | Record which address the mail came through (`sam@audasmedia.com.au`, `hello@where-woof.com`, `hello@kontra.day`, or catch-all), read from the headers. | | FR-3 | Classify each message with JEV: category, importance, suggested action, and a confidence. | | FR-14 | Edit the category list in the UI: add, rename, remove. JEV uses the current list. | | FR-4 | Apply user-written rules from the rules editor to override or refine the classification. | | FR-5 | Show a triage list grouped by category, with the recommended action on each row. | | FR-6 | Bulk actions: select all in a group, then delete, move, or mark. | | FR-7 | Detect unsubscribe links. Keep an "ignore unsubscribe" list. Record what was unsubscribed. | | FR-8 | Draft a reply from a template or rule with the LLM. Let Sam edit, then send. | | FR-9 | Move messages to folders (Gmail labels): replied-to by address, a delete folder, and a saved folder. Deletes are recoverable. | | FR-10 | Produce a daily summary and deliver it **by email**. | | FR-11 | Rules editor: a plain-text area, saved, versioned. JEV and the LLM read it. | | FR-12 | Expose the Hub API: `GET /capabilities`, `POST /ask` (read-only), `POST /notify`. | | FR-13 | Log every action: what, when, which message, which rule. | | FR-15 | Reply identity: send as the address the mail arrived at. Otherwise send as `Sam Rolfe`. | ## 5. Screens and UX flow **Screens** 1. **Login** — session form. One dark indigo band (the design system's login moment). 2. **Inbox (main)** — three areas: left nav (categories and folders), centre list (grouped, with select-all and bulk actions), right detail pane. 3. **Message detail** — body, recommended action, reply draft, and history. 4. **Rules editor** — plain text, save, and a version list. 5. **Reports** — the daily summary, with delete-report. **Main flow** ``` Login → Inbox → scan categories → bulk-delete junk → review "important" → approve or edit reply drafts → send → close ``` **Rules** - Works on a phone browser. Touch targets ≥44px. - No spinners on decorative elements. Subtle opacity pulse only. ## 6. Look and feel Reuse the Family Home Lab design system. Do not invent a new one. - Source: `/home/sam/Docker/Containers/family-home-lab/DESIGN.md` - Tokens: `/home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css` - Copy `tokens.css` into this project. Do not import across projects. Key rules: canvas `#f6f5f4`, one action blue `#0075de`, Inter, 8px grid, pill CTAs, 4px inputs, hairline borders, layered micro-shadows. Include dark mode. ## 7. Data model SQLite, inside the service. Suggested tables: | Table | Holds | |---|---| | `messages` | Gmail id, thread id, from, to, subject, date, body text, source address, received-at | | `classifications` | message id, category, importance, action, confidence, model, created-at | | `rules` | id, text, version, created-at | | `actions_log` | message id, action, actor, rule id, created-at | | `unsubscribe` | domain, status (`done`, `ignored`), created-at | | `drafts` | message id, body, template id, status | | `summaries` | date, text, delivered-to | Retention: keep message bodies for **one month**. Logs are append-only and size-capped. ## 8. Integrations | System | Use | |---|---| | **Gmail API** | Read mail; apply labels; send replies ("Send mail as"). | | **PurelyMail** | Source of the forwarding. Read-only reference. Not required for v1. | | **Amazon SES** | App/website mail only. Not for personal replies in v1. | | **JEV** | Classification (typed category + confidence). HTTP API. | | **OmniRoute** | All other models. | | **n8n** | Triggers the scan on a schedule. | | **Apprise** | Delivery of the daily summary. | | **Hub** | `POST /ask` (read-only), `POST /notify`, `GET /capabilities`. | The Hub interface is frozen in `central_ai_llm_master/docs/ask-contract.md`. Build to it. `/ask` never changes state. ## 9. Technology (locked) | Item | Value | |---|---| | Backend | **Go** | | UI | **HTMX** + server-rendered HTML | | Database | **SQLite** | | Design | Family Home Lab `tokens.css` | | Host | `.13`, Docker | | Container | **This project's own** image and compose file. Not shared with any other project. | | Folder | `/home/sam/paseo/projects/email_agent/` | | Port | Allocate one (suggest **8790**). Record it here once chosen. | | URL | `mail.lab.audasmedia.com.au` via Caddy on `.35` | | Auth | **Separate session login.** Not the console SSO. | ## 10. Rules and constraints 1. All models go through **OmniRoute**, except JEV. 2. Secrets come from `~/.config/environment.d/10-secrets.conf`. Never commit them. 3. `/ask` is read-only. It never changes state. 4. Sending a reply or deleting in bulk needs Sam's action in the UI. No auto-send unless a rule explicitly allows it. 5. **Spam is recommended, never auto-deleted.** Important mail sometimes looks like spam. Confirm every spam action. 6. Deletes are recoverable. Move to a delete folder or trash, never hard-delete. 6. Replies return to the source and stay short. 7. Every action is logged. 8. Do not merge this service into another. Own folder, own container, own port. ## 11. Build order Each phase ends with a Definition of Done (DoD). Do not start the next phase until the DoD passes. **Phase 1 — Skeleton** Go service, SQLite, config, session login, Dockerfile, compose, Caddy route. *DoD:* the app runs on `.13`, login works, an empty dashboard renders, `/health` returns ok. **Phase 2 — Read mail** Gmail OAuth (one manual consent). Fetch, store, and list real messages. *DoD:* the inbox shows real mail, with the source address from FR-2. **Phase 3 — Classify** JEV classification plus the rules table. *DoD:* every message has a category, importance, and suggested action. **Phase 4 — Triage actions** Delete, move, mark; bulk select. *DoD:* select-all then delete works, and the message is recoverable. **Phase 5 — Reply drafts** Templates, LLM draft, edit, send. *DoD:* a reply is drafted, edited, and sent, and the thread updates. **Phase 6 — Unsubscribe and rules editor** *DoD:* a rule written in the editor changes the classification of a real message. **Phase 7 — Summary and Hub** Daily summary; `/capabilities`, `/ask`, `/notify`. *DoD:* the Hub asks "anything important in my inbox?" and gets a correct short answer. **Phase 8 — Documentation** *DoD:* project-docs run; Gitea, Obsidian, Outline, Vikunja, and the map are updated. ## 12. Acceptance criteria The project is done when: - Sam can log in from a phone browser. - Real mail appears, grouped, with the source address shown. - Bulk delete works and is recoverable. - A reply can be drafted, edited, and sent without leaving the app. - The rules editor changes real classifications. - The Hub gets a correct short answer to "anything important in my inbox?". - Every action is in the log. - The app runs in its own container on its own port, behind Caddy. ## 13. Decisions and remaining questions Resolved: 1. **Reply identity:** the address the mail arrived at. Otherwise `Sam Rolfe`. 2. **Categories:** `action`, `important`, `info`, `spam`, `newsletter`, `auto-reply`. **Editable in the UI** (FR-14). 3. **Spam:** recommend and confirm. Never auto-delete. 4. **Daily summary:** by email. 5. **Retention:** one month of message bodies. 6. **Gmail OAuth:** the agent chooses the moment during the build. Remaining: none that block. Add new ones here as they appear.