google-gmail について
curl と jq で Gmail API を操作するスキルで、メール処理を行うアシスタント向けです。権限スコープに応じた操作、メタデータ優先の取得、破壊的操作前の確認を重視します。
主な機能
- 01メールの検索と一覧表示
- 02メールやスレッドの内容取得
- 03ラベル、アーカイブ、ゴミ箱の管理
- 04gws による作成、返信、転送
- 05OAuth スコープの遵守と確認
活用シーン
- 01未読受信トレイの整理
- 02送信者、件名、ラベルで検索
- 03ラベル付けやメールのアーカイブ
- 04確認後の返信の作成と送信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ acedatacloud/ skills --skill google-gmailインストール後、Claude Code で/google-gmailを入力して読み込まれたか確認します。
詳細
curl + jq を使って Gmail を操作します。ユーザーの OAuth ベアラートークンは
$GOOGLE_GMAIL_TOKEN にあり、すべての呼び出しで
Authorization: Bearer $GOOGLE_GMAIL_TOKEN が必要です。トークンには最低限、
gmail.readonly と ID スコープ
(openid email profile)が含まれます。インストール時にユーザーが書き込みを許可している場合は、
gmail.modify(ラベル付け/アーカイブ/ゴミ箱への移動)、および/または
gmail.send(作成+送信)も含まれます。書き込みが実際に失敗するまでは、常に最小限のスコープを前提にしてください。ここから Google に新しいスコープを要求しないでください。
Gmail API は標準の JSON を返します。エラーは
{"error": {"code": 401|403|..., "message": "..."}} の形式で返されるため、そのエラーをそのまま表示してください。401 はトークンの期限切れを意味します(再インストールが必要です)。403 insufficientPermissions は、この呼び出しに必要な書き込みスコープをユーザーが許可していないことを意味します。不足しているスコープを説明し、該当する書き込み項目にチェックを入れてコネクタを再インストールするよう提案してください。
破壊的な書き込み(スレッドをゴミ箱に移動する、メールを送信するなど)の前には、対象/下書きの内容を正確にユーザーに示し、確認を求めてください。 明示的な許可なく、多数のメッセージに対して処理を実行しないでください。
必ず最初に users/me/profile を呼び出し、接続が機能していることを確認し、操作対象の Gmail アカウントを把握してください。メールボックスのペイロードは非常に大きくなることがあります。まずメタデータを取得し、ユーザーが特定のメッセージ本文を求めた場合にのみ format=full を使用してください。
オプション:送信メール用の Google Workspace CLI(gws)
gws は Google 公式の CLI です(公式サポート対象ではなく、googleworkspace 組織がコミュニティでメンテナンスしています)。Google の Discovery Document からコマンドを動的に構築し、API エラー時にはゼロ以外で終了します。また、メッセージのエンコード処理を担う、手作りのヘルパーコマンド(+ が接頭辞)も備えています。
メールの送信には gws を使用してください。 Gmail REST API では、すべての送信メッセージを RFC 822 形式で完全に構成し、base64url でエンコードして raw フィールドに格納する必要があります。また、返信/転送のスレッド情報を In-Reply-To/References/threadId で指定する必要があります。+send/+reply/+reply-all/+forward ヘルパーが、これらすべてを処理します。それ以外のすべて(読み取り、検索、ラベル、添付ファイル)では、gws と curl は同等です。そのため、通常は以下の curl の手順のほうが簡潔です。そちらを使用してください。
インストール
npm install -g @googleworkspace/cli # or: brew install googleworkspace-cli
# Pre-built binaries also at https://github.com/googleworkspace/cli/releases
gws --version
認証
gws は GOOGLE_WORKSPACE_CLI_TOKEN 環境変数から OAuth ベアラートークンを読み込みます。このスキルで使う Gmail トークンは
$GOOGLE_GMAIL_TOKEN にあるため、gws を呼び出す各シェルブロックの先頭で一度、再度エクスポートしてください。
export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_GMAIL_TOKEN"
gws gmail users getProfile --params '{"userId":"me"}' を実行すると、現在のアカウントを確認できます。
送信/返信/転送
# New message
gws gmail +send \
--to [email protected] \
--cc [email protected] \
--subject "Q1 status" \
--body "Numbers attached."
# Reply (handles threadId, In-Reply-To, References automatically;
# To is the original sender, Subject gets the "Re: " prefix)
gws gmail +reply --message-id MSG_ID --body "Thanks — looks good."
# Reply-all
gws gmail +reply-all --message-id MSG_ID --body "+1"
# Forward to new recipients (preserves the original message body
# inline; original headers are summarised in the forward block)
gws gmail +forward --message-id MSG_ID --to [email protected]
Google がリクエストを拒否すると、各ヘルパーはゼロ以外のステータスで終了し、stderr に JSON エラーを出力します。そのエラーをそのまま表示してください。+send/
+reply には gmail.send スコープが必要です。ユーザーが
gmail.readonly しか許可していない場合は、403 insufficientPermissions が返されるため、送信項目にチェックを入れてコネクタを再インストールするよう依頼してください。
以下の読み取り/一覧表示/検索/ラベル/添付ファイルの手順を
gws 用に書き換えていないのは意図的です。1 行の curl ... | jq のほうが短く、シェルパイプラインにも組み込みやすいためです。
レシピ
認証の確認(必ず最初に実行)
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/profile" \
| jq '{email: .emailAddress, totalMessages, totalThreads, historyId}'
受信トレイの最近の未読メールを一覧表示
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
--data-urlencode 'q=is:unread in:inbox newer_than:7d' \
--data-urlencode 'maxResults=20' \
| jq '.messages // [] | .[]'
.messages は必ずデフォルトで [] にしてください。 Gmail の messages.list は、一致するメールが 0 件の場合にフィールドを完全に省略します(レスポンスは {"resultSizeEstimate": 0} のみです)。そのため、.messages[] をそのまま使うと jq が Cannot iterate over null (null) でクラッシュし、終了コード 5 を返します。同じことが一覧取得エンドポイントの
.threads、.labels、.drafts にも当てはまります。結果が空の場合は再試行せず、「過去 7 日間に未読メールはありません」のように、ユーザーにはっきり伝えてください。
messages.list エンドポイントが返すのは {id, threadId} のみです。ヘッダーや本文を取得するには、各メッセージに対して messages.get を呼び出す必要があります。効率的な方法は、ID を一覧取得してから、各 ID に対して format=metadata&metadataHeaders=From,Subject,Date を指定して取得することです。本文が必要な場合にのみ format=full を使用してください。
ヘッダー付きで一覧表示+詳細取得(受信トレイを一括トリアージ)
IDS=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
--data-urlencode 'q=is:unread in:inbox' \
--data-urlencode 'maxResults=10' \
| jq -r '.messages // [] | .[].id')
# If $IDS is empty the for-loop below runs zero times — tell the user
# "no unread mail" rather than echoing an empty result.
for ID in $IDS; do
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ID" \
--data-urlencode 'format=metadata' \
--data-urlencode 'metadataHeaders=From' \
--data-urlencode 'metadataHeaders=Subject' \
--data-urlencode 'metadataHeaders=Date' \
| jq '{id: .id, snippet: .snippet, headers: (.payload.headers | map({(.name): .value}) | add), labels: .labelIds}'
done | jq -s '.'
1 件のメッセージ本文を読む(プレーンテキストと HTML)
ID='18f1a2b3c4d5e6f0'
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ID" \
--data-urlencode 'format=full')
echo "$RESP" | jq '{id, snippet, headers: (.payload.headers | map({(.name): .value}) | add)}'
# Body is base64url-encoded inside payload.parts[].body.data — Gmail
# splits multipart messages, so collect every text/plain or text/html
# leaf and base64url-decode them.
echo "$RESP" | jq -r '
def walk(p):
if (p.parts // null) then (p.parts | map(walk(.)) | add) else [p] end;
walk(.payload)
| map(select(.mimeType=="text/plain" and (.body.data // "") != ""))
| .[].body.data' \
| tr '_-' '/+' | base64 -d 2>/dev/null
プレーンテキストのリーフが空の場合は、text/html のリーフに切り替えてください
(同じ手順で、mimeType のフィルターを変更します)。その場合は、ユーザーに HTML であることを伝えてください。
スレッド全体を読む
THREAD_ID='18f1a2b3c4d5e6f0'
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/threads/$THREAD_ID" \
--data-urlencode 'format=metadata' \
--data-urlencode 'metadataHeaders=From' \
--data-urlencode 'metadataHeaders=Subject' \
--data-urlencode 'metadataHeaders=Date' \
| jq '{id, historyId, messages: [(.messages // [])[] | {id, snippet, from: (.payload.headers | from_entries.From), date: (.payload.headers | from_entries.Date)}]}'
Gmail のクエリで検索する
# Same query DSL the Gmail UI uses: from:, to:, subject:, has:attachment,
# is:unread, label:Work, after:2026/04/01, before:2026/05/01, …
Q='from:[email protected] subject:OKR newer_than:30d'
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
--data-urlencode "q=$Q" \
--data-urlencode 'maxResults=20' \
| jq '.messages // []'
q の構文リファレンス:https://support.google.com/mail/answer/7190 —
モデルが扱いやすいのは、from:、to:、cc:、subject:、label:、
is:unread、is:read、is:starred、has:attachment、filename:pdf、
newer_than:7d、older_than:30d、after:YYYY/MM/DD、before:、in:inbox、
in:trash です。OR/()/- と組み合わせてください。
ラベルを一覧表示する(システム+ユーザー定義)
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/labels" \
| jq '.labels[] | {id, name, type, color: .color.backgroundColor}'
システムラベルは、INBOX、SENT、DRAFT、IMPORTANT、UNREAD、
STARRED、SPAM、TRASH と、CATEGORY_*(個人/ソーシャル/
プロモーション/新着情報/フォーラム)です。
ラベルで絞り込む
LABEL_ID='Label_4' # from labels.list above
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
--data-urlencode "labelIds=$LABEL_ID" \
--data-urlencode 'maxResults=20' \
| jq '.messages // []'
複数の labelIds クエリパラメーターは AND として扱われます。
添付ファイルをダウンロードする
MSG_ID='18f1a2b3c4d5e6f0'
# 1. find the attachment leaf
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID" \
--data-urlencode 'format=full')
echo "$RESP" | jq '
def walk(p):
if (p.parts // null) then (p.parts | map(walk(.)) | add) else [p] end;
walk(.payload)
| map(select(.body.attachmentId? != null))
| .[] | {filename, mimeType, attachmentId: .body.attachmentId, size: .body.size}'
# 2. fetch the attachment by id
ATT_ID='ANGjdJ-abc123'
OUT=/tmp/attachment.bin
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/attachments/$ATT_ID" \
| jq -r .data | tr '_-' '/+' | base64 -d > "$OUT"
file "$OUT"
ページネーション
PAGE_TOKEN=''
while : ; do
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
--data-urlencode 'q=in:inbox' \
--data-urlencode 'maxResults=100' \
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
echo "$RESP" | jq -c '.messages[]?'
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
[ -z "$PAGE_TOKEN" ] && break
done
書き込みのレシピ
これらには gmail.modify(ラベル付け/アーカイブ/ゴミ箱への移動)または
gmail.send(作成+送信)が必要です。インストール時にユーザーが
gmail.readonly だけを許可した場合は、403 insufficientPermissions
が返されます。その旨を伝え、書き込み用のチェックボックスをオンにして
再インストールするよう案内してください。
メッセージを既読/未読にする、スターを付ける、アーカイブする(gmail.modify)
MSG_ID='18f1a2b3c4d5e6f0'
# Mark as read = remove the UNREAD label
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"removeLabelIds":["UNREAD"]}' \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
# Star it = add the STARRED label
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"addLabelIds":["STARRED"]}' \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
# Archive = remove from INBOX (keeps in All Mail)
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"removeLabelIds":["INBOX"]}' \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
modify エンドポイントでは、addLabelIds と removeLabelIds を
同時に指定できます。「アーカイブ+ラベル付け」の操作をアトミックに行うのに便利です。
スレッド全体に適用するには、/threads/$THREAD_ID/modify にも同じ形式を使用してください。
カスタムラベルを適用する
# 1. find or remember the label id from labels.list
LABEL_ID='Label_4'
MSG_ID='18f1a2b3c4d5e6f0'
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"addLabelIds\":[\"$LABEL_ID\"]}" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
新しいラベルを作成する場合も、同じスコープが必要です。
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"name":"Follow up","messageListVisibility":"show","labelListVisibility":"labelShow"}' \
"https://gmail.googleapis.com/gmail/v1/users/me/labels" \
| jq '{id, name}'
メッセージまたはスレッドをゴミ箱に移動する
MSG_ID='18f1a2b3c4d5e6f0'
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/trash"
# Whole thread:
THREAD_ID='18f1a2b3c4d5e6f0'
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/threads/$THREAD_ID/trash"
復元するには、同じ形式で /untrash を使用してください。messages.delete は絶対に使用しないでください。完全に削除され、こちらでは要求していない、より高いスコープが必要になります。
新しいメールを送信する(gmail.send)
Gmail では、メッセージを base64url でエンコードした RFC 2822 形式の文字列として指定する必要があります。
# Compose the message
TO='[email protected]'
SUBJECT='Quick hello'
BODY='Hi Alice,
Just a quick test note from the AceDataCloud Gmail connector.
Best,
YOUR_NAME'
# Multi-line subject lines need MIME encoded-word for non-ASCII; ASCII is fine raw.
RAW=$(printf 'To: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\nMIME-Version: 1.0\r\n\r\n%s' \
"$TO" "$SUBJECT" "$BODY" \
| base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"raw\":\"$RAW\"}" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
| jq '{id, threadId, labelIds}'
ASCII 以外の件名(中国語/絵文字)には、MIME の encoded-word を使用してください。
SUBJECT_RAW='你好,季度复盘草稿'
SUBJECT_ENCODED="=?UTF-8?B?$(printf %s "$SUBJECT_RAW" | base64)?="
スレッド内で返信する(スレッドを維持する)
返信先のメッセージの Message-Id を In-Reply-To ヘッダーと
References ヘッダーに設定し、さらに API の本文に Gmail のスレッド ID を渡して返信してください。
ORIG_MSG_ID='18f1a2b3c4d5e6f0'
ORIG=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ORIG_MSG_ID" \
--data-urlencode 'format=metadata' \
--data-urlencode 'metadataHeaders=Message-ID' \
--data-urlencode 'metadataHeaders=Subject' \
--data-urlencode 'metadataHeaders=From')
MID=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .["Message-ID"] // .["Message-Id"]')
FROM=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .From')
SUBJ=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .Subject')
TID=$(echo "$ORIG" | jq -r .threadId)
RAW=$(printf 'To: %s\r\nSubject: Re: %s\r\nIn-Reply-To: %s\r\nReferences: %s\r\nContent-Type: text/plain; charset=UTF-8\r\nMIME-Version: 1.0\r\n\r\n%s' \
"$FROM" "$SUBJ" "$MID" "$MID" \
'Replying inline — will follow up later today.' \
| base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"raw\":\"$RAW\",\"threadId\":\"$TID\"}" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
| jq '{id, threadId}'
本文に threadId がないと、In-Reply-To ヘッダーが正しくても、Gmail は新しいスレッドを開始します。
送信せずに下書きを保存する
同じ raw ペイロードを使用し、エンドポイントを変えます。書き込み時は drafts も内部的に送信スコープを共有するため、引き続き gmail.send が必要です。
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"message\":{\"raw\":\"$RAW\"}}" \
"https://gmail.googleapis.com/gmail/v1/users/me/drafts" \
| jq '{id, message: {id: .message.id, threadId: .message.threadId}}'
よくあるエラーコード
| HTTP | 意味 | ユーザーへの案内 |
|---|---|---|
401 UNAUTHENTICATED | トークンの期限切れ/取り消し | 「Connections ページで Gmail コネクターに再接続してください。」 |
403 insufficientPermissions | スコープが不足 | 該当するスコープ(ラベル付け/アーカイブ/ゴミ箱への移動には gmail.modify、送信には gmail.send)を特定し、そのチェックボックスをオンにしてコネクターを再インストールするよう案内してください。 |
403 userRateLimitExceeded/429 | クォータ超過/スロットリング | 約 5 秒待ってから、もう一度だけ再試行してください。 |
404 notFound | メッセージ/スレッド/添付ファイル ID が誤っている | ID を再確認するか、適切なクエリを指定して messages.list にフォールバックしてください。 |
400 invalidQuery | q の形式が正しくない | 送信した q とエラーをユーザーに表示してください。 |
$GOOGLE_GMAIL_TOKEN をログに記録したり、そのまま出力したりしないでください。秘密情報として扱ってください。