# Outbound Email Follow-Up Tracker *Spec — February 14, 2026* *Status: Parked, ready to build* --- ## Problem Johan sends emails that require responses. If no reply comes, nobody notices until he manually remembers to check. Conversations also move to phone/WhatsApp/in-person — so "no email reply" doesn't always mean "not handled." ## Solution Triage outgoing emails the same way we triage incoming: K2.5 reads each sent email and decides if it expects a reply. If yes, track it with a deadline. If the deadline passes with no reply, push one alert to Fully. Dismiss via Fully or auto-resolve on inbound reply. ## Architecture Everything lives in MC. Fully is just the display layer. ### IMAP Changes Add Sent folder polling to existing connectors: - `tj_jongsma_me` → poll `Sent` alongside `INBOX` - `johan_jongsma_me` → poll `Sent` alongside `INBOX` Tag outgoing messages with `direction: "outbound"` in MC's message store (inbound messages get `direction: "inbound"` or remain untagged for backwards compat). ### Schema: `follow_ups` table (orchestration.db) ```sql CREATE TABLE follow_ups ( id TEXT PRIMARY KEY, -- MC message ID of the outbound email to_address TEXT NOT NULL, -- recipient email to_name TEXT, -- recipient display name subject TEXT, -- email subject sent_at TEXT NOT NULL, -- ISO timestamp deadline_hours INTEGER DEFAULT 48, -- expected response window expects_reply INTEGER DEFAULT 1, -- 1=yes, 0=no (triage decided no) reply_received INTEGER DEFAULT 0, -- auto-set on inbound match reply_message_id TEXT, -- MC message ID of the inbound reply alerted_at TEXT, -- timestamp when Fully alert was pushed (NULL = not yet) dismissed INTEGER DEFAULT 0, -- set via Fully callback dismissed_at TEXT, -- timestamp created_at TEXT DEFAULT (datetime('now')) ); ``` ### Triage (K2.5) Same triage engine, second pass for outbound messages. Prompt addition: ``` For OUTBOUND emails, determine: 1. Does this email expect a reply? (question, request, proposal, scheduling = yes. FYI, acknowledgment, thank you = no) 2. How urgent is the expected reply? - urgent: 24h (time-sensitive, medical, financial) - normal: 48h (business correspondence, requests) - low: 7d (informational, low-priority asks) 3. Output: { "expects_reply": true/false, "deadline_hours": 24|48|168 } ``` ### Alert Flow **Check runs every hour (cron or heartbeat):** ``` SELECT * FROM follow_ups WHERE expects_reply = 1 AND reply_received = 0 AND dismissed = 0 AND alerted_at IS NULL AND datetime(sent_at, '+' || deadline_hours || ' hours') < datetime('now') ``` For each result: 1. POST to Fully: `{ "message": "🟠 No reply from {to_name} re: {subject} — sent {N} days ago", "priority": "warning" }` 2. SET `alerted_at = now()` 3. **Never alert again for this follow-up** ### Auto-Resolve When an inbound email arrives, check if sender matches any active follow-up: ``` UPDATE follow_ups SET reply_received = 1, reply_message_id = ? WHERE to_address = ? AND reply_received = 0 AND dismissed = 0 ORDER BY sent_at DESC LIMIT 1 ``` Match by email address. Thread matching (In-Reply-To header) is better but address matching covers 90% of cases and is simpler. ### Fully Dismiss Callback When a follow-up alert is dismissed on Fully (long-press done or ×): Alert dashboard POSTs to MC: ``` POST /api/follow-ups/{id}/dismiss ``` MC sets `dismissed = 1, dismissed_at = now()`. This requires: 1. Follow-up alerts include the MC follow-up ID in their metadata 2. Alert dashboard `removeAlert` / done handler checks for follow-up ID and calls MC ### Auto-Expire Follow-ups that were alerted but not dismissed auto-expire after 14 days: ``` UPDATE follow_ups SET dismissed = 1 WHERE alerted_at IS NOT NULL AND dismissed = 0 AND datetime(alerted_at, '+14 days') < datetime('now') ``` Runs on the same hourly check. ## Edge Cases - **Reply via WhatsApp/phone:** Johan dismisses the Fully alert. That's the signal. - **Multiple emails to same person:** Each gets its own follow-up. Inbound reply resolves the most recent one. - **CC/BCC recipients:** Only track the TO address. CC'd people rarely owe a reply. - **Auto-replies/OOO:** Don't count as a real reply. Triage can detect these. - **Johan replies again (bump):** If Johan sends a second email to the same person on the same thread, reset the deadline on the existing follow-up. ## Implementation Order 1. Add Sent folder to IMAP connectors 2. Add `direction` field to MC message model 3. Create `follow_ups` table 4. Add outbound triage prompt to K2.5 5. Add follow-up check to hourly cron / heartbeat 6. Add auto-resolve on inbound match 7. Add dismiss callback endpoint in MC 8. Wire alert dashboard to call MC on dismiss 9. Add auto-expire cleanup ## Not In Scope (Yet) - WhatsApp outbound tracking (could add later, same pattern) - Calendar meeting follow-ups (different trigger, same tracker) - Priority escalation (if 2x deadline passes, escalate from 🟠 to 🔴)