fix: add robust draft deduplication via gmail_list_drafts lookup
The previous approach of checking for DRAFT labels within gmail_read_thread was unreliable because the Gmail API often excludes draft messages from thread reads. This caused duplicate drafts across processing runs. Now explicitly calls gmail_list_drafts at the start of Phase 1 to build a threadId blocklist, and hard-blocks draft creation for any thread already in the set.
This commit is contained in:
@@ -26,14 +26,26 @@ This skill requires the Gmail MCP tools:
|
||||
- `gmail_read_message` — read individual messages
|
||||
- `gmail_read_thread` — read full conversation threads
|
||||
- `gmail_create_draft` — create draft replies
|
||||
- `gmail_list_drafts` — list existing drafts (critical for deduplication)
|
||||
|
||||
If Google Drive MCP is available, use it to review shared documents linked in emails. If Chrome MCP is available, use it to check external tools (Recruitee, HubSpot, etc.) when email context references them.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Phase 0: Draft Deduplication Setup (MANDATORY — run this FIRST)
|
||||
|
||||
**This step is non-negotiable. Run it before reading any emails.**
|
||||
|
||||
1. Call `gmail_list_drafts` with `maxResults: 100` to fetch all existing drafts.
|
||||
2. Build a **blocklist set** of `threadId`s that already have a draft. Extract the `threadId` from each draft's message object.
|
||||
3. Keep this set in memory throughout the entire workflow.
|
||||
4. **HARD RULE: Never call `gmail_create_draft` for any threadId that appears in this blocklist.** No exceptions.
|
||||
|
||||
Why this exists: The Gmail API's `gmail_read_thread` does NOT reliably include draft messages in its response. Previous versions of this skill tried to detect existing drafts by reading threads — this failed silently, causing duplicate drafts across processing runs. The only reliable method is the explicit `gmail_list_drafts` lookup.
|
||||
|
||||
### Phase 1: Inbox Scan
|
||||
|
||||
Search for unread emails in the inbox. **For every email, read the FULL thread using `gmail_read_thread`** — never just the individual message. This is critical for two reasons: (1) you need thread context to write a good reply, and (2) you need to check whether Michael already replied or a draft already exists. For high-volume inboxes (30+ emails), work in batches of 15-20.
|
||||
Search for unread emails in the inbox. **For every email, read the FULL thread using `gmail_read_thread`** — never just the individual message. This is critical for context: you need thread history to write a good reply, and you need to check whether Michael already sent a reply. For high-volume inboxes (30+ emails), work in batches of 15-20.
|
||||
|
||||
For each email, capture:
|
||||
- **From** (name + email)
|
||||
@@ -43,7 +55,8 @@ For each email, capture:
|
||||
- **Core content** (what do they want/need?)
|
||||
- **Attachments or shared links** (Google Docs, PDFs, etc.)
|
||||
- **Whether a reply is needed**
|
||||
- **Existing replies/drafts** — check the thread for messages with SENT or DRAFT labels. If the most recent message in the thread is FROM Michael (sent) or there's already a draft reply, NO new draft is needed. Skip it entirely.
|
||||
- **Whether Michael already replied** — check the thread for sent messages FROM Michael after the incoming email. If yes, skip.
|
||||
- **Whether a draft already exists** — check the threadId against the Phase 0 blocklist. If yes, skip. Do NOT rely on DRAFT labels in the thread — they are unreliable.
|
||||
- **Language of the latest incoming message** — note the exact language (Dutch/English/other) of the most recent message sent TO Michael. This determines what language the reply must be in.
|
||||
|
||||
### Phase 2: Categorize and Prioritize
|
||||
@@ -68,11 +81,13 @@ Draft a Gmail reply for every P1 and P2 email. This is the most important part
|
||||
**Before drafting anything**, read the tone of voice reference at `references/tone-of-voice.md` (relative to this skill's directory). This contains Michael's actual writing patterns extracted from 100+ sent emails. Follow it closely.
|
||||
|
||||
**Mandatory pre-draft checks — run these for EVERY email before creating a draft:**
|
||||
1. **Has Michael already replied?** Read the full thread. If Michael's most recent message is AFTER the incoming email, do NOT draft. He already handled it.
|
||||
2. **Does a draft already exist?** Check for messages with the DRAFT label in the thread. If one exists, do NOT create another draft.
|
||||
1. **Is this threadId in the Phase 0 blocklist?** If YES → do NOT draft. A draft already exists. This check is ABSOLUTE — skip immediately, no further evaluation needed.
|
||||
2. **Has Michael already replied?** Read the full thread. If Michael's most recent message is AFTER the incoming email, do NOT draft. He already handled it.
|
||||
3. **Does this actually need a reply?** Acknowledgments ("we're on it", "thanks", "FYI"), automated notifications, and forwarded-for-info messages don't need replies. When in doubt, classify as P3 and skip.
|
||||
4. **What language was the incoming message in?** Match it exactly. If Lennert writes in Dutch, reply in Dutch. If Dan writes in English, reply in English. Never guess — look at the actual words in the message.
|
||||
|
||||
**After creating a draft**, immediately add the threadId to the blocklist so that no other part of the current run can create a second draft for the same thread.
|
||||
|
||||
**Key rules for drafting:**
|
||||
- Use `gmail_create_draft` with the correct `threadId` and `inReplyTo`/`references` headers so the draft appears in the right conversation
|
||||
- Apply the tone of voice guide for every single draft — no exceptions
|
||||
@@ -106,7 +121,7 @@ Present these to Michael after all processing is done. Ask them one at a time or
|
||||
|
||||
After everything is processed, give Michael a brief summary:
|
||||
- Total emails processed
|
||||
- How many drafts created
|
||||
- How many drafts created (and how many skipped due to existing drafts)
|
||||
- Any items flagged for follow-up
|
||||
- Anything unusual spotted in automated notifications
|
||||
|
||||
@@ -136,7 +151,7 @@ Keep it to a few sentences. He doesn't need a five-paragraph report.
|
||||
|
||||
## Re-processing
|
||||
|
||||
When Michael says "process new emails" or "check for new ones", search for unread emails that arrived after the last batch was processed. Run the same workflow. If any of the new emails are replies in threads you already drafted responses for, read the full updated thread before drafting.
|
||||
When Michael says "process new emails" or "check for new ones", search for unread emails that arrived after the last batch was processed. **Always re-run Phase 0 (gmail_list_drafts) first** to refresh the blocklist — drafts may have been created in a previous run or manually by Michael. Then run the same workflow. If any of the new emails are replies in threads you already drafted responses for, the blocklist will automatically prevent duplicate drafts.
|
||||
|
||||
## Output Format
|
||||
|
||||
@@ -144,8 +159,9 @@ For the initial inbox review (if creating a summary document rather than just dr
|
||||
|
||||
1. **Action Required** — items needing Michael's decision
|
||||
2. **Replies Drafted** — emails where drafts are ready in Gmail
|
||||
3. **FYI Items** — notable updates worth skimming
|
||||
4. **Automated Notifications** — only anomalies highlighted
|
||||
5. **Open Questions** — the Q&A list for Michael
|
||||
3. **Skipped (draft exists)** — threads where a draft was already present
|
||||
4. **FYI Items** — notable updates worth skimming
|
||||
5. **Automated Notifications** — only anomalies highlighted
|
||||
6. **Open Questions** — the Q&A list for Michael
|
||||
|
||||
But the primary output is always **Gmail drafts ready to send** — the document is secondary.
|
||||
|
||||
Reference in New Issue
Block a user