Gmail via Composio
Skill gmail-composio-guide — Version 1.0.0 — Kelvin / Hermes
1. Account & Health
bash -lc 'set -a; . ~/.hermes/.env; set +a; composio-session health' bash -lc 'set -a; . ~/.hermes/.env; set +a; composio-session accounts list --toolkit gmail'
2. Critical: JSON Payload Format
Always write the payload to a temp file and use $(cat /tmp/payload.json) to avoid shell escaping hell.
import subprocess, json
payload = json.dumps({"query": "in:inbox", "max_results": 10})
with open('/tmp/gmail_payload.json', 'w') as f:
f.write(payload)
cmd = "bash -lc 'set -a; . ~/.hermes/.env; set +a; composio-session tools execute GMAIL_FETCH_EMAILS --toolkit gmail --account primary-gmail --json \"$(cat /tmp/gmail_payload.json)\"'"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
3. Search & Read
Query patterns (Gmail search syntax):
in:inbox— inbox onlyis:unread— unread messagesis:starred/is:importantnewer_than:7d/older_than:7dfrom:boss@example.com— from senderto:someone@example.com— to recipientsubject:meeting— subject containshas:attachment/filename:pdflabel:work— use label ID, not display nameafter:2025/01/01 before:2025/02/01— UTC whole days
Parameters:
max_results: 1–500 per page (default is 1 — always set explicitly!)include_payload: true/false — full body + headersverbose: true/false — detailed vs optimized fetchingids_only: true/false — message IDs only (fastest)label_ids:["INBOX", "UNREAD"]— filter by system label IDspage_token: "..." — pagination
4. Default Workflow: Draft First, Send After Approval
The Hermes charter requires explicit same-conversation approval before sending.
- Create draft with
GMAIL_CREATE_EMAIL_DRAFT - Present to user in chat with draft URL
- User reviews in Gmail Drafts folder
- User says "send it" — call
GMAIL_SEND_DRAFTwith thedraft_id
Only skip the draft step for trivial internal emails where the user has pre-approved sending in the same conversation.
5. Create Draft
composio-session tools execute GMAIL_CREATE_EMAIL_DRAFT \
--toolkit gmail --account primary-gmail \
--json '{"recipient_email":"a@example.com","subject":"Subject","body":"Message","is_html":false}'
Draft parameters:
| Field | Type | Notes |
|---|---|---|
recipient_email | string | Primary To recipient (optional for drafts) |
extra_recipients | array | Additional To recipients [ ] |
cc | array | CC recipients [ ] |
bcc | array | BCC recipients [ ] |
subject | string | Optional for drafts |
body | string | Plain text or HTML |
is_html | boolean | Set true for HTML body |
thread_id | string | Reply to existing thread (leave subject empty!) |
attachment | object | Requires S3 key from prior upload |
CRITICAL: CC/BCC/Extra Recipients are arrays, not comma-separated strings.
draft_payload = {
"recipient_email": "primary@example.com",
"extra_recipients": ["jane@example.com", "John Doe <john@example.com>"],
"cc": ["cc1@example.com", "cc2@example.com"],
"bcc": ["bcc@example.com"],
"subject": "Subject",
"body": "Message body",
"is_html": False
}
Thread replies: Provide thread_id and leave subject empty to stay in the same thread. Setting a subject creates a new thread.
6. List / Send / Update / Delete Drafts
# List drafts
composio-session tools execute GMAIL_LIST_DRAFTS \
--toolkit gmail --account primary-gmail \
--json '{"max_results":10,"verbose":true}'
# Send draft (AS-IS — no recipient override)
composio-session tools execute GMAIL_SEND_DRAFT \
--toolkit gmail --account primary-gmail \
--json '{"draft_id":"r-1234567890"}'
# Update draft (replaces ALL content)
composio-session tools execute GMAIL_UPDATE_DRAFT \
--toolkit gmail --account primary-gmail \
--json '{"draft_id":"r-1234567890","recipient_email":"...","subject":"...","body":"..."}'
# Delete draft (permanent)
composio-session tools execute GMAIL_DELETE_DRAFT \
--toolkit gmail --account primary-gmail \
--json '{"draft_id":"r-1234567890","user_id":"me"}'
7. Send Directly (Bypass Draft)
composio-session tools execute GMAIL_SEND_EMAIL \
--toolkit gmail --account primary-gmail \
--json '{"recipient_email":"a@example.com","subject":"Subject","body":"Message"}'
Only use when user has explicitly pre-approved sending, or for trivial/internal emails.
8. Replies & Reply-All
Composio has no native reply/reply-all tool. You must manually fetch headers and construct the draft.
Step 1: Fetch Original Message Headers
composio-session tools execute GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID \
--toolkit gmail --account primary-gmail \
--json '{"message_id":"19b11732c1b578fd","format":"metadata"}'
Step 2: Parse Headers (Python)
def extract_headers(msg_data):
headers = {h["name"].lower(): h["value"] for h in msg_data["payload"]["headers"]}
return {
"from": headers.get("from"),
"to": headers.get("to"),
"cc": headers.get("cc", ""),
"subject": headers.get("subject"),
"thread_id": msg_data.get("threadId", ""),
"message_id": headers.get("message-id", "")
}
def parse_emails(header_value):
if not header_value: return []
import re
pattern = r'(?:[^<>,]*?)\s*<([^>]+)>|([^\s<>,]+@[^\s<>,]+)'
matches = re.findall(pattern, header_value)
return [m[0] if m[0] else m[1] for m in matches]
Step 3: Build Reply-All Draft
from_email = "kelvin@iimmpact.com"
original_sender = parse_emails(headers["from"])[0]
original_to = parse_emails(headers["to"])
original_cc = parse_emails(headers["cc"])
reply_to = [original_sender]
reply_to += [e for e in original_to if e.lower() != from_email.lower()]
reply_cc = [e for e in original_cc if e.lower() != from_email.lower()]
reply_all_draft = {
"thread_id": headers["thread_id"],
"recipient_email": reply_to[0],
"extra_recipients": reply_to[1:] if len(reply_to) > 1 else [],
"cc": reply_cc,
"subject": "", # Leave empty to stay in same thread
"body": "Reply-all text here",
"is_html": False
}
Important: Leave
subject empty when using thread_id. Setting a subject creates a new thread. Threading is handled by thread_id — Gmail auto-sets In-Reply-To/References.
9. HTML Emails
html_draft = {
"recipient_email": "a@example.com",
"subject": "Subject",
"body": "<p>Hello,</p><ol><li>First</li><li>Second</li></ol>...",
"is_html": True
}
10. Multi-Account / Profile Switching
# Authorize multiple accounts composio-session authorize gmail --alias primary-gmail --set-default composio-session authorize gmail --alias secondary-gmail # Set default composio-session account default --toolkit gmail --account secondary-gmail # Rename alias composio-session account rename --toolkit gmail --from primary-gmail --to iimmpact-gmail
Critical finding (tested 2026-05-23):
Test: Draft created with
Don't rely on display_url for multi-account. Tell the user which account alias was used.
display_url always points to mail.google.com/mail/u/0/#... — the first browser account, regardless of which --account you used.Test: Draft created with
--account secondary-gmail → URL: u/0/#drafts/r-5855707778456175488 (NOT u/1/).Don't rely on display_url for multi-account. Tell the user which account alias was used.
11. Common Pitfalls
- Default max_results is 1 — Always set explicitly.
- Results NOT sorted by recency — Sort by
internalDateclient-side. - Payload may be null — Use
GMAIL_FETCH_MESSAGE_BY_MESSAGE_IDfor guaranteed content. - Base64url encoding — Replace
-→+,_→/, fix padding. - Label IDs vs label names —
label_idsrequires internal IDs (e.g. "Label_123"). - Date queries are UTC whole days — Adjust for timezone.
- Shell escaping JSON — Never inline JSON with quotes. Use temp file pattern.
- CC/BCC/Extra Recipients are arrays —
["a@x.com", "b@x.com"], not comma-separated strings. - Thread reply: leave subject empty — Setting a subject creates a new thread.
- GMAIL_SEND_DRAFT cannot add recipients — Draft MUST already have recipients.
- GMAIL_UPDATE_DRAFT replaces everything — Not a patch. Provide complete fields.
- Newly created drafts may not appear immediately — Allow brief delay.
- Draft IDs differ from message IDs — Draft IDs have
rprefix. Don't interchange.
12. Quick Reference Table
| gogcli Command | Composio Equivalent |
|---|---|
gog gmail search 'query' --max N | GMAIL_FETCH_EMAILS with {"query":"...","max_results":N} |
gog gmail get <messageId> | GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID with {"message_id":"...","format":"metadata"} |
gog gmail drafts create ... | GMAIL_CREATE_EMAIL_DRAFT with recipients, subject, body, is_html |
gog gmail drafts list | GMAIL_LIST_DRAFTS with max_results, verbose=true |
gog gmail drafts send <draftId> | GMAIL_SEND_DRAFT with {"draft_id":"..."} |
gog gmail drafts update <draftId> | GMAIL_UPDATE_DRAFT with draft_id + complete content |
gog gmail drafts delete <draftId> | GMAIL_DELETE_DRAFT with {"draft_id":"..."} |
gog gmail send --to X --subject Y --body Z | GMAIL_SEND_EMAIL (bypass draft — only with explicit approval) |
gog gmail send --to X --cc "a,b" | GMAIL_SEND_EMAIL with {"recipient_email":"X","cc":["a","b"]} |
gog gmail send --body-html "<p>..." | GMAIL_CREATE_EMAIL_DRAFT with {"body":"<p>...","is_html":true} |
gog gmail send --reply-to-message-id <msgId> --reply-all | Manual: fetch message → extract To/Cc/From → create draft with thread_id |
gog gmail thread get <tid> | Search by thread ID, then fetch individually |
gog gmail labels list | GMAIL_LIST_LABELS (if available) |
gog auth list | composio-session accounts list --toolkit gmail |
--client work | --account work-gmail |
--dry-run | Draft-first workflow: create draft → present → get approval → send |
13. Tool Discovery
composio-session tools search "" --toolkits gmail composio-session tools search "send" --toolkits gmail composio-session tools search "draft" --toolkits gmail composio-session tools search "label" --toolkits gmail composio-session tools search "reply" --toolkits gmail
Generated from Hermes skill gmail-composio-guide · Last updated 2026-05-23