Gmail Operations Skill
<!-- Updated by Claude Code - Session: Current session - 2025-11-10 --> <!-- Changes: Added references to new utility scripts 02_decode_gmail_url_id.py and 03_get_gmail_thread_id_browser.py -->This skill enables comprehensive Gmail management through the Gmail API client located at /data_sources/gmail/gmail_client.py.
★ Insight ─────────────────────────────────────
Gmail Client Architecture:
- OAuth2 Authentication - Token-based auth with auto-refresh
- Full CRUD Operations - Search, read, create drafts, send, reply, forward
- Attachment Handling - Extract attachments with Content-ID for inline images
─────────────────────────────────────────────────
Quick Start: How This Skill Works
⚠️ CRITICAL: Gmail URL IDs Don't Work!
Gmail URL format: https://mail.google.com/mail/u/0/#inbox/FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz
- The ID after
#inbox/is NOT the API message ID - You CANNOT use it directly with Gmail API
✅ How to Find Emails:
- Search by criteria (BEST): sender, subject, date, has:attachment
- Extract from browser: Use DevTools console to get real ID
- List recent emails: Browse through recent messages
📁 Default Save Location:
algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/YYYY-MM-DD/
├── 00_email_content.md # Email body and metadata
├── original_filename.html # Preserved attachment names
└── mermaid_diagram.md # Extracted diagrams (if found)
🔧 Common Operations:
Search email:
python data_sources/gmail/gmail_client.py --method search_messages \
--query "from:sender@example.com has:attachment" --limit 5
Download email with attachments:
from data_sources.gmail.gmail_client import GmailClient
import base64, os
from datetime import datetime
client = GmailClient('data_sources/gmail/credentials').authenticate()
# 1. Search for email
messages = client.search_messages("from:sender@example.com", max_results=5)
# 2. Get full message
message = client.get_message_content(messages[0]['id'], format='full')
# 3. Extract attachments
attachments = client.extract_message_attachments(message)
# 4. Download each attachment
today = datetime.now().strftime('%Y-%m-%d')
output_dir = f"algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/{today}"
os.makedirs(output_dir, exist_ok=True)
for att in attachments:
if att['attachmentId']:
att_data = client.service.users().messages().attachments().get(
userId='me', messageId=messages[0]['id'], id=att['attachmentId']
).execute()
file_data = base64.urlsafe_b64decode(att_data['data'])
with open(f"{output_dir}/{att['filename']}", 'wb') as f:
f.write(file_data)
📋 What Gets Saved:
- ✅ Full email content as markdown
- ✅ All attachments with original filenames
- ✅ Gmail URL link for reference
- ✅ Message ID and Thread ID
- ✅ Sender, recipient, date metadata
- ✅ Mermaid diagram links (if found in email body)
🚨 NOT Hardcoded:
- ❌ No hardcoded email addresses
- ❌ No hardcoded message IDs
- ❌ No hardcoded folder paths (uses dated folders)
- ✅ Dynamic search by criteria
- ✅ Automatic folder creation by date
- ✅ Preserves original attachment names
When to Use This Skill
Use this skill when:
- Search emails with advanced Gmail queries
- Read email content and extract attachments
- Download HTML attachments from specific emails
- Create and manage email drafts
- Send emails or replies
- Process email threads
- Extract inline images with proper positioning
Quick Start Checklist
When user wants to work with Gmail:
[ ] 1. Activate claude_venv (ALWAYS required)
[ ] 2. Determine operation: search, read, download, or compose
[ ] 3. For search: build query with from:/subject:/has:attachment
[ ] 4. For download: get message_id, then extract attachments
[ ] 5. Save to dated folder: Daniel_communications/emails/YYYY-MM-DD/
[ ] 6. Preserve original filenames and create 00_email_content.md
5-Second Decision Tree:
- User mentions email search? → search_messages() with query
- User wants attachments? → get_message_content() + extract + download
- User wants to compose? → create_draft() or send_message()
Practical Workflow
BEFORE any Gmail operation:
- Activate environment:
source claude_venv/bin/activate - Determine task type:
- Search →
client.search_messages(query="...") - Download →
get_message_content()+extract_message_attachments() - Compose →
create_draft()with HTML formatting
- Search →
- Handle attachments properly (use attachment API, not inline data)
- Save to dated folders (YYYY-MM-DD structure)
Example rapid application:
User: "Download attachments from email about dashboard from james@example.com"
Agent thinks:
- Search needed: from:james@example.com subject:dashboard has:attachment
- Get message_id from search results
- Extract attachments with proper API call
- Save to: algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/2025-11-11/
Authentication & Setup
Required Files
# Credentials location
/data_sources/gmail/credentials/credentials.json # OAuth2 client config
/data_sources/gmail/credentials/token.pickle # Auto-generated auth token
Environment Setup
# ALWAYS use claude_venv
source claude_venv/bin/activate
# Or use full Python path
$PROJECT_ROOT/claude_venv/bin/python
Authentication Flow
- First run triggers OAuth2 browser flow (port 8888)
- Token auto-refreshes when expired
- Token stored in
token.picklefor reuse
Core Operations
1. Search and Read Emails
Search Emails by Query
# Search by sender
python data_sources/gmail/gmail_client.py --method search_messages \
--query "from:sender@example.com" --limit 10
# Search with multiple criteria
python data_sources/gmail/gmail_client.py --method search_messages \
--query "from:client@company.com has:attachment after:2024/11/01" --limit 5
# Find unread emails with attachments
python data_sources/gmail/gmail_client.py --method search_messages \
--query "is:unread has:attachment"
Advanced Search Operators:
from:email- Sender filterto:email- Recipient filtersubject:"text"- Subject containshas:attachment- Has attachmentsis:unread- Unread messagesafter:YYYY/MM/DD- After datebefore:YYYY/MM/DD- Before datelabel:name- Specific label
Get Email Content
# Get full message with attachments info
python data_sources/gmail/gmail_client.py --method get_message_content \
--message-id "MESSAGE_ID"
Python Usage for Attachments:
from data_sources.gmail.gmail_client import GmailClient
client = GmailClient('data_sources/gmail/credentials')
client.authenticate()
# Get message
message = client.get_message_content(message_id, format='full')
# Extract text content
content = client.extract_message_text(message)
print(content['plain_text'])
print(content['html'])
# Get attachments list
attachments = client.extract_message_attachments(message)
for att in attachments:
print(f"File: {att['filename']}, Size: {att['size']}, Type: {att['mimeType']}")
2. Download Attachments
CRITICAL Pattern for Downloading Attachments:
from data_sources.gmail.gmail_client import GmailClient
import base64
import os
client = GmailClient('data_sources/gmail/credentials')
client.authenticate()
# Get full message
message = client.get_message_content(message_id, format='full')
# Extract attachments metadata
attachments = client.extract_message_attachments(message)
# Download each attachment
for attachment in attachments:
if attachment['attachmentId']:
# Download attachment data
att_data = client.service.users().messages().attachments().get(
userId='me',
messageId=message_id,
id=attachment['attachmentId']
).execute()
# Decode and save
file_data = base64.urlsafe_b64decode(att_data['data'])
# Save to appropriate folder
output_path = os.path.join(output_dir, attachment['filename'])
with open(output_path, 'wb') as f:
f.write(file_data)
print(f"Downloaded: {attachment['filename']} ({attachment['size']} bytes)")
Example: Download HTML Attachment from Specific Email
def download_email_attachments(message_id, output_dir):
"""Download all attachments from specific email"""
client = GmailClient('data_sources/gmail/credentials')
client.authenticate()
# Get message
message = client.get_message_content(message_id, format='full')
# Get subject for context
headers = message['payload']['headers']
subject = next((h['value'] for h in headers if h['name'] == 'Subject'), 'No Subject')
print(f"Processing email: {subject}")
# Extract attachments
attachments = client.extract_message_attachments(message)
# Create output directory
os.makedirs(output_dir, exist_ok=True)
downloaded = []
for att in attachments:
if att['attachmentId']:
# Download attachment
att_data = client.service.users().messages().attachments().get(
userId='me',
messageId=message_id,
id=att['attachmentId']
).execute()
# Decode and save
file_data = base64.urlsafe_b64decode(att_data['data'])
output_path = os.path.join(output_dir, att['filename'])
with open(output_path, 'wb') as f:
f.write(file_data)
downloaded.append({
'filename': att['filename'],
'path': output_path,
'size': att['size'],
'type': att['mimeType']
})
print(f"✓ Downloaded: {att['filename']}")
return downloaded, subject
# Usage
message_id = "FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz"
output_dir = "algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/2025-11-10"
attachments, subject = download_email_attachments(message_id, output_dir)
3. Extract Inline Images
CRITICAL: Inline Image Processing
Emails with inline images reference them via cid: (Content-ID) in HTML. You MUST:
- Parse HTML to find
<img src="cid:...">tags - Match Content-ID from attachment headers
- Insert images at correct positions in markdown
from bs4 import BeautifulSoup
import html2text
def process_email_with_inline_images(client, message_id):
"""Extract email with inline images properly positioned"""
# Get message
message = client.get_message_content(message_id, format='full')
# Extract text content
content = client.extract_message_text(message)
# Get attachments with Content-IDs
attachments = []
def process_parts(parts):
for part in parts:
if part.get('filename'):
# Get Content-ID from headers
content_id = None
if 'headers' in part:
for header in part['headers']:
if header['name'].lower() == 'content-id':
content_id = header['value'].strip('<>')
break
attachments.append({
'filename': part['filename'],
'mimeType': part.get('mimeType'),
'attachmentId': part['body'].get('attachmentId'),
'content_id': content_id
})
if 'parts' in part:
process_parts(part['parts'])
if 'payload' in message and 'parts' in message['payload']:
process_parts(message['payload']['parts'])
# Process HTML to insert images
if content['html']:
soup = BeautifulSoup(content['html'], 'html.parser')
# Create CID map
cid_map = {att['content_id']: att for att in attachments if att.get('content_id')}
# Replace cid: references
for img in soup.find_all('img'):
src = img.get('src', '')
if src.startswith('cid:'):
cid = src[4:]
if cid in cid_map:
# Download and save image
att_data = client.service.users().messages().attachments().get(
userId='me',
messageId=message_id,
id=cid_map[cid]['attachmentId']
).execute()
# Save image
file_data = base64.urlsafe_b64decode(att_data['data'])
img_path = f"images/{cid_map[cid]['filename']}"
os.makedirs('images', exist_ok=True)
with open(img_path, 'wb') as f:
f.write(file_data)
# Replace with markdown
img.replace_with(f"\n\n![{cid_map[cid]['filename']}]({img_path})\n\n")
# Convert to markdown preserving links
h = html2text.HTML2Text()
h.body_width = 0
h.ignore_links = False
h.protect_links = True
markdown = h.handle(str(soup))
return markdown, attachments
return content['plain_text'], attachments
4. Create and Send Drafts
Create Plain Text Draft
python data_sources/gmail/gmail_client.py --method create_draft \
--to "recipient@email.com" \
--subject "Meeting Follow-up" \
--body "Thank you for the meeting today..."
Create HTML Draft (Python Required)
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from data_sources.gmail.gmail_client import GmailClient
import base64
client = GmailClient('data_sources/gmail/credentials')
client.authenticate()
# Create HTML message
message = MIMEMultipart('alternative')
message['To'] = "recipient@example.com"
message['Subject'] = "Dashboard Review: Q4 Results"
html_body = """
<html><body>
<p>Hi Team,</p>
<h3>📊 Key Metrics:</h3>
<ul>
<li><strong>Revenue:</strong> $1.2M (+15% MoM)</li>
<li><strong>Active Users:</strong> 45K (+8% MoM)</li>
</ul>
<h3>🎯 Action Items:</h3>
<ol>
<li><strong>This week:</strong> Review Q4 dashboard</li>
<li><strong>Next week:</strong> Finalize budget allocation</li>
</ol>
<p>Best regards,<br>Daniel</p>
</body></html>
"""
html_part = MIMEText(html_body, 'html')
message.attach(html_part)
# Create draft
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
draft_body = {'message': {'raw': raw_message}}
draft = client.service.users().drafts().create(
userId='me',
body=draft_body
).execute()
print(f"Draft created: {draft['id']}")
print(f"URL: https://mail.google.com/mail/u/0/#drafts/{draft['id']}")
Send Draft
python data_sources/gmail/gmail_client.py --method send_draft \
--draft-id "DRAFT_ID"
5. Thread Operations
Get Thread ID from Message ID
# Message ID from Gmail URL format
python data_sources/gmail/gmail_client.py --method get_thread_id \
--message-id "MESSAGE_ID"
Get All Messages in Thread
python data_sources/gmail/gmail_client.py --method get_thread_messages \
--thread-id "THREAD_ID"
6. Reply and Forward
Reply to Message
python data_sources/gmail/gmail_client.py --method reply_to_message \
--message-id "MESSAGE_ID" \
--body "Thank you for your email. Here's my response..."
Forward Message
python data_sources/gmail/gmail_client.py --method forward_message \
--message-id "MESSAGE_ID" \
--to "colleague@example.com"
Common Workflows
Workflow 1: Download Email with Attachments
Task: Download specific email with HTML attachment and save to dated folder
import os
from datetime import datetime
from data_sources.gmail.gmail_client import GmailClient
import base64
def download_email_complete(message_id, base_dir):
"""Download email content and all attachments"""
client = GmailClient('data_sources/gmail/credentials')
client.authenticate()
# Get message
message = client.get_message_content(message_id, format='full')
# Extract headers
headers = message['payload']['headers']
subject = next((h['value'] for h in headers if h['name'] == 'Subject'), 'No Subject')
from_email = next((h['value'] for h in headers if h['name'] == 'From'), 'Unknown')
date = next((h['value'] for h in headers if h['name'] == 'Date'), 'Unknown')
# Create dated directory
today = datetime.now().strftime('%Y-%m-%d')
output_dir = os.path.join(base_dir, today)
os.makedirs(output_dir, exist_ok=True)
# Save email content
content = client.extract_message_text(message)
email_md = f"""# Email: {subject}
**From:** {from_email}
**Date:** {date}
**Message ID:** {message_id}
## Content
{content['plain_text'] or '(HTML only - see attachment)'}
## Attachments
"""
# Download attachments
attachments = client.extract_message_attachments(message)
for att in attachments:
if att['attachmentId']:
# Download
att_data = client.service.users().messages().attachments().get(
userId='me',
messageId=message_id,
id=att['attachmentId']
).execute()
# Save
file_data = base64.urlsafe_b64decode(att_data['data'])
att_path = os.path.join(output_dir, att['filename'])
with open(att_path, 'wb') as f:
f.write(file_data)
email_md += f"- [{att['filename']}](./{att['filename']}) ({att['size']} bytes, {att['mimeType']})\n"
# Save email metadata
md_path = os.path.join(output_dir, '00_email_content.md')
with open(md_path, 'w') as f:
f.write(email_md)
print(f"✓ Email saved to: {output_dir}")
print(f"✓ Attachments: {len(attachments)}")
return output_dir
# Usage
message_id = "FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz"
base_dir = "algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails"
output_dir = download_email_complete(message_id, base_dir)
Workflow 2: Extract Mermaid Diagram from HTML
Task: Extract Mermaid diagram code from HTML attachment
from bs4 import BeautifulSoup
import re
def extract_mermaid_from_html(html_path):
"""Extract Mermaid diagram code from HTML file"""
with open(html_path, 'r', encoding='utf-8') as f:
html_content = f.read()
soup = BeautifulSoup(html_content, 'html.parser')
# Find mermaid code blocks
mermaid_blocks = []
# Method 1: Look for <div class="mermaid">
for div in soup.find_all('div', class_='mermaid'):
mermaid_blocks.append(div.get_text().strip())
# Method 2: Look for <pre><code class="language-mermaid">
for code in soup.find_all('code', class_='language-mermaid'):
mermaid_blocks.append(code.get_text().strip())
# Method 3: Look for script tags with mermaid content
for script in soup.find_all('script'):
script_text = script.get_text()
if 'mermaid' in script_text.lower():
# Try to extract diagram definition
matches = re.findall(r'(?:graph|flowchart|sequenceDiagram|classDiagram)[^`]*', script_text)
mermaid_blocks.extend(matches)
return mermaid_blocks
# Usage
html_file = "algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/2025-11-10/diagram.html"
diagrams = extract_mermaid_from_html(html_file)
# Save to markdown
md_output = "algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/emails/2025-11-10/diagram.md"
with open(md_output, 'w') as f:
f.write("# Mermaid Diagrams\n\n")
for i, diagram in enumerate(diagrams, 1):
f.write(f"## Diagram {i}\n\n")
f.write("```mermaid\n")
f.write(diagram)
f.write("\n```\n\n")
print(f"✓ Extracted {len(diagrams)} diagrams to {md_output}")
Daniel Personal Communications Folder Structure
CRITICAL: Folder Organization
algorithms/A8_G&A_div/Daniel Personal/Daniel_communications/
├── emails/
│ └── YYYY-MM-DD/ # Dated folders for emails
│ ├── 00_email_content.md
│ ├── attachment.html
│ └── diagram.md
├── calls/
│ └── YYYY-MM-DD/
└── meetings/
└── YYYY-MM-DD/
Naming Rules:
- Use date format:
YYYY-MM-DD(e.g.,2025-11-10) - Email summary:
00_email_content.md - Preserve original attachment filenames
- Extract diagrams to separate
.mdfiles
Best Practices
1. Always Use Existing Client
- NEVER create custom Gmail scripts
- ALWAYS use
/data_sources/gmail/gmail_client.py - Reuse examples from existing folders
2. Environment
- ALWAYS activate
claude_venvfirst - Check authentication before operations
- Token auto-refreshes when expired
3. Attachment Handling
- Download attachments with proper error handling
- Preserve original filenames
- Save to dated folders in appropriate locations
- Extract inline images at correct positions
4. HTML Processing
- Use
BeautifulSoupfor HTML parsing - Use
html2textfor markdown conversion - Preserve all links with
protect_links=True - Extract Mermaid diagrams separately
5. Client Communications
- CRITICAL: Verify client isolation (no cross-contamination)
- Use proper folder structure
- Include email metadata in saved files
- Link to original Gmail URL when useful
Reference: Gmail Client Methods
Search & Read
list_messages(max_results, query)- List messages with optional filtersearch_messages(search_query, max_results)- Search with queryget_message_content(message_id, format)- Get full messageextract_message_text(message)- Extract plain text and HTMLextract_message_attachments(message)- Get attachment metadata
Drafts
get_drafts(max_results)- List draftscreate_draft(to_email, subject, body, from_email)- Create plain text draftupdate_draft(draft_id, to_email, subject, body)- Update draftsend_draft(draft_id)- Send draftdelete_draft(draft_id)- Delete draft
Threads
list_threads(max_results, query)- List threadsget_thread_messages(thread_id)- Get all messages in threadget_thread_id(message_id)- Get thread ID from message
Actions
send_message(to_email, subject, body, attachments)- Send new emailreply_to_message(message_id, body)- Reply to messageforward_message(message_id, to_email)- Forward messagemark_as_read(message_id)- Mark as readmark_as_unread(message_id)- Mark as unread
Troubleshooting
Authentication Issues
# Delete token to re-authenticate
rm data_sources/gmail/credentials/token.pickle
# Run any command to trigger re-auth
python data_sources/gmail/gmail_client.py --method list_messages --limit 1
Message ID from Gmail URL
CRITICAL: Gmail URL IDs (like FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz) are NOT the same as API message IDs!
Problem:
URL: https://mail.google.com/mail/u/0/#inbox/FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz
URL ID: FMfcgzQcqbWzsKXlVrtllTxwjvvKdPsz ❌ DOES NOT WORK with Gmail API
Utility Scripts Available:
/data_sources/gmail/02_decode_gmail_url_id.py- Attempts to decode Gmail URL IDs (limited success)/data_sources/gmail/03_get_gmail_thread_id_browser.py- Extracts thread ID using Safari/AppleScript automation
Solutions:
-
Search by criteria (RECOMMENDED):
# Search by subject python data_sources/gmail/gmail_client.py --method search_messages \ --query "subject:\"keyword from email\"" --limit 5 # Search by sender and date python data_sources/gmail/gmail_client.py --method search_messages \ --query "from:sender@example.com after:2025/11/10" --limit 10 # Search by attachment type python data_sources/gmail/gmail_client.py --method search_messages \ --query "has:attachment filename:html" --limit 5 -
Extract from browser (if email is open):
Manual Method:
- Open email in Gmail
- Open DevTools (F12)
- Run in Console:
document.querySelector('[data-legacy-thread-id]').getAttribute('data-legacy-thread-id')
Automated Method (macOS):
# Uses AppleScript to automate Safari and extract thread ID python data_sources/gmail/03_get_gmail_thread_id_browser.py- Opens URL in Safari
- Extracts thread ID automatically
- Falls back to search if extraction fails
-
List recent emails and find manually:
python data_sources/gmail/gmail_client.py --method list_messages --limit 20
Best Practice: Always search by email attributes (sender, subject, date) rather than trying to use URL IDs.
HTML Attachment Not Downloading
- Check
attachmentIdis present - Verify attachment type is correct
- Use proper base64 decoding:
base64.urlsafe_b64decode()
Inline Images Not Showing
- Check for
cid:references in HTML - Extract Content-ID from attachment headers
- Download images before converting HTML to markdown
Quick Reference Commands
# Search emails
python data_sources/gmail/gmail_client.py --method search_messages \
--query "QUERY" --limit N
# Get email content
python data_sources/gmail/gmail_client.py --method get_message_content \
--message-id "ID"
# Create draft
python data_sources/gmail/gmail_client.py --method create_draft \
--to "email" --subject "subject" --body "body"
# List drafts
python data_sources/gmail/gmail_client.py --method get_drafts
# Send draft
python data_sources/gmail/gmail_client.py --method send_draft \
--draft-id "ID"
Remember: Gmail operations are critical for client communication. Always verify folder structure, preserve original content, and ensure proper client data isolation.