Claude Media
ClaudeでGASのGmail自動ラベル分けを作る — 検証用ラベルで安全に試す

ClaudeでGASのGmail自動ラベル分けを作る — 検証用ラベルで安全に試す

GmailAppの検索クエリとラベル付けで受信メールを仕分けるGASを、Claudeに書かせるときの依頼文と、検証用ラベルで試す手順、読み書き上限の見方をまとめました。

Gmailの受信メールを送信元や件名でラベル分けする処理は、GAS(Google Apps Script)のGmailAppだけで作れます。Claudeに書かせるなら、依頼文で決めておくことは3つです。どの検索クエリで拾うか、ラベルを付けたあとに受信トレイから外すか、最初はどのラベルに付けて試すか。この3つが曖昧だと、動くけれど仕分け済みのメールが二重に処理されるスクリプトが出てきます。

ここでは、ルールを表で渡してClaudeにコードを書かせ、検証用ラベルで試してから本番ラベルに切り替える流れを順に示します。claspでローカル開発する準備はClaude CodeでGAS開発を進める手順にあるので、この記事ではGmail側の書き方に絞ります。

仕分けに使うGmailAppの4つの部品

ラベル分けに必要なメソッドは多くありません。公式リファレンスでは、次の4つで足ります。

やりたいことメソッド補足
条件に合うスレッドを拾うメソッドGmailApp.search(query, start, max)補足クエリはGmailの検索欄に入力する書き方
ラベルを用意するメソッドGmailApp.createLabel(name) / getUserLabelByName(name)補足戻り値はGmailLabel
ラベルを付けるメソッドlabel.addToThreads(threads)補足1回のバッチは最大100スレッド
受信トレイから外すメソッドthread.moveToArchive()補足任意。付けるだけなら不要

単位に注意してください。searchが返すのはメッセージではなくスレッドで、ラベルもスレッド単位で付きます。同じスレッドに別の送信元のメールが混ざると、そのスレッド全体が同じラベルになります。

どのメソッドも、初回の実行時にhttps://mail.google.com/スコープの承認を求められます。公式リファレンスの各メソッドの項に、必要なスコープとして載っているものです。メール全体への権限になるので、承認画面で自分のアカウントのスクリプトであることを確認してから進めます。

Claudeに渡すルール表と依頼文

コードを書かせる前に、仕分けルールを表にします。Claudeには「いい感じに分けて」ではなく、検索クエリそのものを渡すほうが手戻りが少なくなります。

ラベル検索クエリ受信トレイから外す
auto-newsletter検索クエリfrom:(news@example.com OR info@example.org)受信トレイから外すする
auto-invoice検索クエリsubject:(請求書 OR 領収書)受信トレイから外すしない
auto-notify検索クエリfrom:(no-reply@example.net)受信トレイから外すする

宛先はダミーです。自分の受信箱で実際に届いているメールの送信元を見ながら、数件ずつ書き換えてください。検索クエリは「Gmailの検索欄に入れるのと同じ書き方」で書けるので、先にGmailの検索欄で試して、期待どおりのメールが出ることを確かめておくと、スクリプト側の原因切り分けが要らなくなります。

依頼文の例です。

GASでGmailの受信メールを自動でラベル分けしてください。
- ルールは次の表のとおり(label / query / archive の配列にする)
- 対象は受信トレイ内の直近1日(newer_than:1d)で、ラベル未付与のスレッドだけ
- DRY_RUN = true の間は、対象スレッドの件名と件数をLogger.logに出すだけで何も変更しない
- DRY_RUN = false のときは、ラベルが無ければ作り、100スレッド以下のバッチで付ける
- 1回の実行で処理するスレッドは最大200件で打ち切る
- ラベル名の先頭は auto- とし、既存のラベルは変更しない

ポイントは3つあります。ラベル未付与を条件にするのは、仕分け済みを再処理しないためです。実行の打ち切り件数は、後述の上限と6分制限に対する保険になります。DRY_RUNは、検証用ラベルより前の段階で「何件拾うか」だけを見るための切り替えです。

返ってくるコードの典型的な形

次のコードは、公式リファレンスのGmailApp.searchとGmailLabel.addToThreadsに沿って組んだ例です。Claudeの出力をそのまま再現したものではなく、上の依頼文を満たすコードがとる典型的な形として読んでください。

const DRY_RUN = true;        // false にすると実際にラベルを付ける
const MAX_THREADS = 200;     // 1回の実行で処理する上限
const RULES = [
  { label: 'auto-newsletter',
    query: 'from:(news@example.com OR info@example.org)', archive: true },
  { label: 'auto-invoice',
    query: 'subject:(請求書 OR 領収書)', archive: false },
];
 
function labelInbox() {
  let remaining = MAX_THREADS;
  for (const rule of RULES) {
    if (remaining <= 0) break;
    const q = `in:inbox newer_than:1d -label:${rule.label} ${rule.query}`;
    const threads = GmailApp.search(q, 0, remaining);
    remaining -= threads.length;
 
    if (DRY_RUN) {
      threads.forEach(t => Logger.log(`[dry] ${rule.label}: ${t.getFirstMessageSubject()}`));
      continue;
    }
    const label = GmailApp.getUserLabelByName(rule.label)
      || GmailApp.createLabel(rule.label);
    for (let i = 0; i < threads.length; i += 100) {
      label.addToThreads(threads.slice(i, i + 100));
    }
    if (rule.archive) threads.forEach(t => t.moveToArchive());
  }
}

-label:で自分のラベルを除いているので、2回目以降の実行は同じスレッドを拾いません。この除外を依頼文から落とすと、毎回同じメールを検索して読み書きの上限を食うコードになります。

検証用ラベルで本番前に試す

いきなり本番ラベルに付けると、クエリが広すぎたとき受信トレイから大量のメールが消えます。次の順で段階を踏むと、戻せない操作を避けられます。

手順

本番ラベルに切り替えるまでの4段階

  1. 1

    DRY_RUNで件数と件名を見る

    DRY_RUN = trueのままlabelInboxを実行し、実行ログに出た件名が想定どおりかを確認します。何も変更されません。

  2. 2

    ルール1本だけ検証用ラベルで実行する

    RULESを1行に減らし、ラベル名をauto-testに変え、archiveをfalseにします。MAX_THREADSも10程度に下げてからDRY_RUN = falseにします。

  3. 3

    Gmailで結果を確かめ、必要なら外す

    label:auto-testで検索し、付いたスレッドが意図どおりか確かめます。違っていれば、GmailApp.getUserLabelByName('auto-test').getThreads(0, 100)で取得し、removeFromThreadsで外せます。

  4. 4

    本番のラベル名とルールに戻す

    ルールを全部戻し、archiveの設定を元にして実行します。ここまで来て初めて受信トレイからの退避が起きます。

アーカイブを最後の段階まで残すのは、ラベルが付いただけなら受信トレイに残っていて、誤判定に気づきやすいからです。

1日の上限と6分制限を数える

GASには実行回数や処理量の上限があります。公式の割り当て表(Quotas)で、この処理に関わるのは次の項目です。

数字

個人のGoogleアカウント(gmail.com)の上限

  • メールの読み書き(送信を除く)

    20,000 / 日

    Workspaceは50,000 / 日

  • トリガーの合計実行時間

    90分 / 日

    Workspaceは6時間 / 日

  • 1回の実行時間

    6分

    どちらのアカウントも同じ

Google Workspaceアカウントでは値が大きくなります

割り当ては「ユーザーごとで、最初のリクエストから24時間後にリセット」と説明されています。超えるとスクリプトが例外を投げて止まります。表の値は予告なく変わりうると明記されているので、実運用の前に最新の表を開いてください。

1スレッドへのラベル付けが読み書きの何回分に数えられるかは、公式の表に書かれていません。そのため、設計では次のように見積もると安全側に寄せられます。

  • 1回の実行はMAX_THREADSで頭打ちにしておく。検索で読む分と、ラベル付けとアーカイブで書く分の両方が数えられる前提で、1日の実行回数を掛けても2万に届かない値にする
  • 時間主導トリガーは最短で1分おきにできますが、トリガーの合計実行時間が日に90分(個人アカウント)なので、1回10秒かかる処理を1分おきに回すと1時間に約10分を消費するため、約9時間で枠を使い切ります
  • 初回に過去のメールを全部仕分けたいときは、newer_than:1dを外した一度きりの実行に分け、6分に収まらなければ6分制限を分割実行に直す手順の方法で続きから再開させます

メールの送信はこの処理に含まれませんが、仕分けに加えて通知メールを送る改造をするときは、送信の宛先数が個人アカウントで1日100通までという別の枠に当たります。

定期実行は時間主導トリガーで足りる

仕分けをユーザーの操作なしに回すには、時間主導トリガーを1本作ります。公式のトリガーの説明では、時間主導トリガーはcronのようなもので、最短1分おき、最長で月1回の間隔にできます。1時間おきなら次の1行です。

function installTrigger() {
  ScriptApp.newTrigger('labelInbox').timeBased().everyHours(1).create();
}

installTriggerは一度だけ実行します。毎回実行すると同じトリガーが増えるので、依頼文には「作成前に同名の関数を呼ぶ既存トリガーを削除する」と一行足すと安全です。インストール型トリガーは作成したアカウントの権限で動きます。実行したあとは、エディタの「トリガー」画面を開き、同じ関数のトリガーが1本だけ登録されているかを確かめてください。

この構成では、Claudeは仕分けの実行時には関わりません。Claudeが担うのはコードを書く段階だけで、毎時の実行コストはGoogle側の割り当ての中に収まります。メールを読んで内容で判断させたい場合は、ClaudeのGmailコネクタのように会話の中で都度扱う方法が合います。差出人や件名で決まる規則的な仕分けは、GASのほうが向いています。

GASプロジェクトに置くCLAUDE.mdの書き方

同じ指示を毎回の依頼文に書くのが面倒になったら、プロジェクトのCLAUDE.mdに移します。公式のメモリのページは、指示を「検証できる具体さ」で書くこと、1ファイルは200行以内を目安にすることを勧めています。GAS向けなら次のような内容です。

# Gmail仕分けGAS
 
- ラベル付けは必ず DRY_RUN 定数で切り替えられるようにする。初期値は true
- GmailApp.search は max を必ず指定する(未指定の全件取得を書かない)
- 追加するラベル名は auto- で始め、既存ラベルの削除・改名はしない
- 変更後は clasp push まで実行し、DRY_RUN=true の実行ログ確認は人が行う
- moveToTrash / sendEmail / reply は使わない

CLAUDE.mdは「強制される設定」ではなく文脈として読まれるため、守られなかったときの確認が要ります。メモリのページは、/contextで読み込まれているかを見ることと、必ず実行したい処理はhooksにすることを案内しています。例えば「ファイルを編集するたびに型チェックを走らせる」といった処理は、PostToolUse hookに回すほうが確実です。

よくあるつまずき

落とし穴

同じスレッドに複数のルールが当たる

searchはスレッド単位なので、送信元が違うメールが同じスレッドに混ざると、先に処理したルールのラベルが付きます。ルール表は、より限定的な条件を上に並べると意図が通りやすくなります。

落とし穴

DRY_RUNを戻し忘れてトリガーが空回りする

DRY_RUN = trueのままトリガーを作ると、毎時ログを出すだけで何も仕分けません。実行ログの[dry]行が残っていないかを、本番切り替えの確認項目に入れてください。

落とし穴

既存のラベル名と衝突する

auto-のような接頭辞を決めておかないと、手動で作ったラベルに自動処理の結果が混ざり、どれを外せばよいか分からなくなります。

仕分けの条件を変えたくなったら、Claudeへの依頼は「RULESの表に1行足して」だけで済みます。検索クエリを自分でGmailに入れて確かめ、DRY_RUNと検証用ラベルを通してから本番に混ぜる、この順を崩さないことが、受信箱を壊さない一番の近道です。issueにラベルを付ける同種の自動化なら、claude-code-actionでissueにラベルを自動で付ける手順も、条件を先に決める点で共通しています。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developers.google.com/apps-script/reference/gmail/gmail-app
  2. developers.google.com/apps-script/reference/gmail/gmail-label
  3. developers.google.com/apps-script/guides/services/quotas
  4. developers.google.com/apps-script/guides/triggers/installable
  5. code.claude.com/docs/en/memory
この記事を共有:XはてブLinkedIn