Draft-and-watch email loop — closed-loop spec (approved 2026-05-11)
Why this is in the vault
Founder approved this skill build via the new HQ click-back rail on 2026-05-11 13:20 ET, with a hard constraint: the draft-and-watch email pattern must close its loops through the existing RDCO surfaces (vault, Notion task board, HQ /decisions/). No opaque side channels. Every drafted email and every parsed reply leaves a vault-canonical trace. This doc locks the spec before the build starts so the implementation can't drift.
Source priority: founder explicitly named this as the #1 L5 unlock when reviewing the Vending-Bench research brief ([[06-reference/2026-05-01-vending-bench-research-brief.md]]). The reason it's L5-grade: lets Ray drive multi-step external relationships (intro asks, follow-ups, scheduling, project negotiation) at the founder's reputation without ever taking the send-button.
Hard constraints (from founder, 2026-05-11)
- Ray never autonomously sends external email. Founder retains the send-button. Period. Memory pointer: [[~/.claude/projects/-Users-ray/memory/feedback_no_autonomous_external_email]].
- Drafts surface on
/decisions/<slug>.html, not in iMessage. The decision page IS the approval UX. - Replies parsed by Ray, routed back through canonical surfaces:
- Follow-up payload → the originating
/decisions/<slug>.html(or a new linked decision page if the reply requires a new founder call). - Ray-async work spawned by the reply → new Notion Task Board entry.
- Substance worth retaining → vault note in
03-contacts/(relationship-shifting context) or05-meetings/(multi-turn coordination).
- Follow-up payload → the originating
- No drift to opaque side channels. Every state transition is auditable from the vault.
The loop
Trigger (Ray-side):
• /curiosity surfaces a contact to reach
• /sync-contacts finds a relationship cold for >Nd
• /check-board hits a task whose unblock requires an external ask
• Founder explicitly asks Ray to draft a message
↓
Stage 1 — Draft
Ray composes draft using vault context on the recipient
(prior threads, relationship notes, ongoing project state).
Writes to ~/rdco-hq/public/decisions/<YYYY-MM-DD>-email-<recipient-slug>.html
Page renders:
• To / Subject / Body (preview)
• Rationale block: why this email, what reply Ray expects, what
action the reply unlocks
• 4-option click-back rail:
[ SEND-AS-IS ] → fires Gmail MCP send + arms watch
[ EDIT-IN-GMAIL ] → opens Gmail draft URL on iPhone; founder
edits + sends; Ray detects via watch
[ KILL ] → archive draft, no send, no watch
[ DEFER until ] → re-surface decision page on chosen date
↓
Stage 2 — Send (founder-gated)
Founder taps SEND-AS-IS → iMessage payload returns to Ray →
Ray calls Gmail MCP send_message → records {message_id, thread_id,
recipient, sent_at, originating_decision_slug} in
~/.claude/state/email-watch-ledger.json
OR
Founder taps EDIT-IN-GMAIL → opens Gmail compose URL with pre-filled
To/Subject/Body → founder edits + sends manually → Ray detects via
polling watch (Stage 3) using From=founder + recipient match.
↓
Stage 3 — Watch
Cron-fired /watch-email-replies skill (every 30m during waking
hours, every 2h overnight) queries Gmail for new messages in
threads where Ray is the originating party. For each new reply:
1. Fetch thread, isolate the new reply body
2. Classify reply intent (yes/no/info-request/scheduling/decline/etc)
3. Route per the matrix below
↓
Stage 4 — Route the reply
A. Founder-decision-needed (reply requires judgment):
→ write a new /decisions/<slug>-followup.html linked to the
originating decision page. Append to _decisions.json.
→ iMessage the founder a tight summary + decision-page link.
B. Ray-async-work-spawned (reply unblocks something Ray can run):
→ create Notion Task Board entry with the unblocked work.
→ execute next /check-board cycle.
C. Substantive-context-only (reply is informational, no action):
→ append to a vault note in 03-contacts/<recipient>.md OR
05-meetings/<date>-<topic>.md.
→ no founder ping; surfaces in next /morning-prep if relevant.
D. Cold or hostile reply:
→ archive originating decision page status = decided / outcome
= "declined" / link the cold reply.
→ iMessage the founder ("X declined; no further action") and stop.
↓
Stage 5 — Close the loop
Originating decision page status flips: open → decided (or shipped)
once the reply is processed AND any spawned follow-up either
completes or has its own open decision page.
What Ray needs to build
New skills
/draft-emailat~/.claude/skills/draft-email/SKILL.md— composes a draft + writes the decision page + drafts the Gmail compose URL. Does NOT send./watch-email-repliesat~/.claude/skills/watch-email-replies/SKILL.md— cron-fired polling against the email-watch ledger. Routes replies per Stage 4 matrix.
New state files
~/.claude/state/email-watch-ledger.json— every sent-from-loop email tracked as{message_id, thread_id, recipient, originating_decision_slug, status: awaiting | replied | timed-out, sent_at, last_polled, expected_reply_by}.
New cron entries
*/30 6-23 * * *→/watch-email-replies(every 30m during waking hours)0 0,2,4 * * *→/watch-email-replies(sparse overnight)
Skill changes downstream
/check-boardskill should be aware: tasks whose unblock requires external email get routed through/draft-emailinstead of being marked Blocked-waiting-on-founder. Add a step in/check-boardstep 4: "if blocker is 'external party A's input needed' → spawn/draft-email to:A subject:... body-context:..."./sync-contactsskill could surface 7+d cold relationships as/draft-emailcandidates (founder approval gated).
Decision-page template extension
- The existing
/decisions/<slug>.htmltemplate needs an email-draft variant that renders:- Recipient + Subject + Body preview
- Rationale block
- 4-option rail with email-specific actions (SEND-AS-IS, EDIT-IN-GMAIL, KILL, DEFER)
- SEND-AS-IS button wires up the canonical click-back URL with
DECISION:<slug>:SEND_AS_IS || (no additional context) - EDIT-IN-GMAIL button is a direct
https://mail.google.com/mail/?view=cm&to=...&su=...&body=...link - KILL fires
DECISION:<slug>:KILL || <optional reason> - DEFER fires
DECISION:<slug>:DEFER || until:<date>
Open questions to resolve during build
- Multi-thread routing: if a reply branches a thread (e.g. CC'd someone new who replies separately), how does Ray distinguish branches? Likely: index by
thread_idnot just sender; treat each new participant as a thread-fan-out that needs its own routing decision. - Stale ledger entries: if no reply after N days (recipient-dependent: a colleague might be 3d, a cold intro might be 21d), should Ray surface a re-prompt decision page? Default yes; the
expected_reply_byfield is the trigger. - First-build scope cut: start with ONE pattern — Ray drafts an outbound ask, founder taps SEND-AS-IS, Ray watches Gmail, routes reply per Stage 4 matrix. Don't build EDIT-IN-GMAIL flow in v0; founder can still manually edit in Gmail compose, but Ray's "send" path is only the click-back. v1 adds the manual-edit detection.
Acceptance criteria (founder review)
- 1 outbound ask drafted, founder approves via click-back, email sent
- Reply lands within expected window, Ray parses it, routes per Stage 4
- Originating decision page closes with outcome captured
- Ledger entry transitions cleanly through awaiting → replied → closed
- Vault note OR Notion ticket OR follow-up decision page exists for the reply, depending on Stage 4 route
- No state ends up in opaque side channels (i.e. only in Ray's session memory or Gmail itself with no vault trace)
Related
- [[06-reference/2026-05-01-vending-bench-research-brief]] — the source brief that named this as #1 L5 unlock
- [[06-reference/concepts/2026-05-11-hq-as-decision-surface-notion-as-data-store]] — the surface architecture this loop plugs into
- [[~/.claude/projects/-Users-ray/memory/feedback_no_autonomous_external_email]] — the founder-side send-button rule that makes this skill founder-gated
- [[~/.claude/projects/-Users-ray/memory/feedback_imessage_as_generative_ui_return_channel]] — the click-back wire format used in the SEND-AS-IS button
- [[01-projects/mac/2026-05-11-mac-pivot-retainer-to-info-product]] — sibling decision filed today; both validate the click-back rail end-to-end