openclaw/skills/gws-gmail/SKILL.md
Read, search, send, and manage Gmail messages for Dylan, Julia, or other accounts. Use when asked about email, inbox, unread messages, sending emails, checking mail, or Julia's email.
npx skillsauth add Dbochman/dotfiles gws-gmailInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Access Gmail via the gws CLI at /opt/homebrew/bin/gws. Credentials are AES-256-GCM encrypted at ~/.config/gws/.
| Account | Owner | Flag |
|---------|-------|------|
| [email protected] | Dylan | Default (no flag needed) |
| [email protected] | Julia | --account [email protected] |
| [email protected] | Dylan (spam) | --account [email protected] |
| [email protected] | OpenClaw | --account [email protected] |
When Dylan asks about "my email", use default. When he says "Julia's email", use her account.
All commands follow: gws gmail <resource> <method> [--params '<JSON>'] [--json '<JSON>'] [--account <email>]
--params = URL/query parameters (userId, q, maxResults, etc.)--json = request body (for send, modify, etc.)--account = target accountImportant: Most Gmail endpoints require "userId": "me" in params.
# Unread messages
gws gmail users messages list --params '{
"userId": "me",
"q": "is:unread",
"maxResults": 10
}'
# From a specific sender
gws gmail users messages list --params '{
"userId": "me",
"q": "from:[email protected]"
}'
# With attachments, recent
gws gmail users messages list --params '{
"userId": "me",
"q": "has:attachment newer_than:7d"
}'
# Julia's unread
gws gmail users messages list --params '{
"userId": "me",
"q": "is:unread",
"maxResults": 10
}' --account [email protected]
| Query | Meaning |
|-------|---------|
| is:unread | Unread messages |
| is:starred | Starred messages |
| is:important | Important messages |
| in:inbox | Messages in inbox |
| in:sent | Sent messages |
| from:[email protected] | From specific sender |
| to:[email protected] | To specific recipient |
| subject:keyword | Subject contains keyword |
| has:attachment | Has attachments |
| filename:pdf | Has PDF attachment |
| after:2026/01/01 | After date |
| before:2026/02/01 | Before date |
| newer_than:7d | Newer than 7 days |
| older_than:1m | Older than 1 month |
| label:work | Has label |
| category:promotions | In category |
Combine queries: from:[email protected] is:unread after:2026/01/01
# Full message with body
gws gmail users messages get --params '{
"userId": "me",
"id": "<messageId>",
"format": "full"
}'
# Metadata only (headers)
gws gmail users messages get --params '{
"userId": "me",
"id": "<messageId>",
"format": "metadata",
"metadataHeaders": ["From", "Subject", "Date", "To"]
}'
The metadata response does NOT put headers at the top level. They're nested under payload.headers as an array of {name, value} objects:
{
"id": "...",
"internalDate": "1777719291000",
"labelIds": ["INBOX", ...],
"payload": {
"headers": [
{"name": "From", "value": "..."},
{"name": "Subject", "value": "..."},
{"name": "Date", "value": "..."}
]
}
}
To extract them flat, pipe through jq:
gws gmail users messages get --params '{"userId":"me","id":"<id>","format":"metadata","metadataHeaders":["From","Subject","Date"]}' \
| jq '.payload.headers | map({(.name): .value}) | add'
# → {"From":"[email protected]","Subject":"...","Date":"..."}
For batch summaries (e.g., morning briefing), loop a list call's IDs through this pattern and concat — don't fall back to format: "full" snippets, which is slow and lossy.
gws gmail users threads get --params '{
"userId": "me",
"id": "<threadId>",
"format": "full"
}'
Messages must be base64url-encoded RFC 2822 format.
IMPORTANT: Use Python base64.urlsafe_b64encode to encode the raw email. Do NOT use shell printf | base64 | tr — it corrupts ! to \! and mangles special characters.
# Step 1: Build base64url payload with Python (safe for all characters)
RAW_B64=$(python3 -c "
import base64
msg = 'From: [email protected]\r\nTo: [email protected]\r\nSubject: Hello\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nMessage body here!'
print(base64.urlsafe_b64encode(msg.encode()).decode().rstrip('='))
")
# Step 2: Send via gws
gws gmail users messages send --params '{"userId": "me"}' \
--json "{\"raw\":\"${RAW_B64}\"}"
# Reply to a thread (add threadId, In-Reply-To, References headers)
RAW_B64=$(python3 -c "
import base64
msg = 'From: [email protected]\r\nTo: [email protected]\r\nSubject: Re: Original Subject\r\nIn-Reply-To: <original-message-id>\r\nReferences: <original-message-id>\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nReply body here'
print(base64.urlsafe_b64encode(msg.encode()).decode().rstrip('='))
")
gws gmail users messages send --params '{"userId": "me"}' \
--json "{\"threadId\":\"<threadId>\",\"raw\":\"${RAW_B64}\"}"
For HTML emails, use Content-Type: text/html; charset=utf-8 in the headers.
# List drafts
gws gmail users drafts list --params '{"userId": "me"}'
# Create a draft (use Python base64 pattern from Send section)
RAW_B64=$(python3 -c "
import base64
msg = 'To: [email protected]\r\nSubject: Draft\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nDraft body'
print(base64.urlsafe_b64encode(msg.encode()).decode().rstrip('='))
")
gws gmail users drafts create --params '{"userId": "me"}' \
--json "{\"message\":{\"raw\":\"${RAW_B64}\"}}"
# Send a draft
gws gmail users drafts send --params '{"userId": "me"}' --json '{
"id": "<draftId>"
}'
# Delete a draft
gws gmail users drafts delete --params '{"userId": "me", "id": "<draftId>"}'
# List all labels
gws gmail users labels list --params '{"userId": "me"}'
# Create a label
gws gmail users labels create --params '{"userId": "me"}' --json '{
"name": "OpenClaw/NewLabel",
"labelListVisibility": "labelShow",
"messageListVisibility": "show"
}'
# Get label details
gws gmail users labels get --params '{"userId": "me", "id": "<labelId>"}'
# Mark as read (remove UNREAD)
gws gmail users messages modify --params '{"userId": "me", "id": "<messageId>"}' --json '{
"removeLabelIds": ["UNREAD"]
}'
# Star a message
gws gmail users messages modify --params '{"userId": "me", "id": "<messageId>"}' --json '{
"addLabelIds": ["STARRED"]
}'
# Archive (remove from INBOX)
gws gmail users messages modify --params '{"userId": "me", "id": "<messageId>"}' --json '{
"removeLabelIds": ["INBOX"]
}'
# Apply custom label
gws gmail users messages modify --params '{"userId": "me", "id": "<messageId>"}' --json '{
"addLabelIds": ["<labelId>"]
}'
gws gmail users messages batchModify --params '{"userId": "me"}' --json '{
"ids": ["<msgId1>", "<msgId2>"],
"addLabelIds": ["STARRED"],
"removeLabelIds": ["UNREAD"]
}'
# Trash (recoverable)
gws gmail users messages trash --params '{"userId": "me", "id": "<messageId>"}'
# Permanent delete (irreversible!)
gws gmail users messages delete --params '{"userId": "me", "id": "<messageId>"}'
# List threads
gws gmail users threads list --params '{
"userId": "me",
"q": "is:unread",
"maxResults": 10
}'
# Modify thread labels
gws gmail users threads modify --params '{"userId": "me", "id": "<threadId>"}' --json '{
"addLabelIds": ["STARRED"],
"removeLabelIds": ["UNREAD"]
}'
# Trash entire thread
gws gmail users threads trash --params '{"userId": "me", "id": "<threadId>"}'
# Get attachment (returns base64 data)
gws gmail users messages attachments get --params '{
"userId": "me",
"messageId": "<messageId>",
"id": "<attachmentId>"
}'
# Vacation responder
gws gmail users settings getVacation --params '{"userId": "me"}'
# Filters
gws gmail users settings filters list --params '{"userId": "me"}'
# Forwarding
gws gmail users settings forwardingAddresses list --params '{"userId": "me"}'
# Send-as aliases
gws gmail users settings sendAs list --params '{"userId": "me"}'
For any endpoint, check available parameters:
gws schema gmail.users.messages.list
gws schema gmail.users.messages.send
gws schema gmail.users.threads.get
Julia's inbox has an automated daily briefing via cron:
| Label | Purpose |
|-------|---------|
| OpenClaw/Urgent | Time-sensitive, needs immediate attention |
| OpenClaw/Action | Requires response or action from Julia |
| OpenClaw/FYI | Informational, no action needed |
| OpenClaw/Financial | Bills, bank alerts, transactions |
| OpenClaw/Shopping | Orders, shipping, receipts |
| OpenClaw/Newsletters | Subscriptions, digests, promotions |
| OpenClaw/Social | Social media notifications, invites |
NEVER send an email unless the user has explicitly asked you to send it. Drafting an email is not permission to send it. Approving a draft is not permission to send it. The user must clearly and unambiguously instruct you to send before you call the send endpoint. When in doubt, ask. This applies to all accounts and all recipients — no exceptions.
gws CLI for Gmail — do NOT use himalaya, mutt, mail, or any other email CLI. The gws CLI handles multi-account auth and is the only supported tool.gws outputs JSON by default — parse directly or pipe through jqraw field for send/drafts uses base64url encoding — always use Python base64.urlsafe_b64encode (shell printf | base64 | tr corrupts ! and other special chars)format: "metadata" with metadataHeaders to fetch only headers (faster for triage)development
Search the web for current information, news, facts, and answers. Use when asked questions about current events, needing to look something up, finding websites, researching topics, or when you need up-to-date information beyond your training data.
development
Summarize any URL, YouTube video, podcast, PDF, or file into concise text. Use when asked to read an article, summarize a link, get the gist of a video or podcast, extract content from a URL, or when you need to understand what a web page or document contains.
development
Play music via Spotify and control Google Home speakers. Use when asked to play music, songs, artists, playlists, podcasts, or control speakers/volume/audio.
testing
Create new OpenClaw skills, modify and improve existing skills, and measure skill performance with evals. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy. Also use when asked to "make a skill", "turn this into a skill", "improve this skill", or "test this skill".