Python/FastAPI service that processes Gmail, classifies purchases and notifications, and feeds structured data into Current OS and other services. Deployed on Taproot via Docker.
PROJECT
Bud
Inbox intelligence
PythonFastAPIDockern8nSupabase
ABOUT
FEATURES
SHIPPED
- ✓PurchaseHandlerMAY 2026
Classifies Gmail purchase receipts and extracts structured data — merchant, amount, items. Deployed on Taproot via Docker.
IN PROGRESS
- ◎Gmail Classification
n8n workflow connects Gmail OAuth to Bud's classification endpoint. Routes notifications, receipts, and digests to appropriate handlers.
PLANNED
- ○Inbox Intelligence
Surfaces patterns across inbox data — recurring charges, unusual spend, subscription changes. Feeds summary into Current OS.
CHANGELOG
BUDfeaturebuginfrastructure
Pipeline live — Gmail trigger activated, handler resilience fixed
Features
- Gmail purchase router workflow published and active in n8n — polls inbox every minute for new emails
- Classification pipeline end-to-end: Gmail trigger → Claude Haiku classify → confidence-based routing → handler POST or Supabase log
- Read status filter set to all emails (read + unread) — prevents missed classification if email is opened before n8n polls
Bug Fixes
- Handler crashed on emails where Claude returned null for amount —
PurchaseExtraction.amountwas required, now optional email_logaudit trail never written when extraction failed — crash happened inextract()beforestore()was reached; moved email_log write before compliance checks- Handler returned 200 with error body but route still reported
status: "ok"— now returns"partial"when email is logged but no purchase row created - Deployed model mismatch:
EmailPayloadon docker-host was missingunsubscribe_linkandis_readfields — scp'd the updated model file
Infrastructure
- Lost session reconstructed from git state and uncommitted files — committed as
dab51e7 - Google Cloud OAuth, n8n Gmail credential, and workflow import confirmed already complete from prior session
- Gmail Trigger node had stale "Every Day" poll entries from JSON import — removed, set to Every Minute
- Deployment confirmed: scp to
/opt/bud/on docker-host,docker compose build && up -d
Lessons
- When deploying via scp, every changed file must be pushed — not just the files edited in the current session; a model file mismatch caused an AttributeError that only surfaced at runtime
- n8n workflow JSON imports can carry orphaned poll configuration entries that prevent activation — the
.trim()error gave no indication which node or field was the problem - Handler pipelines should write the audit log (email_log) unconditionally at the top of
store(), not after validation — a crash inextract()leaves zero trace otherwise
TODO
- Confirm end-to-end with a real purchase email landing in both
email_logandpurchasestables — fix deployed but not yet validated
BUDinfrastructureaifeature
PurchaseHandler live — Steps 2–4 complete, n8n up
Features
- PurchaseHandler local test passed — 3/3 email formats (Amazon, DoorDash, Netflix) extracted and written to Supabase
- Handler deployed on docker-host — FastAPI container running at
192.168.1.153:8001, health endpoint confirmed from network - n8n operational — CT 102 (
192.168.1.152:5678) created, Docker installed, n8n running and survives reboot - Gmail API enabled on Google Cloud project
life-dashboard— OAuth credentials next
Bug Fixes
- Claude Haiku wraps JSON extraction responses in
```json ```fences despite explicit instructions — stripped beforemodel_validate_json()inclaude_client.py
Infrastructure
- Supabase schema live —
email_logandpurchasestables with RLS, indexes, and soft delete .env.examplecommitted — documentsSUPABASE_URL,SUPABASE_SERVICE_KEY,ANTHROPIC_API_KEYservices.mdcreated at~/.claude/services.md— global inventory of cloud platforms, APIs, and self-hosted services; updated by/wrapgoing forward- n8n compose file uses
N8N_SECURE_COOKIE=false— required for HTTP access on local network
Lessons
- Smaller models (Haiku) reliably ignore "no markdown formatting" instructions — fence stripping is a required defensive layer, not optional
scpwith~fails in PowerShell; full Windows paths (C:\Users\nrisa\...) required — write files locally and copy over rather than fighting heredocs or rsync- Services inventory needs to exist before the second integration, not after — build the index early so "do I already have a GCP project?" has a real answer
TODO
- Step 5: create Google OAuth 2.0 credentials in life-dashboard, wire Gmail trigger in n8n
- Add
budto save-state project registry via/ship
BUDfeatureinfrastructureai
PurchaseHandler built — schema live, handler ready for local test
Features
- PurchaseHandler complete — extract → validate → store pipeline wired to
/api/v1/handle/purchase - Extraction prompt with three few-shot examples (Amazon order, DoorDash delivery, Netflix subscription) — Haiku model, JSON-only output
HandlerResponsereturnsemail_log_idandpurchase_idon success — n8n gets traceable IDs, not just a status stringamount > 0and non-emptyvendorvalidation gates every write — no silent bad data reaches Supabase
Infrastructure
email_logandpurchasestables live in Supabase — RLS enabled, dedup index onmessage_id, FK from purchases into email_log- Migration files in
migrations/numbered 001/002 — ordering enforces the FK dependency at run time - Sync Supabase client wrapped in
asyncio.to_thread— avoids supabase-py async API fragility across 2.x minor versions email_logrow written beforepurchasesinsert — if the purchases write fails, the audit trail exists and carries the error
Lessons
- Supabase service_role bypasses RLS by design — adding a write policy is redundant and misleading; only anon/authenticated reads need explicit policies
- supabase-py async API changed names between 2.x minor versions (
acreate_clientvscreate_async_client) — sync +asyncio.to_threadis the portable fix - Prompt templates with JSON few-shot examples can't use
.format()— JSON braces read as format variables;.replace()per slot avoids the trap - Write the audit log row first, then the derived row — partial failures are recoverable; a missing anchor row is not
TODO
- Step 2 checkpoint: venv setup,
.env, curl tests with three email formats against localhost - Steps 3–6 after checkpoint: Docker deployment on docker-host, n8n LXC, Gmail trigger workflow, end-to-end validation