Installs into .claude/skills of the current project.
Are you the author of Gw Gmail?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/elnora-ai-gw-gmail)
---
name: gw-gmail
description: >
Send, draft, list, read, reply, scan, and manage Gmail via CLI.
TRIGGERS: "gmail", "email", "send email", "draft email", "reply email", "inbox",
"outreach", "scan replies", "mail", "compose", "trash email", "email thread"
---
# Gmail
## Invocation
```bash
CLI="python3 ${CLAUDE_PLUGIN_ROOT}/cli/gw.py"
```
## Account Routing
Accounts are user-defined in `accounts.json` (see `gw auth list` / `gw auth login --account <name>`); the names and emails below are only examples.
| Account | Example email | Typical use |
|---|---|---|
| `--account main` | `you@example.com` | Primary (default) |
| `--account work` | `work@example.com` | Work / outreach |
| `--account personal` | `personal@example.com` | Personal |
## Commands
### Send & Draft
Always use `--body-file -` with heredoc. Never `--body "..."` — special characters break shell quoting.
```bash
$CLI gmail send --to EMAIL --subject "..." --body-file - [--cc "a@x.com,b@y.com"] [--thread-id TID] [--attach FILE] [--no-signature] [--plain | --html-body HTML] [--account work] <<'BODY'
Email body here.
BODY
$CLI gmail draft --to EMAIL --subject "..." --body-file - [--cc "a@x.com,b@y.com"] [--thread-id TID] [--attach FILE] [--no-signature] [--plain | --html-body HTML] [--account work] <<'BODY'
Draft body here.
BODY
```
Multiple recipients: `--to` and `--cc` accept comma-separated addresses or repeated flags. Always comma-separate in one string for clarity: `--cc "a@x.com,b@y.com,c@z.com"`.
Signature: the account's Gmail send-as signature is appended to the HTML part by default. `--no-signature` omits it (for a body that carries its own sign-off block). The flag works on every verb that builds a message: `send`, `draft`, the four reply verbs, `update-draft` and `attach-to-draft`.
Plain text: `--plain` sends the body as a single `text/plain` part with no HTML alternative, so there is no signature and no styling, and the recipient's mail client shows it in its own default font, like a hand-typed email. Threading, To/Cc, the From display name and attachments are unchanged. Works on `send`, `draft` and the four reply verbs. Default (without the flag) is plain + HTML with the signature.
Your own HTML: `--html-body HTML` sends `HTML` verbatim as the `text/html` alternative, with the `--body` text as the `text/plain` part. It implies no signature: the send-as signature is not appended, the HTML is not wrapped or styled, and nothing in it is converted. Pass it from a file with `--html-body "$(cat /path/to/body.html)"`. Threading, To/Cc, the From display name and attachments are unchanged. Works on `send`, `draft` and the four reply verbs. `--html-body` with `--plain` is a usage error.
Response: `{"sent":true,"id":"MSG_ID","threadId":"TID"}` or `{"drafted":true,"id":"DRAFT_ID","messageId":"MSG_ID"}`
### Attachments
**The CLI fully supports file attachments.** Use `--attach` with an absolute file path. Repeat for multiple files.
```bash
# Single attachment
$CLI gmail send --to EMAIL --subject "..." --attach /path/to/file.pdf --body-file - <<'BODY'
See attached.
BODY
# Multiple attachments
$CLI gmail draft --to EMAIL --subject "..." --attach /path/to/report.pdf --attach /path/to/data.csv --body-file - <<'BODY'
Two files attached.
BODY
# Attachment on a draft reply
$CLI gmail draft-reply MESSAGE_ID --attach /path/to/file.pdf --body-file - <<'BODY'
Attaching the document you requested.
BODY
```
Works on: `send`, `draft`, `draft-reply`. Any file type. Uses MIME multipart/mixed encoding. Supports `~/` paths.
**Quote paths with spaces** (e.g. Google Drive): `--attach "/path/with spaces/file.pdf"`
### Reply
All four reply commands preserve the thread, add a `Re:` subject prefix if missing, and include your display name + signature from `sendAs` (`--no-signature` omits the signature; `--plain` sends plain text only, with no HTML and so no signature; `--html-body` sends your own HTML with no signature). **Original Cc is auto-preserved** on plain reply/draft-reply (minus your own address). Override with `--cc "a@x,b@y"` or clear with `--no-cc`.
```bash
# Plain reply (To = original sender, Cc preserved from original)
$CLI gmail reply MESSAGE_ID --body-file - [--cc "a@x,b@y"] [--no-cc] [--to EMAIL] [--attach FILE] [--no-signature] [--plain | --html-body HTML] [--account work] <<'BODY'
Reply text.
BODY
# Plain draft reply — does NOT send
$CLI gmail draft-reply MESSAGE_ID --body-file - [--cc "a@x,b@y"] [--no-cc] [--to EMAIL] [--attach FILE] [--no-signature] [--plain | --html-body HTML] [--account work] <<'BODY'
Draft reply text.
BODY
# Reply-all: To = original sender, Cc = original To + Cc, deduped, minus self
$CLI gmail reply-all MESSAGE_ID --body-file - [--cc "..."] [--no-cc] [--to EMAIL] [--attach FILE] [--no-signature] [--plain | --html-body HTML] <<'BODY'
Reply to everyone on the thread.
BODY
# Draft reply-all — does NOT send
$CLI gmail draft-reply-all MESSAGE_ID --body-file - [--cc "..."] [--no-cc] [--to EMAIL] [--attach FILE] [--no-signature] [--plain | --html-body HTML] <<'BODY'
Draft reply to everyone.
BODY
```
**Cc behavior:**
- Default (no flag): plain `reply`/`draft-reply` preserves the original Cc list. `reply-all`/`draft-reply-all` expands Cc to include original To recipients too (minus you, minus whoever's in your new To).
- `--cc "frank@w.com,gina@x.com"`: explicit override, discards original Cc.
- `--no-cc`: force Cc empty. Conflicts with `--cc`.
- `--to "dave@z.com"`: override the primary recipient. In reply-all, the original sender moves to Cc instead of being dropped.
**Self-reply (follow-up on your own sent message):** detected via the authenticated `sendAs` email. Plain reply flips To to the first original recipient. Reply-all flips To to all original recipients. Cc is preserved from the original in both modes.
### Read
```bash
$CLI gmail list [--query "is:unread"] [--limit 20] [--account main] --compact
$CLI gmail get MESSAGE_ID --compact
$CLI gmail get-thread THREAD_ID --compact
$CLI gmail scan [--since "1d"] --compact # no --limit or --query
$CLI gmail labels --compact
```
Response shapes (validated):
`list`: `{"messages":[{"id","threadId","from","to","subject","date","snippet","labelIds"}],"count","query"}`
`get`: `{"id","threadId","from","to","subject","date","snippet","labelIds","body"}`
`get-thread`: `{"messages":[...],"count","threadId"}`
`scan`: `{"messages":[...],"count","query"}`
`labels`: `{"labels":[{"id","name","type"}],"count"}`
### Manage Drafts
Full lifecycle — find, modify, send, delete:
```bash
# Find drafts — returns draftId for chaining
$CLI gmail list-drafts [--query "from:someone@example.com"] [--limit 20] --compact
# (Also works: gmail list --query "in:drafts" auto-routes to list-drafts)
# Inspect a specific draft
$CLI gmail get-draft DRAFT_ID --compact
# Attach a file to an existing draft (preserves body, subject, recipients, existing attachments).
# update-draft and attach-to-draft rebuild the message and re-append the signature: pass
# --no-signature again on a draft that was created without one. They always rebuild the
# HTML part from the plain text, so a draft created with --plain gains one and a draft
# created with --html-body loses its HTML.
$CLI gmail attach-to-draft DRAFT_ID --attach /path/to/report.pdf
# Update any field of an existing draft — omitted fields are preserved
$CLI gmail update-draft DRAFT_ID --subject "New subject"
$CLI gmail update-draft DRAFT_ID --body-file - <<'BODY'
Replacement body.
BODY
$CLI gmail update-draft DRAFT_ID --attach /path/to/file.pdf # replaces existing attachments
$CLI gmail update-draft DRAFT_ID --attach /path/to/file.pdf --append-attachments # keeps existing + adds new
$CLI gmail update-draft DRAFT_ID --no-signature # stops re-appending the send-as signature
# Send or discard
$CLI gmail send-draft DRAFT_ID [--account work]
$CLI gmail delete-draft DRAFT_ID # permanent, no trash
```
Response shapes:
- `list-drafts`: `{"drafts":[{"draftId","id","threadId","from","to","subject","date","snippet","labelIds"}],"count","query"}`
- `update-draft`: `{"updated":true,"id":"DRAFT_ID","messageId":"MSG_ID"}`
- `attach-to-draft`: `{"updated":true,"id":"DRAFT_ID","messageId":"MSG_ID","attached":N}`
- `delete-draft`: `{"deleted":true,"id":"DRAFT_ID"}`
**Canonical attach-to-existing-draft workflow:**
```bash
# Find the draft you're editing in Gmail, then attach a PDF from the terminal
DID=$($CLI gmail list-drafts --query "subject:\"Kill the Bench\"" --limit 1 --compact \
| python3 -c "import json,sys; print(json.loads(sys.stdin.read())['drafts'][0]['draftId'])")
$CLI gmail attach-to-draft "$DID" --attach ~/Downloads/report.pdf
```
### Manage Messages
```bash
$CLI gmail trash MESSAGE_ID
$CLI gmail download-attachments MESSAGE_ID --dest /path/to/dir
```
### Labels
`labels` lists them; `modify-labels` applies them. Labels are given by name or by ID — names are resolved for you, so prefer the name.
```bash
$CLI gmail modify-labels MESSAGE_ID --add "partners/acme"
$CLI gmail modify-labels THREAD_ID --add "partners/acme" --thread # whole thread
$CLI gmail modify-labels MESSAGE_ID --add "customers" --remove "INBOX" # label + archive
```
`--add`/`--remove` accept comma-separated lists. Response: `{"id","modified":true,"thread":bool,"added":[...],"removed":[...]}`
Needs the `gmail.modify` scope. A 403 "insufficient authentication scopes" means the token predates it — re-authenticate with `$CLI auth login --scopes gmail`.
## UI Verification
After `draft`, `draft-reply`, `draft-reply-all`, `update-draft`, or `attach-to-draft`, verify the draft looks correct in Gmail using Chrome DevTools MCP:
1. Open Gmail drafts:
```
mcp__chrome-devtools__navigate_page → https://mail.google.com/mail/u/0/#drafts
```
2. Click into the draft and take a screenshot. Confirm: recipients, subject, body content, and attachments are as expected.
3. If anything looks wrong, fix it with `gmail update-draft` before reporting done.
Do NOT verify sent messages — those are already out the door.
## Safety Rules
1. **Never auto-send first time.** Draft first, show the user for review.
2. **Outreach = `--account work`** always. Never cold email from `main`.
3. **Max 50 emails/hour** via `--account work`.
4. **Bulk sends:** draft first 3-5, wait for "approved", then send rest.
5. **Never delete emails.** Only trash.