# Mail Agent Specification ## Overview IMAP-based email triage agent with multi-tier escalation. Runs as a service, processes incoming mail, auto-handles obvious cases, escalates uncertain ones. ## Architecture ``` New Mail (IMAP IDLE push) │ ▼ ┌─────────────────────────┐ │ L1: Cheap Model │ Fireworks llama-v3p1-8b-instruct │ - Spam → delete │ │ - Newsletter → archive │ │ - Receipt → archive │ │ - Obvious junk → gone │ │ - Uncertain → L2 │ └─────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ L2: James (Opus) │ via Gateway │ - Review context │ │ - Draft reply? │ │ - Handle or escalate │ └─────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ L3: Johan │ Signal notification │ - Important stuff │ │ - Needs human decision │ └─────────────────────────┘ ``` ## Accounts (Multi-account support) First account: - **ID:** proton - **Host:** 127.0.0.1 - **IMAP Port:** 1143 - **Username:** tj@jongsma.me - **Password:** BlcMCKtNDfqv0cq1LmGR9g - **TLS:** STARTTLS ## REST API ### Accounts ``` GET /accounts # List all configured accounts POST /accounts # Add account DELETE /accounts/{id} # Remove account ``` ### Mailboxes ``` GET /accounts/{id}/mailboxes # List folders ``` ### Messages ``` GET /accounts/{id}/messages ?folder=INBOX &unread=true &from=sender@example.com # Search by sender &limit=50 &offset=0 GET /accounts/{id}/messages/{uid}?folder=INBOX # Returns full message with body, attachments list PATCH /accounts/{id}/messages/{uid}?folder=INBOX Body: {"seen": true} # Mark as read Body: {"seen": false} # Mark as unread Body: {"folder": "Archive"} # Move to folder DELETE /accounts/{id}/messages/{uid}?folder=INBOX # Delete message ``` ### Actions ``` POST /accounts/{id}/unsubscribe/{uid}?folder=INBOX # Find unsubscribe link in email, execute it ``` ### Push Events (IMAP IDLE) ``` GET /accounts/{id}/events?folder=INBOX # SSE stream, emits on new mail: # data: {"type": "new", "uid": 123, "from": "...", "subject": "..."} ``` ## Triage Pipeline ### L1 Triage (Cheap Model) - **Model:** Fireworks `accounts/fireworks/models/llama-v3p1-8b-instruct` - **Cost:** ~$0.20/1M tokens (very cheap) **L1 Prompt:** ``` Classify this email. Respond with JSON only. From: {from} Subject: {subject} Preview: {first 500 chars} Categories: - spam: Obvious spam, phishing, scams - newsletter: Marketing, newsletters, promotions - receipt: Order confirmations, invoices (not shipping) - shipping: Shipping/delivery updates (picked up, in transit, delivered) - notification: Automated notifications (GitHub, services) - personal: From a real person, needs attention - important: Urgent, financial, legal, medical - uncertain: Not sure, needs human review For shipping emails, also extract: - carrier: UPS, FedEx, USPS, DHL, etc. - status: ordered, picked_up, in_transit, out_for_delivery, delivered - item: Brief description of what's being shipped - expected_date: Expected delivery date (if available) Response format: {"category": "...", "confidence": 0.0-1.0, "reason": "brief reason"} For shipping: {"category": "shipping", "confidence": 0.9, "reason": "...", "shipping": {"carrier": "UPS", "status": "picked_up", "item": "E3-1275 Server", "expected_date": "2026-02-03"}} ``` **L1 Actions:** | Category | Confidence > 0.8 | Confidence < 0.8 | |----------|------------------|------------------| | spam | Delete | → L2 | | newsletter | Archive | → L2 | | receipt | Archive + label | → L2 | | shipping | → Dashboard + Archive | → L2 | | notification | Archive | → L2 | | personal | → L2 | → L2 | | important | → L2 + flag | → L2 + flag | | uncertain | → L2 | → L2 | ### Shipping Dashboard Integration **Dashboard API:** http://100.123.216.65:9200 (James Dashboard) When shipping email detected: 1. POST to `/api/news` with shipping update 2. Archive the email 3. Track status in local state **Dashboard payload:** ```json POST /api/news { "title": "📦 E3-1275 Server", "body": "Picked up by UPS. Expected Feb 3rd.", "type": "info", "source": "shipping", "url": null } ``` **Status progression:** - `ordered` → "Order confirmed" - `picked_up` → "Picked up by {carrier}" - `in_transit` → "In transit" - `out_for_delivery` → "Out for delivery" - `delivered` → "Delivered ✓" **Auto-cleanup:** - When status = `delivered`, set a flag - Next day, DELETE the news item from dashboard - Track shipments in local JSON: `~/.config/mail-agent/shipments.json` ### L2 Triage (James/Opus) - Receives escalated emails via Gateway hook - Full context review - Can: archive, delete, draft reply, escalate to Johan ### L3 Escalation (Johan) - Uses existing Clawdbot Signal integration (no hardcoded number needed) - POST to gateway, let it route to Johan via Signal - Summary of what needs attention - Only for actually important stuff **Escalation via Gateway:** ``` POST to gateway → routes to Johan's Signal via existing channel config ``` ## Configuration ```yaml # config.yaml server: host: 127.0.0.1 port: 8025 accounts: proton: host: 127.0.0.1 port: 1143 username: tj@jongsma.me password: BlcMCKtNDfqv0cq1LmGR9g tls: starttls folders: watch: [INBOX] archive: Archive spam: Spam triage: enabled: true l1: provider: fireworks model: accounts/fireworks/models/llama-v3p1-8b-instruct api_key: ${FIREWORKS_API_KEY} l2: gateway_url: http://localhost:18080 # or however gateway is reached # Hook mechanism TBD l3: gateway_url: http://localhost:18080 # Uses existing Signal integration shipping: dashboard_url: http://100.123.216.65:9200 auto_cleanup_days: 1 # Remove from dashboard 1 day after delivered rules: always_escalate_from: - "*@inou.com" - "*@kaseya.com" auto_archive_from: - "*@github.com" - "noreply@*" auto_delete_from: - known-spam-domains.txt ``` ## Tech Stack - **Language:** Python 3.11+ - **Framework:** FastAPI - **IMAP:** imapclient (IDLE support) - **Async:** asyncio + anyio - **LLM:** httpx for Fireworks API ## Files Structure ``` mail-agent/ ├── SPEC.md # This file ├── README.md # Usage docs ├── config.yaml # Configuration ├── requirements.txt # Dependencies ├── src/ │ ├── __init__.py │ ├── main.py # FastAPI app entry │ ├── config.py # Config loading │ ├── models.py # Pydantic models │ ├── imap/ │ │ ├── __init__.py │ │ ├── client.py # IMAP connection │ │ ├── idle.py # IDLE push handler │ │ └── parser.py # Email parsing │ ├── api/ │ │ ├── __init__.py │ │ ├── accounts.py # Account endpoints │ │ ├── messages.py # Message endpoints │ │ └── events.py # SSE endpoint │ ├── triage/ │ │ ├── __init__.py │ │ ├── l1.py # L1 cheap model triage │ │ ├── l2.py # L2 escalation to James │ │ └── l3.py # L3 escalation to Johan │ └── actions/ │ ├── __init__.py │ └── unsubscribe.py # Unsubscribe handler ├── systemd/ │ └── mail-agent.service └── tests/ └── ... ``` ## Systemd Service ```ini [Unit] Description=Mail Agent After=network.target protonmail-bridge.service [Service] Type=simple User=johan WorkingDirectory=/home/johan/dev/mail-agent ExecStart=/home/johan/dev/mail-agent/.venv/bin/python -m src.main Restart=always RestartSec=10 Environment=FIREWORKS_API_KEY=... [Install] WantedBy=default.target ``` ## Environment Variables - `FIREWORKS_API_KEY` — For L1 model - `MAIL_AGENT_CONFIG` — Path to config.yaml (default: ./config.yaml) ## Shipment Tracking State Stored in `~/.config/mail-agent/shipments.json`: ```json { "shipments": [ { "id": "ups-1234567890", "carrier": "UPS", "item": "E3-1275 Server", "status": "picked_up", "expected_date": "2026-02-03", "dashboard_news_id": "abc123", "last_updated": "2026-01-30T22:00:00Z", "delivered_at": null } ] } ``` - On new shipping email: upsert shipment, update dashboard - On delivered: set `delivered_at`, schedule cleanup - Cleanup job: delete from dashboard after 1 day ## First Account (Pre-configured) The Proton Bridge account is already running: - Service: `systemctl --user status protonmail-bridge` - IMAP: 127.0.0.1:1143 - Account: tj@jongsma.me - Bridge password: BlcMCKtNDfqv0cq1LmGR9g ## Open Questions 1. **Gateway hook mechanism:** How does L2/L3 escalation reach James/Johan? POST to gateway? Will check gateway code for webhook/hook endpoint. 2. ~~**Johan's Signal number:**~~ RESOLVED: Use existing Clawdbot Signal integration via gateway 3. ~~**Fireworks API key:**~~ RESOLVED: Available in env ## Build Checklist - [ ] Project scaffold - [ ] Config loading - [ ] IMAP client with IDLE - [ ] REST API endpoints - [ ] L1 triage with Fireworks - [ ] Shipping detection + dashboard integration - [ ] Shipment tracking state + auto-cleanup - [ ] L2 escalation hook (gateway) - [ ] L3 escalation via gateway → Signal - [ ] Systemd service - [ ] Test with Proton account - [ ] README documentation