5.0 KiB
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→ pollSentalongsideINBOXjohan_jongsma_me→ pollSentalongsideINBOX
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)
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:
- POST to Fully:
{ "message": "🟠 No reply from {to_name} re: {subject} — sent {N} days ago", "priority": "warning" } - SET
alerted_at = now() - 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:
- Follow-up alerts include the MC follow-up ID in their metadata
- 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
- Add Sent folder to IMAP connectors
- Add
directionfield to MC message model - Create
follow_upstable - Add outbound triage prompt to K2.5
- Add follow-up check to hourly cron / heartbeat
- Add auto-resolve on inbound match
- Add dismiss callback endpoint in MC
- Wire alert dashboard to call MC on dismiss
- 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 🔴)