- README: architecture Mermaid, Phase 8 status, Phase 7 endpoints, Documentation index - docs/outline/: AI-context collection (index, system, config, code map, runbook) - docs/email-agent-architecture.mmd + rendered PNG + Archify map (showcase pass) - docs/vikunja-tasks.json (Vikunja project 17, tasks 179-185) - docs/phase8-documentation.md evidence
4.6 KiB
4.6 KiB
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 useDelivered-To. /askis 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
-retainjob clears older bodies and capsactions_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.