用 gws CLI + Claude Code 清理 13 年沒整理的 Gmail:完整操作教學

用 gws CLI + Claude Code 清理 13 年沒整理的 Gmail:完整操作教學

我的 Gmail 用了 13 年,從來沒整理過。

89,275 封信。foodpanda 促銷、LinkedIn 通知、Uber One 廣告、各種驗證碼。每次打開信箱看到那個數字就想關掉。我是一個很不喜歡刪東西的人,總覺得「搞不好以後會用到」,結果就是 13 年什麼都沒刪。

這週末花了一個下午,用 gws CLI + Claude Code 清掉 54,124 封,信箱直接少了 40%。

這篇是完整的操作教學,照著做就行。


前置準備

開始之前需要三樣東西:

1. 安裝 gws CLI

gws 是 Google Workspace 的官方 CLI 工具。裝完之後 Gmail、Calendar、Drive 全部變成 terminal 指令。最重要的是——所有輸出都是 JSON,AI Agent 可以直接解析。

brew install googleworkspace-cli

沒用 Homebrew 的話,還有其他安裝方式:

# npm
npm install -g @googleworkspace/cli

# 或從 source 編譯
cargo install --git https://github.com/googleworkspace/cli --locked

完整安裝選項可以看 GitHub repo

2. OAuth 認證

gws 使用 OAuth 2.0 認證。第一次需要建立 GCP 專案和 OAuth client:

# 一次性設定:建立 Cloud 專案、啟用 API、登入
gws auth setup

gws auth setup 需要先安裝 gcloud CLI。如果你沒裝 gcloud,會看到以下錯誤

也可以手動到 Google Cloud Console 建立 OAuth client,然後把 client_secret.json 放到 ~/.config/gws/client_secret.json,繼續下一步

設定完成後登入:

# 登入(選擇要授權的服務範圍)
gws auth login

這時會需要選擇 scope,記得要選擇 gmail,其他看需求

瀏覽器會跳出 Google 授權頁面,點授權就好。

授權完會選擇存取範圍,選擇完按繼續看到「You may now close this window」就可以關掉視窗回到 Terminal。

重要提醒: 跑完 gws auth setupgws auth login 之後,瀏覽器跳出 Google 授權頁面,但你看到的不是授權按鈕,而是這個畫面:

已封鎖存取權:
「claude code」未完成 Google 驗證程序

這個應用程式目前處於測試階段,只有獲得開發人員核准的測試人員可以存取。
如果您認為自己應該擁有存取權,請與開發人員聯繫。

發生錯誤 403:access_denied

gws auth setup 建立的 OAuth 同意畫面預設是「測試」狀態(未發布)。Google 規定:

  • 測試模式的 OAuth app,只有被加入「測試使用者」清單的 Google 帳號才能授權
  • 你的 Gmail 帳號雖然是 GCP 專案的擁有者,但不會自動被加入測試使用者清單

所以你需要手動把自己的 Gmail 帳號加進去。

網路上很多教學,這裡就不贅述~


認證完成後,token 會用 AES-256-GCM 加密存在本機 keyring,之後不用重新登入。

確認認證狀態:

gws auth status

會看到類似這樣的輸出:

{
  "auth_method": "oauth2",
  "token_valid": true,
  "user": "你的email@gmail.com",
  "enabled_api_count": 35,
  "scope_count": 50
}

完整的認證教學可以參考 gws README 的 Authentication 段落

用完之後建議到 Google 帳戶安全性設定 檢查授權範圍,不需要的話可以撤銷。

3. Claude Code

需要 Claude Code(Max 或 Pro 方案)。

npm install -g @anthropic-ai/claude-code

3.1. 安裝 gws 的 Claude Code Skills(推薦)

這是很多人不知道的部分。gws 內建了 95 個 Agent Skills,專門給 Claude Code 用。裝了之後 Claude Code 就「知道」怎麼呼叫 gws 的正確語法。

# 一次安裝所有 gws skills
npx skills add https://github.com/googleworkspace/cli

# 或只裝你需要的
npx skills add https://github.com/googleworkspace/cli/tree/main/skills/gws-gmail
npx skills add https://github.com/googleworkspace/cli/tree/main/skills/gws-drive

不裝 skill 也能用 gws,但 Claude Code 容易猜錯語法。我第一次操作的時候就踩了這個坑——AI 把指令寫成 gws gmail users.messages.list,跑不過。正確的語法是空格分隔 gws gmail users messages list。裝了 skill 就不會有這個問題。

三樣都備齊,可以開始了。


Step 1:看信箱現況

打開 Claude Code,第一步是搞清楚自己的信箱到底有多少信。

我的 prompt:

「幫我看這個 Gmail 信箱有多少信」

Claude Code 的回應:

它先跑了 gws gmail users getProfile

gws gmail users getProfile --params '{"userId": "me"}'

回傳:

{
  "emailAddress": "xxx@gmail.com",
  "messagesTotal": 89276,
  "threadsTotal": 85689
}

然後它主動跑 gws gmail +triage 來看最近的收件匣概覽:

gws gmail +triage

這個 helper 指令會列出最近的未讀信件摘要(寄件人、主旨、日期),像這樣:

date                from                              subject
────────────────    ─────────────────────────────     ──────────────────────────
Sat, 04 Apr 2026    "Uber 電子明細" <noreply@uber>     [Personal],您的訂單
Sat, 4 Apr 2026     foodpanda <contact@info...>        【企客】,你已經錯失好多了?
Sat, 04 Apr 2026    "台新銀行" <TSB@mhurcv...>         連假免出門🏠Uber Eats 23折優惠

89,275 封。真的有點扯。

為了方便了解他做了什麼,我把 claude code 實際跑的指令寫出來,但實際上你最後會看到的是他總結給你的結果~

Step 2:讀取特定信件

信箱現況看完了,我想先處理一封研討會的入選通知。

我的 prompt:

「幫我看這封:xxxxxx」

Claude Code 的操作:

它先搜尋符合的信件:

gws gmail users messages list \
  --params '{"userId": "me", "q": "subject:研討會 投稿入選", "maxResults": 5}'

回傳 message ID 清單後,他用 +read 讀取信件內容:

gws gmail +read --id 19d4da41657c3eb1

然後 Claude Code 主動幫我整理重點:

這是某技術研討會的投稿入選通知,重點整理:

緊急:截止日是明天!
需要回覆:
1. 確認出席
2. 提供宣傳照片(至少 500x500 像素 JPG)
3. 確認宣傳資料

你的議程資訊:
- 講題:XXX
- 日期:待定

它不只是把信件內容貼出來,還會自動判斷「哪些是你需要做的事」,這是 AI Agent 跟傳統 CLI 最大的差別。


Step 3:草擬回覆信

信讀完了,要回覆確認出席。

我的 prompt:

「好,草擬回覆信,圖片附件在 ~/Downloads/xxxx.jpg」

Claude Code 的操作:

它先確認圖片存在和尺寸:

sips -g pixelWidth -g pixelHeight ~/Downloads/xxxx.jpg
# pixelWidth: 1994
# pixelHeight: 2448

確認 1994x2448 符合 500x500 以上要求後,用 +reply-all(因為信裡要求用全部回覆)建立草稿:

gws gmail +reply-all \
  --message-id 19d4da41657c3eb1 \
  --body '<p>您好,</p>
<p>感謝您的提醒。以下確認事項:</p>
<ol>
<li><strong>出席確認</strong>:兩日都可以到場。</li>
<li><strong>宣傳照片</strong>:請參閱附件。</li>
<li><strong>宣傳資料</strong>:目前資訊無需修改。</li>
</ol>
<p>Best regards,<br>Jason Yang</p>' \
  --html \
  -a xxxx.jpg \
  --draft

注意: -a 附件的檔案路徑必須在當前工作目錄內,所以它先把照片 cp 到工作目錄再附加。

回傳:

{
  "id": "r3413428723371210479",
  "message": {
    "id": "19d597af94f38088",
    "labelIds": ["DRAFT"],
    "threadId": "19d4da41657c3eb1"
  }
}

草稿建立成功!存在 Gmail 草稿匣裡,可以先到 Gmail 確認內容再手動送出。

--draft flag 是個好習慣——讓 AI 先建草稿,你確認後再寄。不要讓 AI 直接送出信件。


Step 4:搜尋舊發票

接下來處理另一件事:我要找一張幾個月前買線上課程的發票。只記得大概是去年七八月,花了xxx台幣(美金顯示)。

我的 prompt:

「我想要找某個課程之前購買的發票,信件有點多,大概是在去年七八月左右,價格是xxx台幣,但應該是顯示美金」

Claude Code 的操作:

它先用關鍵字搜尋:

gws gmail users messages list \
  --params '{"userId": "me", "q": "課程名稱 after:2025/06/01 before:2025/10/01", "maxResults": 10}'

第一次搜太窄沒結果,它自動放寬條件,拿掉「發票」只留課程名稱:

gws gmail users messages list \
  --params '{"userId": "me", "q": "課程名稱 after:2025/06/01 before:2025/10/01", "maxResults": 10}'

找到幾封候選信件後,再加上付款相關關鍵字篩選:

gws gmail users messages list \
  --params '{"userId": "me", "q": "課程名稱 (invoice OR receipt OR 付款 OR payment) after:2025/06/01 before:2025/10/01", "maxResults": 10}'

找到 3 封候選,它用 +read 逐一讀取,最終找到付款平台的收據:

gws gmail +read --id 19xxxxxxxxxx
Receipt from ○○○ Limited  Receipt #XXXX-XXXX
Amount paid: $XXX.00
Date paid: Aug X, 2025
Payment method: Visa ****XXXX

Summary:
- 某課程 早鳥方案 × 1
- Amount paid : $XXX.00

金額跟我記憶中的吻合,確認就是這張。

整個搜尋過程大概 10 秒。如果用 Gmail 網頁版手動搜,光翻頁就要翻很久。


Step 5:轉寄購買證明

找到發票後,我需要轉寄給課程方處理要事。

我的 prompt:

「幫我把這封發票轉寄到 support@某某.site,說我要申請xxx」

Claude Code 的操作:

它用 +forward 轉寄原始收據,並在上方加上說明:

gws gmail +forward \
  --message-id 19xxxxxxxxxx \
  --to support@某某.site \
  --body '<p>您好,</p>
<p>以下附上當初的購買收據作為證明。</p>
<p>Best regards,<br>Jason</p>' \
  --html \
  --draft

同樣用 --draft 先存草稿。原始收據會自動帶在轉寄信件裡,不需要手動貼。


Step 6:批次清理信箱

前面的操作讓我確認 gws + Claude Code 的組合很順手。接下來進入正題——清理 89,275 封信。

6.1 盤點各類別數量

我的 prompt:

「我想整理信件,你幫我開一個 agent team 來研究怎麼整理」

Claude Code 派出兩個平行 agent:一個做清理策略規劃,一個分析信箱現狀。

分析 agent 跑了這些查詢來估算各類別數量:

# 促銷信(1年前)
gws gmail users messages list \
  --params '{"userId": "me", "q": "category:promotions older_than:1y", "maxResults": 500}' \
  --page-all --page-limit 100

# 社群通知(6個月前)
gws gmail users messages list \
  --params '{"userId": "me", "q": "category:social older_than:6m", "maxResults": 500}' \
  --page-all --page-limit 100

# 驗證碼(1個月前)
gws gmail users messages list \
  --params '{"userId": "me", "q": "subject:(驗證碼 OR \"verification code\" OR OTP) older_than:1m", "maxResults": 500}' \
  --page-all --page-limit 100

--page-all 是個很好用的 flag,自動幫你翻頁,每頁輸出一行 JSON(NDJSON 格式)。不用自己寫 pagination 邏輯。

結果:

類別 查詢條件 數量
促銷郵件(1年前) category:promotions older_than:1y 37,300 封
社群通知(6個月前) category:social older_than:6m 6,382 封
驗證碼(1個月前) subject:(驗證碼 OR OTP) older_than:1m 78 封

光這三類就佔信箱將近 49%。

6.2 抽樣確認

這一步很重要,不要跳過。

Claude Code 自動抽樣每個類別的前幾封,列出寄件人和主旨讓我確認:

# 取得前 5 封的 message ID
gws gmail users messages list \
  --params '{"userId": "me", "q": "category:promotions older_than:1y", "maxResults": 5}'

# 逐封讀取 metadata
gws gmail users messages get \
  --params '{"userId": "me", "id": "195fbe4176a1908d", "format": "metadata", "metadataHeaders": ["Subject","From","Date"]}'

結果:

Anna Anisin <anna.a@formulatedby.com> | AI & Data Weekly
foodpanda <contact@info.foodpanda.com.tw> | 【企客】,你已經錯失好多了?
Max <no-reply@recommendations.max.com> | 上線倒數計時開始:《最後生還者》第 2 季
"天下學習" <cwlearning@cw.com.tw> | 【週末微學習,限時體驗】
Uber One <uberone@uber.com> | 4 月 Uber One 會員專屬優惠攻略來了!

全部都是典型促銷垃圾。社群通知也是:

LinkedIn <notifications-noreply@linkedin.com> | You have 4 new invitations
LinkedIn <messages-noreply@linkedin.com> | Full Stack Engineer: 台灣大哥大...
LinkedIn <jobs-listings@linkedin.com> | Google is hiring a Software Engineer

確認安全後,才進入批次清理。

6.3 開 Agent Team 平行清理

我的 prompt:

「三個一起來,開 agent team 處理」

Claude Code 同時派出 3 個背景 agent,每個負責一個類別:

Agent 1 — 促銷信(37,300 封):

# 1. 收集所有 message ID
gws gmail users messages list \
  --params '{"userId": "me", "q": "category:promotions older_than:1y", "maxResults": 500}' \
  --page-all --page-limit 100 | jq -r '.messages[]?.id'

# 2. 備份 ID 到本地檔案
# 存到 /tmp/gmail-promo-ids.txt

# 3. 批次移到垃圾桶(每次最多 1000 封)
gws gmail users messages batchModify \
  --params '{"userId": "me"}' \
  --json '{"ids": ["id1","id2",...], "addLabelIds": ["TRASH"], "removeLabelIds": ["INBOX"]}'

Agent 2 — 社群通知(6,382 封): 同樣流程,query 換成 category:social older_than:6m

Agent 3 — 驗證碼(78 封): 同樣流程,query 換成驗證碼關鍵字

三個 agent 平行執行,互不干擾。促銷那批最大,分了 38 批處理(每批 1000 封),耗時約 5 分鐘。

安全措施

整個過程有三道安全網:

  1. 抽樣確認過了 — 每個類別都人工看過樣本
  2. Message ID 備份 — 所有被刪的 ID 都存在 /tmp/gmail-*-ids.txt
  3. 只用 batchModify 移到垃圾桶 — Gmail 垃圾桶有 30 天恢復期,不是永久刪除

batchModify + addLabelIds: ["TRASH"] 而不是 messages delete(永久刪除)。這個差別很重要。


Step 7:驗收結果

三個 agent 跑完後,Claude Code 自動回報:

全部完成!三個 agent 的清理結果:

| 類別               | 數量        | 狀態 |
|--------------------|------------|------|
| 促銷郵件(1年前)    | 37,300 封  | 完成 |
| 社群通知(6個月前)  | 6,382 封   | 完成 |
| 驗證碼(1個月前)    | 78 封      | 完成 |
| 合計               | 54,124 封  | 全部移至垃圾桶 |

信箱從 89,275 封降到約 45,515 封,清掉了將近一半。

之後又進行了幾輪更深入的清理:

輪次 類別 數量
第二輪 GitHub 通知、出貨通知、密碼重設 1,408 封
第三輪 Medium、Freelancer.com、外送促銷 1,957 封
第四輪 Uber、Substack、Coursera、LinkedIn、雜項 6,999 封
累計 54,124 封(60.6%)

最終信箱剩約 35,000 封。從 89,275 砍到 35,000,六成都是垃圾。

一個下午做完 13 年沒做的事。


延伸用法:AI 郵件助理

清完之後我才發現,gws + Claude Code 不只能清信,根本是一個完整的郵件助理。

搜尋舊信

前面示範過了。跟 Claude Code 說「幫我找某個課程的收據」,gws 搜尋 + AI 解讀,幾秒就翻出來了。

草擬回覆

收到研討會入選通知,我只說「幫我草擬回覆」,它直接 +reply-all --draft 建好草稿,附件都帶好。

轉寄文件

找到發票後,說「轉寄給某某信箱,說我要做什麼」,它用 +forward --draft 處理。原始收據自動帶在轉寄裡。

Uber 收據分析

我還讓它把所有外送平台收據抓出來,解析成 CSV,然後做成互動式 Dashboard。

以前這些事每件都要打開 Gmail、搜尋半天、點進去、複製貼上。現在一句話搞定。


gws CLI 常用指令速查

用途 指令
安裝 brew install googleworkspace-cli
認證設定 gws auth setup
登入 gws auth login
認證狀態 gws auth status
收件匣摘要 gws gmail +triage
讀信 gws gmail +read --id <MSG_ID>
搜尋信件 gws gmail users messages list --params '{"userId":"me","q":"<query>"}'
回覆 gws gmail +reply --message-id <ID> --body '<text>' --draft
全部回覆 gws gmail +reply-all --message-id <ID> --body '<text>' --html --draft
轉寄 gws gmail +forward --message-id <ID> --to <email> --draft
寄信 gws gmail +send --to <email> --subject '<subject>' --body '<text>'
附件 -a <file>(檔案需在工作目錄內)
批次操作 gws gmail users messages batchModify --params '{"userId":"me"}' --json '...'
自動翻頁 --page-all --page-limit 100 --page-delay 100

所有 + 開頭的是 helper 指令(語法比較人性化),其他的走標準 Google API 路徑。


注意事項 + 踩坑紀錄

踩坑 1:gws 指令語法

gws 的指令格式是 gws <service> <resource> [sub-resource] <method>,參數用 --params 傳 JSON。

# 錯誤 ❌ — 用了點號分隔
gws gmail users.messages.list --user-id me

# 錯誤 ❌ — 少了 users 這層
gws gmail messages list --format json

# 正確 ✅
gws gmail users messages list --params '{"userId":"me","maxResults":100}'

關鍵差異:

  1. 需要 users 這個 resource 層級(Gmail API 的路徑是 /users/{userId}/messages
  2. 所有參數都用 --params 包成 JSON,不是 CLI flags
  3. userId 固定傳 "me" 代表當前授權的帳號

Helper 指令(+read+reply 等)語法比較直覺,不需要記 API 路徑。建議優先用 helper。

踩坑 2:附件路徑限制

-a 附件的檔案路徑必須在當前工作目錄內,不能用絕對路徑或 ~/ 開頭的路徑:

# 錯誤 ❌
gws gmail +reply --message-id xxx -a ~/Downloads/photo.jpg
# Error: resolves to path which is outside the current directory

# 正確 ✅
cp ~/Downloads/photo.jpg ./photo.jpg
gws gmail +reply --message-id xxx -a photo.jpg

踩坑 3:OAuth scope 限制

如果你的 OAuth app 在測試模式(未驗證),Google 限制同意畫面可授權的 scope 數量。用 recommended scope preset(85+ scopes)會失敗。解法是指定只需要的服務:

gws auth login -s gmail,drive

建議先小量測試

不要一上來就清全部。先拿一個小類別(像驗證碼,通常只有幾十封)試跑一次完整流程:分類 → 抽樣 → 備份 ID → 移到垃圾桶 → 確認。確認整個流程沒問題再放大規模。

Token 成本

  • Google API:免費。Gmail API 有每日配額但一般個人用量不會超過
  • Claude Code token:這是主要成本。如果你用月費制一定夠

Claude Code 權限控制

跑批次任務的時候,建議在 Claude Code 的設定裡把 gws 相關指令設為允許,避免每次執行都要手動確認。但 gws gmail messages delete(永久刪除)不要放進自動允許清單。

trash(垃圾桶)就好,不要用 delete(永久刪除)。