| name | gmail |
| description | CLI for Gmail via Google's REST API — list, search, read, send, draft, label, delete. Use for all Gmail email operations. |
| last-updated | "2026-07-04T00:00:00.000Z" |
gmail
Read and send email through the gmail CLI for Gmail via Google's REST API.
All interaction goes through the gmail CLI. Authentication is via OAuth 2.0,
stored securely in ~/.gmail/token.json.
How it works
The Gmail CLI uses the Google Gmail REST API directly — no browser automation, no
IMAP/SMTP. This is more robust and reliable than browser-based approaches.
- OAuth 2.0 authentication with automatic token refresh
- REST API for all operations (list, read, send, draft, label, delete)
- macOS Keychain integration for storing sensitive credentials
- Gmail-specific features: labels (not folders), threads, stars, spam, trash
Config lives in ~/.gmail/. The OAuth token is refreshed automatically.
Prerequisites
Verify the CLI is available:
which gmail
If missing, install it editable with uv:
cd /Users/dansmart/gitsky/dotfiles/agentic/skills/gmail
uv pip install -e .
Authentication
First-time setup
-
Create a Google Cloud project:
- Go to Google Cloud Console
- Click the project dropdown at the top → New Project
- Name it (e.g., "Gmail CLI") and click Create
-
Enable the Gmail API:
- Go to APIs & Services → Library
- Search for "Gmail API"
- Click Enable (wait for it to finish)
-
Configure OAuth consent screen:
- Go to APIs & Services → OAuth consent screen
- User Type: External
- Fill in:
- App name: "Gmail CLI"
- User support email: your email
- Developer contact: your email
- Click Save and Continue
- Skip "Scopes" (click Save and Continue)
- Skip "Test users" for now (click Save and Continue)
- Click Back to Dashboard
-
Create OAuth credentials:
- Go to APIs & Services → Credentials
- Click + Create Credentials → OAuth client ID
- Application type: Desktop app
- Name: "Gmail CLI"
- Click Create
- Click Download JSON (or copy the JSON)
- Save as
~/.gmail/credentials.json
-
Login:
gmail login
This opens a browser window for OAuth consent. Select your Google account,
grant permissions, and you'll be redirected to a success page. The token is
saved to ~/.gmail/token.json (chmod 600).
If you get a 403 error: Make sure you completed step 2 (enable Gmail API)
and step 3 (OAuth consent screen). The consent screen must be saved before
credentials work.
-
Optional — store refresh token in Keychain:
security add-generic-password -s 'gmail' -a 'your-email@gmail.com' -w '<refresh-token>'
Subsequent sessions
The OAuth token is automatically refreshed. If it expires, re-run gmail login.
Read
gmail list
gmail list --limit 20
gmail list --unread
gmail list --starred
gmail list --label INBOX
gmail list --label SENT
gmail list --query "from:boss@company.com"
gmail list --query "subject:invoice"
gmail list --query "has:attachment"
gmail list --query "after:2026-01-01 before:2026-07-04"
gmail read <message-id>
gmail read --raw <message-id>
Gmail query syntax:
from:name@example.com — sender
to:name@example.com — recipient
subject:keyword — subject line
is:unread, is:starred, is:important
has:attachment, has:drive
after:YYYY/MM/DD, before:YYYY/MM/DD
label:label-name — custom labels
Unread workflow
Process unread emails one at a time:
gmail list --unread --limit 1
gmail read <message-id>
gmail label --remove UNREAD --message-id <id>
Send
gmail send --to alice@example.com --subject "Hi" --body "Quick note"
gmail send --to a@x.com --subject "Report" --body-file ./report.txt --cc boss@x.com
echo "body from stdin" | gmail send --to a@x.com --subject Hi --body-file - --confirm
--to takes a single email (Gmail API limitation for simple send)
--cc for carbon copy
- Body from
--body or --body-file (- reads stdin)
- Confirms first (prints message, waits for
y/N) unless --confirm
Always show the draft to the user and get explicit approval before sending.
Drafts
gmail draft --to alice@example.com --subject "Hi" --body "Draft message"
gmail draft --to a@x.com --subject "Report" --body-file ./draft.txt
gmail draft --list
gmail draft --list --limit 5
gmail draft --list --raw
gmail draft --show <draft-id>
gmail draft --show <draft-id> --raw
gmail draft --delete <draft-id>
Delete / Trash / Spam
gmail delete <message-id> --trash
gmail delete <message-id> --spam
gmail delete <message-id>
Labels (Gmail's folders)
Gmail uses labels instead of folders. A message can have multiple labels.
gmail label --list
gmail label --list --raw
gmail label --create "Projects/Client-X"
gmail label --add LABEL_ID --message-id <id>
gmail label --remove LABEL_ID --message-id <id>
Built-in label IDs:
INBOX, SENT, DRAFT, SPAM, TRASH
UNREAD, STARRED, IMPORTANT
Custom labels get auto-generated IDs (use gmail label --list to find them).
Archive
Archive removes the INBOX label (message stays in "All Mail"):
gmail label --remove INBOX --message-id <id>
Star / Unstar
gmail label --add STARRED --message-id <id>
gmail label --remove STARRED --message-id <id>
Output formats
Most commands support --raw for JSON output — useful for scripting:
gmail list --raw | jq '.[].id'
gmail read --raw <id> | jq '.payload.headers'
Error handling
Not authenticated — run gmail login
credentials.json not found — download from Google Cloud Console
Token expired — automatic refresh; if that fails, re-run gmail login
Gmail API error — network issue or API quota exceeded; retry
403 Error (login fails with 403)
If gmail login shows a 403 error in the redirect URL, check these:
-
Gmail API not enabled: Go to APIs & Services → Library → search "Gmail API"
→ click Enable. Wait 1-2 minutes after enabling.
-
OAuth consent screen incomplete: Go to APIs & Services → OAuth consent screen.
Make sure you clicked Save and Continue through all sections (App info, Scopes,
Test users). The status will show as "Testing" — that's fine for personal use.
-
Add yourself as a test user (if consent screen is in "Testing" mode):
- OAuth consent screen → Test users section
- Click Add Users
- Add your Gmail address
- Click Save
- Wait 1-2 minutes, then try
gmail login again
-
Wrong project selected: Make sure you're in the correct project when enabling
the API (check the project dropdown at the top matches gmail-cli-501412).
-
Billing not required: The Gmail API is free for reasonable usage — you do NOT
need to enable billing for basic send/read operations.
Etiquette & security
- Email is sensitive. Never paste message contents, addresses, or tokens into
external services, logs, or other models.
- OAuth token in
~/.gmail/token.json is chmod 600 — never commit it.
credentials.json (OAuth client secret) should also be kept private.
- Respect rate limits. Gmail API has generous quotas but don't hammer it.
- Re-confirm every send with the user before it goes out.
Differences from folder-based email
Gmail uses labels, not folders:
| Concept | Folders (IMAP/OWA) | Gmail Labels |
|---|
| Organization | One folder per message | Multiple labels per message |
| "Move" | Copy then delete | Add/remove labels |
| Archive | Move to Archive folder | Remove INBOX label |
| Delete | Move to Trash | Remove all labels → auto-trash |
This means a message can be in "Inbox" AND "Projects" AND "Important" simultaneously.
Gmail API limitations
- No attachments in this CLI version (API supports it, not implemented)
- Single recipient in
--to (use BCC/CC workarounds or send multiple)
- No HTML body — plaintext only
- No threads — messages individually (Gmail threads via
threadId not exposed)
These can be added in future versions if needed.