mail-agent/SPEC.md

354 lines
9.8 KiB
Markdown

# 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