重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
gmail-access by terrylica/cc-skills
npx skills add https://github.com/terrylica/cc-skills --skill gmail-access通过 Claude Code 编程方式读取和搜索 Gmail。
关键:在运行任何 Gmail 命令之前,您必须完成此预检清单。不要跳过任何步骤。
ls -la "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail" 2>/dev/null || echo "BINARY_NOT_FOUND"
如果显示 BINARY_NOT_FOUND:请先构建它:
cd ~/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli && bun install && bun run build
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
如果显示 NOT_SET:您必须运行下面的设置流程。不要继续执行 Gmail 命令。
始终验证您正在访问当前项目的正确电子邮件账户。
# 显示当前项目上下文
echo "=== Gmail 账户上下文 ==="
echo "工作目录: $(pwd)"
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID}"
# 检查 GMAIL_OP_UUID 在何处定义(mise 层次结构)
echo ""
echo "=== mise 配置来源 ==="
grep -l "GMAIL_OP_UUID" .mise.local.toml .mise.toml ~/.config/mise/config.toml 2>/dev/null || echo "在标准位置未找到"
# 快速连接测试 — 从真实邮件显示账户邮箱
echo ""
echo "=== 账户验证 ==="
$GMAIL_CLI list -n 1 2>&1 | head -5
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在继续之前停止并请用户确认:
list -n 1 的输出显示了账户的收件箱 — 请验证这与项目预期的邮箱是否匹配.mise.local.toml 设置了 GMAIL_OP_UUID# 检查缓存的令牌是否存在且未过期
TOKEN_FILE="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.json"
APP_CREDS="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.app-credentials.json"
echo "令牌文件: $([ -f "$TOKEN_FILE" ] && echo "存在" || echo "缺失")"
echo "应用凭据: $([ -f "$APP_CREDS" ] && echo "已缓存" || echo "缺失 — 首次运行将需要 1Password")"
如果令牌文件缺失:首次运行将打开浏览器进行 OAuth 授权。这是预期的。如果应用凭据缺失:将调用一次 1Password 来缓存 client_id/client_secret,之后不再调用。
按顺序执行以下步骤。在决策点使用 AskUserQuestion。
command -v op && echo "OP_CLI_INSTALLED" || echo "OP_CLI_MISSING"
如果显示 OP_CLI_MISSING:停止并通知用户:
需要 1Password CLI。请使用以下命令安装:
brew install 1password-cli
# 尝试常见保险库 — "Claude Automation" 用于服务账户,"Employee" 用于交互式账户
for VAULT in "Claude Automation" "Employee" "Personal"; do
ITEMS=$(op item list --vault "$VAULT" --format json 2>/dev/null | jq -r '.[] | select(.title | test("gmail|oauth|google"; "i")) | "\(.id)\t\(.title)"')
[ -n "$ITEMS" ] && echo "=== 保险库: $VAULT ===" && echo "$ITEMS"
done
解析输出并根据结果继续:
如果找到项目,使用 AskUserQuestion 并附带发现的项目:
AskUserQuestion({
questions: [{
question: "哪个 1Password 项目包含您的 Gmail OAuth 凭据?",
header: "Gmail OAuth",
options: [
// 从 op item list 结果填充 - 示例:
{ label: "Gmail API - dental-quizzes (56peh...)", description: "Employee 保险库中的 OAuth 客户端" },
{ label: "Gmail API - personal (abc12...)", description: "个人 OAuth 客户端" },
],
multiSelect: false
}]
})
如果未找到项目,使用 AskUserQuestion 来指导设置:
AskUserQuestion({
questions: [{
question: "在 1Password 中未找到 Gmail OAuth 凭据。您希望如何继续?",
header: "设置",
options: [
{ label: "创建新的 OAuth 凭据(推荐)", description: "我将指导您完成 Google Cloud Console 设置" },
{ label: "我在其他地方有凭据", description: "帮助我将它们添加到 1Password" },
{ label: "暂时跳过", description: "我稍后再设置" }
],
multiSelect: false
}]
})
用户选择项目(包含 UUID)后,使用 AskUserQuestion:
AskUserQuestion({
questions: [{
question: "将 GMAIL_OP_UUID 添加到当前项目的 .mise.local.toml 中吗?",
header: "配置",
options: [
{ label: "是,添加到 .mise.local.toml(推荐)", description: "创建/更新 gitignored 配置文件" },
{ label: "仅显示配置", description: "我将手动添加" }
],
multiSelect: false
}]
})
如果选择"是,添加到 .mise.local.toml":
.mise.local.toml 是否存在GMAIL_OP_UUID 追加到 [env] 部分[env]
GMAIL_OP_UUID = "<selected-uuid>"
.mise.local.toml 是否在 .gitignore 中如果选择"仅显示配置":输出 TOML 配置供用户手动添加。
mise trust 2>/dev/null || true
cd . && echo "重新加载后的 GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
如果仍显示 NOT_SET:通知用户重新启动 shell 或运行 source ~/.zshrc。
GMAIL_OP_UUID="${GMAIL_OP_UUID}" $HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail list -n 1
如果出现 OAuth 提示:首次运行时这是预期的。浏览器将打开进行 Google 授权。
GMAIL_CLI="$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail"
# 列出最近的邮件
$GMAIL_CLI list -n 10
# 搜索邮件
$GMAIL_CLI search "from:someone@example.com" -n 20
# 按日期范围搜索
$GMAIL_CLI search "from:phoebe after:2026/01/27" -n 10
# 读取特定邮件(包含完整正文)
$GMAIL_CLI read <message_id>
# 读取并下载内联图片(撰写时复制粘贴的截图)
$GMAIL_CLI read <message_id> --save-images
# 将内联图片下载到特定目录
$GMAIL_CLI read <message_id> --save-images --image-dir ./attachments/my-folder/
# 简写:--image-dir 隐含 --save-images
$GMAIL_CLI read <message_id> --image-dir ./attachments/my-folder/
# JSON 输出,包含图片元数据和保存路径
$GMAIL_CLI read <message_id> --save-images --json
# 导出为 JSON
$GMAIL_CLI export -q "label:inbox" -o emails.json -n 100
# JSON 输出(用于解析)
$GMAIL_CLI list -n 10 --json
# 创建草稿邮件
$GMAIL_CLI draft --to "user@example.com" --subject "Hello" --body "Message body"
# 创建草稿回复(并入现有对话线程)
$GMAIL_CLI draft --to "user@example.com" --subject "Re: Hello" --body "Reply text" --reply-to <message_id>
邮件通常包含复制粘贴的截图(嵌入在 HTML 正文中的内联图片,而非文件附件)。这些在纯文本中显示为 [image: image.png] 占位符,但包含可通过 Gmail API 访问的真实图片数据。
| 标志 | 效果 |
|---|---|
--save-images | 将所有内联图片下载到磁盘(默认:~/.claude/tools/gmail-images/<message_id>/) |
--image-dir <path> | 自定义输出目录(隐含 --save-images) |
| 无标志 | 显示图片元数据(数量、文件名、大小)但不下载 |
--- 内联图片 (3) ---
image.png image/png 245.3 KB
image.png image/png 512.1 KB
photo.jpg image/jpeg 89.7 KB
--- 已保存到磁盘 ---
./attachments/01_image.png (251,234 B)
./attachments/02_image.png (524,001 B)
./attachments/03_photo.jpg (91,852 B)
--- Markdown 引用 ---



has:attachment 无法找到内联图片。 Gmail 搜索没有针对内联图片的操作符。要发现包含内联图片的邮件,您必须读取邮件并检查 MIME 树。
查找包含内联图片邮件的策略:
# 按发件人/日期搜索,然后读取每个邮件以检查图片
$GMAIL_CLI search "from:sender@example.com after:2026/02/01" -n 10 --json | \
jq -r '.[].id' | while read id; do
COUNT=$($GMAIL_CLI read "$id" --json | jq '.inlineImages | length')
[ "$COUNT" -gt 0 ] && echo "$id 有 $COUNT 个内联图片"
done
从线程(多封回复邮件)下载图片时,后续回复包含所有先前的内联图片。线程中的最后一封邮件通常是超集。
建议:对于线程对话,仅从最新回复下载图片以避免重复。如果不确定,请比较文件大小。
复制粘贴的截图通常都使用通用文件名 image.png。CLI 会添加零填充的索引前缀:01_image.png、02_image.png 等。这些机器生成的名称应重命名为描述性名称以便通信归档。
当内联图片包含手写批注(截图上的圆圈、箭头、叠加的书写文本)时,执行系统的两级分析:
将批注格式化为 Markdown 中每个图片下方的块引用说明:

> **批注转录**:[视觉标记的详细描述。]
> 手写文本内容为:_"此处为精确转录"_
> [对批注者请求的解读。]
不要将批注转录推迟到第二次处理。 在首次检查图片时捕获所有批注,以避免冗余的重新读取。
draft 命令在您的 Gmail 草稿文件夹中创建邮件,以便在发送前审阅。
必需选项:
--to - 收件人邮箱地址--subject - 邮件主题行--body - 邮件正文文本可选:
--from - 发件人邮箱别名(回复时自动检测,见下文发件人对齐)--reply-to - 要回复的邮件 ID(创建具有正确标头的线程回复)--json - 以 JSON 格式输出草稿详细信息用户在 Gmail 中配置了多个"以...身份发送"别名。发件人地址必须正确匹配,否则收件人将看到来自错误身份的回复。
规则 1 - 回复(设置了 --reply-to): CLI 通过读取原始邮件的 To/Cc/Delivered-To 标头并与用户的"以...身份发送"别名匹配,自动检测正确的发件人。无需手动干预。CLI 将打印:
From: amonic@gmail.com (从原始邮件自动检测)
如果自动检测失败(例如,邮件是密送的),请显式传递 --from。
规则 2 - 新邮件(无 --reply-to): 当起草全新邮件(非回复)时,您必须在创建草稿之前使用 AskUserQuestion 来确认使用哪个发件人别名。切勿假设默认值。
AskUserQuestion({
questions: [{
question: "此邮件应从哪个邮箱地址发送?",
header: "以...身份发送",
options: [
// 从已知别名填充或让用户指定
{ label: "amonic@gmail.com", description: "个人 Gmail" },
{ label: "terry@eonlabs.com", description: "工作邮箱" },
],
multiSelect: false
}]
})
然后通过 --from 传递选定的地址:
$GMAIL_CLI draft --to "recipient@example.com" --from "amonic@gmail.com" --subject "Hello" --body "Message"
规则 3 - 始终在输出中验证: 创建草稿后,确认输出中显示了发件人地址。如果缺失或错误,请删除草稿并重新创建。
在每次创建草稿后,您必须向用户提供直接访问 Gmail 以审阅草稿的链接。这至关重要,因为草稿在发送前应始终进行视觉确认。
创建草稿后始终输出此内容:
草稿已创建!请在此处审阅:
https://mail.google.com/mail/u/0/#drafts
发件人:<sender_address>
切勿跳过此步骤。 用户必须能够点击进入 Gmail 并在发送前视觉验证草稿内容、发件人、收件人和线程。
# 1. 找到要回复的邮件
$GMAIL_CLI search "from:someone@example.com subject:meeting" -n 5 --json
# 2. 创建草稿回复 - From 从原始邮件的 To 标头自动检测
$GMAIL_CLI draft \
--to "someone@example.com" \
--subject "Re: Meeting tomorrow" \
--body "Thanks for the update. I'll be there at 2pm." \
--reply-to "19c1e6a97124aed8"
# 3. 始终向用户呈现审阅链接 + 发件人地址
# 1. 询问用户使用哪个别名发送(AskUserQuestion)
# 2. 使用显式的 --from 创建草稿
$GMAIL_CLI draft \
--to "someone@example.com" \
--from "amonic@gmail.com" \
--subject "Hello" \
--body "Message body"
# 3. 始终向用户呈现审阅链接 + 发件人地址
注意: 创建草稿后,如果用户之前只有读取权限,则需要重新认证。CLI 将提示 OAuth 授权以添加 gmail.compose 作用域。
| 查询 | 描述 |
|---|---|
from:sender@example.com | 来自特定发件人 |
to:recipient@example.com | 发送给特定收件人 |
subject:keyword | 主题包含关键词 |
after:2026/01/01 | 在日期之后 |
before:2026/02/01 | 在日期之前 |
label:inbox | 具有标签 |
is:unread | 未读邮件 |
has:attachment | 有文件附件(不匹配内联图片 — 见内联图片提取) |
| 变量 | 必需 | 描述 |
|---|---|---|
GMAIL_OP_UUID | 是 | 1Password 项目 UUID,用于 OAuth 凭据 |
GMAIL_OP_VAULT | 否 | 1Password 保险库(默认:服务账户为 Claude Automation) |
~/.claude/tools/gmail-tokens/
├── <uuid>.json # OAuth 令牌(访问 + 刷新),每小时刷新
└── <uuid>.app-credentials.json # client_id + client_secret(静态,从 1Password 缓存)
client_id/client_secret → 缓存到 <uuid>.app-credentials.json<uuid>.json强制重新进行 1Password 查找(例如,在轮换 OAuth 应用凭据后):
rm ~/.claude/tools/gmail-tokens/<uuid>.app-credentials.json
invalid_grantGoogle OAuth "测试"模式的刷新令牌在7天内未刷新后会过期。如果在该时间窗口内每小时刷新器未运行,refresh_token 将被永久吊销。
修复:删除过期的令牌文件并通过浏览器重新授权:
# 1. 备份并移除过期的令牌
mv ~/.claude/tools/gmail-tokens/<uuid>.json ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# 2. 运行任何 gmail 命令 — 浏览器将打开进行 OAuth 授权
$GMAIL_CLI list -n 1
# 3. 验证每小时刷新器是否获取到新令牌
~/.claude/automation/gmail-token-refresher/gmail-oauth-token-refresher 2>&1
# 4. 清理备份
rm ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# 一次性检查所有账户
for f in ~/.claude/tools/gmail-tokens/*.json; do
[ "$(basename "$f")" = "*.json" ] && continue
case "$(basename "$f")" in *.app-credentials.json) continue ;; esac
UUID=$(basename "$f" .json)
python3 -c "
import json, datetime
t = json.load(open('$f'))
exp = datetime.datetime.fromtimestamp(t.get('expiry_date',0)/1000)
delta = (exp - datetime.datetime.now()).total_seconds()
status = 'VALID' if delta > 0 else 'EXPIRED'
print(f' {\"$UUID\"}: {status} (expires in {int(delta/60)}m)' if delta > 0 else f' {\"$UUID\"}: EXPIRED ({int(-delta/3600)}h ago)')
" 2>/dev/null
done
每周安装次数
54
仓库
GitHub 星标数
26
首次出现
2026年2月7日
安全审计
安装于
opencode53
github-copilot52
codex52
kimi-cli52
amp52
gemini-cli52
Read and search Gmail programmatically via Claude Code.
CRITICAL : You MUST complete this preflight checklist before running any Gmail commands. Do NOT skip steps.
ls -la "$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail" 2>/dev/null || echo "BINARY_NOT_FOUND"
If BINARY_NOT_FOUND : Build it first:
cd ~/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli && bun install && bun run build
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID:-NOT_SET}"
If NOT_SET : You MUST run the Setup Flow below. Do NOT proceed to Gmail commands.
ALWAYS verify you're accessing the correct email account for the current project.
# Show current project context
echo "=== Gmail Account Context ==="
echo "Working directory: $(pwd)"
echo "GMAIL_OP_UUID: ${GMAIL_OP_UUID}"
# Check where GMAIL_OP_UUID is defined (mise hierarchy)
echo ""
echo "=== mise Config Source ==="
grep -l "GMAIL_OP_UUID" .mise.local.toml .mise.toml ~/.config/mise/config.toml 2>/dev/null || echo "Not found in standard locations"
# Quick connectivity test — shows the account email from a real email
echo ""
echo "=== Account Verification ==="
$GMAIL_CLI list -n 1 2>&1 | head -5
STOP and confirm with user before proceeding:
list -n 1 output shows the account's inbox — verify this matches the project's intended email.mise.local.toml sets GMAIL_OP_UUID in the mise hierarchy# Check cached token exists and is not expired
TOKEN_FILE="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.json"
APP_CREDS="$HOME/.claude/tools/gmail-tokens/${GMAIL_OP_UUID}.app-credentials.json"
echo "Token file: $([ -f "$TOKEN_FILE" ] && echo "EXISTS" || echo "MISSING")"
echo "App credentials: $([ -f "$APP_CREDS" ] && echo "CACHED" || echo "MISSING — will need 1Password on first run")"
If token file is MISSING : First run will open a browser for OAuth consent. This is expected. If app credentials are MISSING : 1Password will be called once to cache client_id/client_secret, then never again.
Follow these steps IN ORDER. Use AskUserQuestion at decision points.
command -v op && echo "OP_CLI_INSTALLED" || echo "OP_CLI_MISSING"
If OP_CLI_MISSING : Stop and inform user:
1Password CLI is required. Install with:
brew install 1password-cli
# Try common vaults — "Claude Automation" for service accounts, "Employee" for interactive
for VAULT in "Claude Automation" "Employee" "Personal"; do
ITEMS=$(op item list --vault "$VAULT" --format json 2>/dev/null | jq -r '.[] | select(.title | test("gmail|oauth|google"; "i")) | "\(.id)\t\(.title)"')
[ -n "$ITEMS" ] && echo "=== Vault: $VAULT ===" && echo "$ITEMS"
done
Parse the output and proceed based on results:
If items found , use AskUserQuestion with discovered items:
AskUserQuestion({
questions: [{
question: "Which 1Password item contains your Gmail OAuth credentials?",
header: "Gmail OAuth",
options: [
// POPULATE FROM op item list RESULTS - example:
{ label: "Gmail API - dental-quizzes (56peh...)", description: "OAuth client in Employee vault" },
{ label: "Gmail API - personal (abc12...)", description: "Personal OAuth client" },
],
multiSelect: false
}]
})
If NO items found , use AskUserQuestion to guide setup:
AskUserQuestion({
questions: [{
question: "No Gmail OAuth credentials found in 1Password. How would you like to proceed?",
header: "Setup",
options: [
{ label: "Create new OAuth credentials (Recommended)", description: "I'll guide you through Google Cloud Console setup" },
{ label: "I have credentials elsewhere", description: "Help me add them to 1Password" },
{ label: "Skip for now", description: "I'll set this up later" }
],
multiSelect: false
}]
})
After user selects an item (with UUID), use AskUserQuestion:
AskUserQuestion({
questions: [{
question: "Add GMAIL_OP_UUID to .mise.local.toml in current project?",
header: "Configure",
options: [
{ label: "Yes, add to .mise.local.toml (Recommended)", description: "Creates/updates gitignored config file" },
{ label: "Show me the config only", description: "I'll add it manually" }
],
multiSelect: false
}]
})
If "Yes, add to .mise.local.toml" :
.mise.local.toml existsGMAIL_OP_UUID to [env] section[env]
GMAIL_OP_UUID = "<selected-uuid>"
.mise.local.toml is in .gitignoreIf "Show me the config only" : Output the TOML for user to add manually.
mise trust 2>/dev/null || true
cd . && echo "GMAIL_OP_UUID after reload: ${GMAIL_OP_UUID:-NOT_SET}"
If still NOT_SET : Inform user to restart their shell or run source ~/.zshrc.
GMAIL_OP_UUID="${GMAIL_OP_UUID}" $HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail list -n 1
If OAuth prompt appears : This is expected on first run. Browser will open for Google consent.
GMAIL_CLI="$HOME/.claude/plugins/marketplaces/cc-skills/plugins/gmail-commander/scripts/gmail-cli/gmail"
# List recent emails
$GMAIL_CLI list -n 10
# Search emails
$GMAIL_CLI search "from:someone@example.com" -n 20
# Search with date range
$GMAIL_CLI search "from:phoebe after:2026/01/27" -n 10
# Read specific email with full body
$GMAIL_CLI read <message_id>
# Read and download inline images (copy-pasted screenshots in compose)
$GMAIL_CLI read <message_id> --save-images
# Download inline images to a specific directory
$GMAIL_CLI read <message_id> --save-images --image-dir ./attachments/my-folder/
# Shorthand: --image-dir implies --save-images
$GMAIL_CLI read <message_id> --image-dir ./attachments/my-folder/
# JSON output with image metadata and saved paths
$GMAIL_CLI read <message_id> --save-images --json
# Export to JSON
$GMAIL_CLI export -q "label:inbox" -o emails.json -n 100
# JSON output (for parsing)
$GMAIL_CLI list -n 10 --json
# Create a draft email
$GMAIL_CLI draft --to "user@example.com" --subject "Hello" --body "Message body"
# Create a draft reply (threads into existing conversation)
$GMAIL_CLI draft --to "user@example.com" --subject "Re: Hello" --body "Reply text" --reply-to <message_id>
Emails often contain copy-pasted screenshots (inline images embedded in the HTML body, not file attachments). These appear as [image: image.png] placeholders in plain text but contain real image data accessible via the Gmail API.
| Flag | Effect |
|---|---|
--save-images | Download all inline images to disk (default: ~/.claude/tools/gmail-images/<message_id>/) |
--image-dir <path> | Custom output directory (implies --save-images) |
| No flag | Shows image metadata (count, filenames, sizes) but does NOT download |
--- Inline Images (3) ---
image.png image/png 245.3 KB
image.png image/png 512.1 KB
photo.jpg image/jpeg 89.7 KB
--- Saved to Disk ---
./attachments/01_image.png (251,234 B)
./attachments/02_image.png (524,001 B)
./attachments/03_photo.jpg (91,852 B)
--- Markdown References ---



has:attachment does NOT find inline images. Gmail search has no operator for inline images. To discover emails with inline images, you must read the email and check the MIME tree.
Strategy for finding emails with inline images:
# Search by sender/date, then read each to check for images
$GMAIL_CLI search "from:sender@example.com after:2026/02/01" -n 10 --json | \
jq -r '.[].id' | while read id; do
COUNT=$($GMAIL_CLI read "$id" --json | jq '.inlineImages | length')
[ "$COUNT" -gt 0 ] && echo "$id has $COUNT inline image(s)"
done
When downloading images from a thread (multiple reply emails), later replies include all prior inline images. The last email in a thread is typically the superset.
Recommendation : For threaded conversations, download images from the latest reply only to avoid duplicates. Compare by file size if unsure.
Copy-pasted screenshots often all share the generic filename image.png. The CLI prefixes a zero-padded index: 01_image.png, 02_image.png, etc. These machine-generated names should be renamed to descriptive names for correspondence archival.
When inline images contain handwritten annotations (circles, arrows, written text overlaid on screenshots), perform a systematic two-level analysis:
Format annotations as blockquote captions beneath each image in markdown:

> **Annotation transcription**: [Detailed description of visual markup.]
> Handwritten text reads: _"exact transcription here"_
> [Interpretation of what the annotator is requesting.]
Do NOT defer annotation transcription to a second pass. Capture all annotations on the first image examination to avoid redundant re-reads.
The draft command creates emails in your Gmail Drafts folder for review before sending.
Required options:
--to - Recipient email address--subject - Email subject line--body - Email body textOptional:
--from - Sender email alias (auto-detected when replying, see Sender Alignment below)--reply-to - Message ID to reply to (creates threaded reply with proper headers)--json - Output draft details as JSONThe user has multiple Send As aliases configured in Gmail. The From address MUST match correctly or the recipient sees a reply from the wrong identity.
Rule 1 - Replies (--reply-to is set): The CLI auto-detects the correct sender by reading the original email's To/Cc/Delivered-To headers and matching against the user's Send As aliases. No manual intervention needed. The CLI will print:
From: amonic@gmail.com (auto-detected from original email)
If auto-detection fails (e.g., the email was BCC'd), explicitly pass --from.
Rule 2 - New emails (no --reply-to): When drafting a brand new email (not a reply), you MUST use AskUserQuestion to confirm which sender alias to use BEFORE creating the draft. Never assume the default.
AskUserQuestion({
questions: [{
question: "Which email address should this be sent from?",
header: "Send As",
options: [
// Populate from known aliases or let user specify
{ label: "amonic@gmail.com", description: "Personal Gmail" },
{ label: "terry@eonlabs.com", description: "Work email" },
],
multiSelect: false
}]
})
Then pass the selected address via --from:
$GMAIL_CLI draft --to "recipient@example.com" --from "amonic@gmail.com" --subject "Hello" --body "Message"
Rule 3 - Always verify in output: After draft creation, confirm the From address is shown in the output. If it's missing or wrong, delete the draft and recreate.
After EVERY draft creation, you MUST present the user with a direct Gmail link to review the draft. This is critical because drafts should always be visually confirmed before sending.
Always output this after creating a draft:
Draft created! Review it here:
https://mail.google.com/mail/u/0/#drafts
From: <sender_address>
Never skip this step. The user must be able to click through to Gmail and visually verify the draft content, sender, recipients, and threading before sending.
# 1. Find the message to reply to
$GMAIL_CLI search "from:someone@example.com subject:meeting" -n 5 --json
# 2. Create draft reply - From is auto-detected from original email's To header
$GMAIL_CLI draft \
--to "someone@example.com" \
--subject "Re: Meeting tomorrow" \
--body "Thanks for the update. I'll be there at 2pm." \
--reply-to "19c1e6a97124aed8"
# 3. ALWAYS present the review link + From address to user
# 1. Ask user which alias to send from (AskUserQuestion)
# 2. Create draft with explicit --from
$GMAIL_CLI draft \
--to "someone@example.com" \
--from "amonic@gmail.com" \
--subject "Hello" \
--body "Message body"
# 3. ALWAYS present the review link + From address to user
Note: After creating drafts, users need to re-authenticate if they previously only had read access. The CLI will prompt for OAuth consent to add the gmail.compose scope.
| Query | Description |
|---|---|
from:sender@example.com | From specific sender |
to:recipient@example.com | To specific recipient |
subject:keyword | Subject contains keyword |
after:2026/01/01 | After date |
before:2026/02/01 | Before date |
label:inbox | Has label |
Reference: https://support.google.com/mail/answer/7190
| Variable | Required | Description |
|---|---|---|
GMAIL_OP_UUID | Yes | 1Password item UUID for OAuth credentials |
GMAIL_OP_VAULT | No | 1Password vault (default: Claude Automation for service accounts) |
~/.claude/tools/gmail-tokens/
├── <uuid>.json # OAuth token (access + refresh), refreshed hourly
└── <uuid>.app-credentials.json # client_id + client_secret (static, cached from 1Password)
client_id/client_secret → cached to <uuid>.app-credentials.json<uuid>.jsonTo force a fresh 1Password lookup (e.g., after rotating OAuth app credentials):
rm ~/.claude/tools/gmail-tokens/<uuid>.app-credentials.json
invalid_grantGoogle OAuth "Testing" mode refresh tokens expire after 7 days without a refresh. If the hourly refresher was not running during that window, the refresh_token becomes permanently revoked.
Fix : Delete the expired token file and re-authorize via browser:
# 1. Back up and remove the expired token
mv ~/.claude/tools/gmail-tokens/<uuid>.json ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# 2. Run any gmail command — browser will open for OAuth consent
$GMAIL_CLI list -n 1
# 3. Verify the hourly refresher picks up the new token
~/.claude/automation/gmail-token-refresher/gmail-oauth-token-refresher 2>&1
# 4. Clean up backup
rm ~/.claude/tools/gmail-tokens/<uuid>.json.expired
# Check all accounts at once
for f in ~/.claude/tools/gmail-tokens/*.json; do
[ "$(basename "$f")" = "*.json" ] && continue
case "$(basename "$f")" in *.app-credentials.json) continue ;; esac
UUID=$(basename "$f" .json)
python3 -c "
import json, datetime
t = json.load(open('$f'))
exp = datetime.datetime.fromtimestamp(t.get('expiry_date',0)/1000)
delta = (exp - datetime.datetime.now()).total_seconds()
status = 'VALID' if delta > 0 else 'EXPIRED'
print(f' {\"$UUID\"}: {status} (expires in {int(delta/60)}m)' if delta > 0 else f' {\"$UUID\"}: EXPIRED ({int(-delta/3600)}h ago)')
" 2>/dev/null
done
Weekly Installs
54
Repository
GitHub Stars
26
First Seen
Feb 7, 2026
Security Audits
Gen Agent Trust HubPassSocketWarnSnykWarn
Installed on
opencode53
github-copilot52
codex52
kimi-cli52
amp52
gemini-cli52
Skills CLI 使用指南:AI Agent 技能包管理器安装与管理教程
50,200 周安装
news-shopping:通过Serper API实现Google新闻与购物搜索,助力市场监控与产品研究
54 周安装
WebPerf 性能调试工具包 - 47个Chrome DevTools代码片段,快速诊断网页加载、交互与核心指标
54 周安装
ReasoningBank Intelligence:AI自适应学习系统,实现智能代理的元认知与策略优化
WorkOS Widgets集成指南:用户管理、SSO配置、身份验证组件快速部署
54 周安装
AI结对编程工具:支持多模式协作、实时代码审查与TDD开发
AgentDB性能优化指南:向量数据库150倍至12500倍加速,内存减少4-32倍
is:unread | Unread emails |
has:attachment | Has file attachment (does NOT match inline images — see Inline Image Extraction) |