docs: Phase 8 documentation - Gitea, Obsidian, Outline, Vikunja, map (AUD-74)
- 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
This commit is contained in:
44
docs/outline/00-index.md
Normal file
44
docs/outline/00-index.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Email Agent — AI Document Index
|
||||
|
||||
> **Target**: AI agents.
|
||||
> **Writing style**: ASD-STE100 Simplified Technical English.
|
||||
|
||||
## Document catalog
|
||||
|
||||
Read this index. Inject the exact document you need. Do not search all files.
|
||||
|
||||
| Document | Path | Scope |
|
||||
| :--- | :--- | :--- |
|
||||
| **System Overview** | `docs/outline/01-system-overview.md` | Purpose, architecture, services, phase status. |
|
||||
| **Configuration and Environment** | `docs/outline/02-config-and-env.md` | Environment variables, config paths, ports, secrets. |
|
||||
| **Code Map** | `docs/outline/03-code-map.md` | Folder structure, entry points, dependencies, schema. |
|
||||
| **Agent Runbook** | `docs/outline/04-agent-runbook.md` | Commands to test, deploy, restart, and roll back. |
|
||||
|
||||
The build spec is `AGENTS.md`. The phase evidence files are `docs/phaseN-*.md`.
|
||||
The interactive map is `docs/email-agent-map.html`.
|
||||
|
||||
## System summary
|
||||
|
||||
The Email Agent triages the family inbox. All mail lands in one Gmail account.
|
||||
Sam opens one page, clears the inbox in minutes, and sees nothing important
|
||||
missed. The service reads Gmail over IMAP, classifies each message with JEV,
|
||||
recommends an action, applies bulk actions, drafts replies, and answers the Hub.
|
||||
|
||||
## Core configuration
|
||||
|
||||
- **Secrets file**: `~/.config/environment.d/10-secrets.conf` (mode 600).
|
||||
- **Project directory**: `/home/sam/paseo/projects/email_agent/`.
|
||||
- **Live host**: `.13`, one container, own image.
|
||||
- **Live port**: `8790`. Public URL: `https://mail.lab.audasmedia.com.au`.
|
||||
- **Database**: SQLite at `/data/email_agent.db` on the `email_agent_data` volume.
|
||||
|
||||
## Agent restart steps
|
||||
|
||||
1. Read the catalog above. Inject only the documents you need.
|
||||
2. Check git status: `git status`.
|
||||
3. Check recent commits: `git log -n 5 --oneline`.
|
||||
4. Run the Go tests in a container:
|
||||
|
||||
```sh
|
||||
timeout 600 docker run --rm -v "$PWD":/src -w /src golang:1.25-alpine go test ./...
|
||||
```
|
||||
98
docs/outline/01-system-overview.md
Normal file
98
docs/outline/01-system-overview.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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.
|
||||
86
docs/outline/02-config-and-env.md
Normal file
86
docs/outline/02-config-and-env.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Email Agent — Configuration and Environment
|
||||
|
||||
## Secrets
|
||||
|
||||
All secrets live in `~/.config/environment.d/10-secrets.conf` on `.13` (mode
|
||||
600). The file is never committed. `docker-compose.yml` passes the names through
|
||||
from the host environment.
|
||||
|
||||
| Key | Purpose |
|
||||
| :--- | :--- |
|
||||
| `GMAIL_USER` | Gmail account that receives the forwarded mail. |
|
||||
| `GMAIL_APP_PASSWORD` | Gmail app password for IMAP and SMTP. No OAuth. |
|
||||
| `EMAIL_AGENT_SESSION_SECRET` | Session cookie signing key. |
|
||||
| `EMAIL_AGENT_PASSWORD` | Login password for Sam. |
|
||||
| `JEV_API_KEY` | JEV (TypeSafe System One) API key. |
|
||||
| `OMNIROUTE_API_KEY` | OmniRoute API key for reply drafts. |
|
||||
| `HUB_TOKEN` | Shared bearer token for the Hub API. |
|
||||
| `APPRISE_URLS` | Apprise destination for the summary email. |
|
||||
| `EMAIL_AGENT_SUMMARY_TO` | Summary recipient. |
|
||||
|
||||
`environment.d` reaches the systemd user manager. It does not reach a fresh SSH
|
||||
shell. Read the values explicitly, or pass them to `docker compose`.
|
||||
|
||||
## Runtime variables
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
| :--- | :--- | :--- |
|
||||
| `EMAIL_AGENT_ENV` | `development` | `development`, `production`, or `test`. |
|
||||
| `EMAIL_AGENT_ADDR` | `:8790` | Listen address. |
|
||||
| `EMAIL_AGENT_PORT` | `8790` | Listen port when `ADDR` is unset. |
|
||||
| `EMAIL_AGENT_DB_PATH` | `data/email_agent.db` | SQLite file. Container uses `/data/email_agent.db`. |
|
||||
| `EMAIL_AGENT_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`. |
|
||||
| `EMAIL_AGENT_PUBLIC_BASE_URL` | — | Public URL for links. |
|
||||
|
||||
## Gmail variables
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
| :--- | :--- | :--- |
|
||||
| `EMAIL_AGENT_IMAP_HOST` | `imap.gmail.com` | IMAP host. |
|
||||
| `EMAIL_AGENT_IMAP_PORT` | `993` | IMAP port. Implicit TLS. |
|
||||
| `EMAIL_AGENT_IMAP_LOOKBACK_DAYS` | `14` | First-scan window. |
|
||||
| `EMAIL_AGENT_IMAP_MAX_MESSAGES` | `100` | Per-scan message cap. |
|
||||
| `EMAIL_AGENT_SMTP_HOST` | `smtp.gmail.com` | SMTP submission host. |
|
||||
| `EMAIL_AGENT_SMTP_PORT` | `587` | SMTP port. STARTTLS. |
|
||||
|
||||
The dialer uses `tcp4`. Gmail is reached over IPv4 only.
|
||||
|
||||
## Model and integration variables
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
| :--- | :--- | :--- |
|
||||
| `JEV_BASE_URL` | `https://api.typesafe.ai` | JEV base URL. |
|
||||
| `JEV_MODEL` | `jev-latest` | System One model alias. |
|
||||
| `EMAIL_AGENT_CLASSIFY_CONCURRENCY` | `4` | In-flight JEV requests. |
|
||||
| `EMAIL_AGENT_CLASSIFY_MAX_MESSAGES` | `200` | Messages per classify run. |
|
||||
| `OMNIROUTE_BASE_URL` | `http://192.168.20.13:20129/v1` | OmniRoute gateway. |
|
||||
| `OMNIROUTE_MODEL` | `auto/best-chat` | Reply-draft model. |
|
||||
| `HUB_BASE_URL` | `http://192.168.20.13:8788` | Hub base URL. |
|
||||
| `HUB_NOTIFY_TIMEOUT` | `5s` | Notify call timeout. |
|
||||
| `APPRISE_URL` | `http://192.168.20.35:8210` | Apprise API base. |
|
||||
| `APPRISE_KEY` | — | Stored Apprise configuration key. |
|
||||
| `EMAIL_AGENT_SUMMARY_TO` | `samuelrolfe@gmail.com` | Summary recipient. |
|
||||
| `EMAIL_AGENT_RETENTION_DAYS` | `30` | Message-body window for `-retain` (AGENTS.md section 7). |
|
||||
| `EMAIL_AGENT_ACTIONS_LOG_MAX_ROWS` | `50000` | Cap for the append-only `actions_log`. |
|
||||
|
||||
## Config paths
|
||||
|
||||
| Path | Role |
|
||||
| :--- | :--- |
|
||||
| `/home/sam/paseo/projects/email_agent/` | Project source and docs. |
|
||||
| `/home/sam/paseo/projects/email_agent/docker-compose.yml` | Own container and image. |
|
||||
| `/home/sam/paseo/projects/email_agent/.env.example` | Full list of variable names. |
|
||||
| `/data/email_agent.db` | SQLite file in the container. |
|
||||
| `email_agent_data` | Docker named volume for the database. |
|
||||
|
||||
## Ports
|
||||
|
||||
| Port | Host | Service |
|
||||
| :--- | :--- | :--- |
|
||||
| `8790` | `.13` | Email Agent HTTP. |
|
||||
| `443` | `.35` | Caddy TLS for `mail.lab.audasmedia.com.au`. |
|
||||
| `993` | cloud | Gmail IMAP to Gmail. |
|
||||
| `587` | cloud | Gmail SMTP to Gmail. |
|
||||
| `8788` | `.13` | Central AI Hub. This service both serves and calls it. |
|
||||
| `8210` | `.35` | Apprise API. |
|
||||
| `20129` | `.13` | OmniRoute gateway. |
|
||||
158
docs/outline/03-code-map.md
Normal file
158
docs/outline/03-code-map.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# 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 <id>` | 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.
|
||||
143
docs/outline/04-agent-runbook.md
Normal file
143
docs/outline/04-agent-runbook.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Email Agent — Agent Runbook
|
||||
|
||||
## Before you start
|
||||
|
||||
1. Read `00-index.md`. Inject only the documents you need.
|
||||
2. Read `AGENTS.md` section 11 (build order) and section 10 (rules).
|
||||
3. Check git status and the last commits.
|
||||
|
||||
```sh
|
||||
cd /home/sam/paseo/projects/email_agent
|
||||
git status
|
||||
git log -n 5 --oneline
|
||||
```
|
||||
|
||||
## Run the tests
|
||||
|
||||
Run the Go tests in a container. The image has the toolchain.
|
||||
|
||||
```sh
|
||||
timeout 600 docker run --rm -v "$PWD":/src -w /src golang:1.25-alpine go test ./...
|
||||
timeout 600 docker run --rm -v "$PWD":/src -w /src golang:1.25-alpine go vet ./...
|
||||
```
|
||||
|
||||
Expected: `go test ./...` is green and `go vet ./...` is silent.
|
||||
|
||||
## Run locally
|
||||
|
||||
```sh
|
||||
go run ./cmd/email-agent
|
||||
curl -s localhost:8790/health
|
||||
```
|
||||
|
||||
The health reply is JSON: `{"status":"ok","database":"ok", ...}`.
|
||||
|
||||
## Build and deploy on .13
|
||||
|
||||
Load the host secrets, then build and start the container.
|
||||
|
||||
```sh
|
||||
set -a; . ~/.config/environment.d/10-secrets.conf; set +a
|
||||
cd /home/sam/paseo/projects/email_agent
|
||||
EMAIL_AGENT_VERSION=phase8 EMAIL_AGENT_COMMIT=$(git rev-parse --short HEAD) docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Check the result:
|
||||
|
||||
```sh
|
||||
docker ps --filter name=email-agent
|
||||
curl -s https://mail.lab.audasmedia.com.au/health
|
||||
```
|
||||
|
||||
## Check the live service
|
||||
|
||||
```sh
|
||||
curl -s http://127.0.0.1:8790/health
|
||||
curl -s http://127.0.0.1:8790/readyz
|
||||
timeout 10 docker exec email-agent email-agent -version
|
||||
timeout 10 docker exec email-agent email-agent -check-smtp
|
||||
```
|
||||
|
||||
`-check-smtp` verifies IPv4, STARTTLS, and login. It sends no mail.
|
||||
|
||||
## Trigger the jobs
|
||||
|
||||
The jobs also run from the dashboard buttons. The CLI runs them without the UI.
|
||||
|
||||
```sh
|
||||
timeout 60 docker exec email-agent email-agent -fetch
|
||||
timeout 120 docker exec email-agent email-agent -classify
|
||||
timeout 60 docker exec email-agent email-agent -summary
|
||||
```
|
||||
|
||||
`-summary-send` sends a real email. Get Sam's approval first. `-retain` clears
|
||||
old bodies and caps the log.
|
||||
|
||||
## The Hub API
|
||||
|
||||
`HUB_TOKEN` protects `/capabilities` and `/ask`. `email_agent` is registered
|
||||
with the running Hub.
|
||||
|
||||
```sh
|
||||
set -a; . ~/.config/environment.d/10-secrets.conf; set +a
|
||||
curl -s -H "Authorization: Bearer $HUB_TOKEN" http://127.0.0.1:8790/capabilities
|
||||
curl -s -H "Authorization: Bearer $HUB_TOKEN" -H 'Content-Type: application/json' \
|
||||
-d '{"question":"anything important in my inbox?","asker":"sam"}' \
|
||||
http://127.0.0.1:8790/ask
|
||||
```
|
||||
|
||||
`/ask` is read-only. It never writes to the database.
|
||||
|
||||
## Restart
|
||||
|
||||
```sh
|
||||
docker compose restart email-agent
|
||||
docker logs --tail 50 email-agent
|
||||
```
|
||||
|
||||
## Roll back
|
||||
|
||||
1. `git revert <commit> && docker compose up -d --build`.
|
||||
2. The database is on the `email_agent_data` volume. It is not changed by a code
|
||||
revert. A schema migration is forward-only; restore a database copy to go
|
||||
back a schema version.
|
||||
3. To disable the Hub API, remove `HUB_TOKEN` from the host secrets and restart.
|
||||
|
||||
## Rebuild the documentation
|
||||
|
||||
```sh
|
||||
cd /home/sam/paseo/projects/email_agent
|
||||
mmdc -i docs/email-agent-architecture.mmd -o docs/diagrams/email-agent-architecture.png --scale 2 -b white
|
||||
cd /home/sam/.agents/skills/archify
|
||||
node bin/archify.mjs validate architecture /home/sam/paseo/projects/email_agent/docs/archify/email-agent.architecture.json --quality showcase --json
|
||||
node bin/archify.mjs deliver architecture /home/sam/paseo/projects/email_agent/docs/archify/email-agent.architecture.json /home/sam/paseo/projects/email_agent/docs/email-agent-map.html --quality showcase --json
|
||||
```
|
||||
|
||||
## Publish the map
|
||||
|
||||
```sh
|
||||
~/.agents/skills/project-docs/scripts/publish-maps.sh /home/sam/paseo/projects/email_agent
|
||||
```
|
||||
|
||||
Result: `https://maps.lab.audasmedia.com.au/email_agent/docs/`.
|
||||
|
||||
## Documentation surfaces
|
||||
|
||||
| Surface | Location |
|
||||
| :--- | :--- |
|
||||
| Gitea | `ssh://git@gitea.lab.audasmedia.com.au:2222/sam/email_agent.git` |
|
||||
| Obsidian | Vault `010 inbox` on `.27` (the sorter files it later). |
|
||||
| Outline | Collection `Email Agent` on `outline.lab.audasmedia.com.au`. |
|
||||
| Vikunja | Project `Email Agent` on `vikunja.lab.audasmedia.com.au`. |
|
||||
| Map | `central_ai_llm_master/docs/hub-architecture-map.html` and this repo's map. |
|
||||
|
||||
## Safety rules
|
||||
|
||||
- Force IPv4 for every Gmail connection. IPv6 fails from `.13`.
|
||||
- Never commit a secret.
|
||||
- Never send a real email as a test. Ask Sam first.
|
||||
- Never follow a real unsubscribe link. Only mark and record.
|
||||
- Spam is recommended, never auto-deleted.
|
||||
- Bound every `docker run` with `timeout` and `--entrypoint sh`. A bare
|
||||
`docker run` starts the server and hangs.
|
||||
Reference in New Issue
Block a user