Gmailの自動ラベルが競合する前に確認|GASで仕分けルールをdry-run監査

Gmailの自動ラベルが競合する前に確認|GASで仕分けルールをdry-run監査

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

「請求」と「請求書」のような似た条件を増やすと、同じメールが複数ルールに当たり、どのラベルを優先するか分からなくなることがあります。いきなり受信トレイへ適用する前に、まずルール表だけを検査して止める方法を紹介します。

この記事のコードはGmailを読み取らず、ラベルも作成・変更しません。スプレッドシート上の仕分けルールから、空欄、優先順位の重複、キーワードの重なりを確認する無料範囲の例です。

先に知っておきたいこと

  • できること:ルール表の入力不備と競合候補をdry-runで洗い出す
  • しないこと:メール検索、本文取得、ラベル作成、ラベル付与、送信、削除
  • 必要なもの:GoogleスプレッドシートとApps Script
  • 安全設計:ルール表を更新せず、キーワードやラベル名をログへ出さない

準備するシート

シート名を「LabelRules」にして、1行目へ次の列を用意します。

  • A列:priority(1から999の整数。小さいほど先に判定)
  • B列:keyword(件名判定に使う文字列)
  • C列:label(付けたいラベル名)
  • D列:enabled(TRUEまたはFALSE)
  • E列:note(任意のメモ)

サンプルは「10/請求書/経理/請求/TRUE」「20/請求/経理/確認/TRUE」のように入力します。この2行はキーワードが重なるため、監査結果では競合候補になります。

コピーして使える最小コード

function auditGmailLabelRuleConflictsFree() {
  const CONFIG = {
    SHEET_NAME: 'LabelRules',
    MAX_RULES: 200,
    DRY_RUN: true
  };

  if (CONFIG.DRY_RUN !== true) {
    throw new Error('安全のためDRY_RUN=trueで実行してください。');
  }

  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(CONFIG.SHEET_NAME);
  if (!sheet) {
    throw new Error('LabelRulesシートが見つかりません。');
  }

  const values = sheet.getDataRange().getDisplayValues();
  if (values.length < 2) {
    throw new Error('監査するルールがありません。');
  }

  const headers = values[0].map(normalizeRuleText_);
  const required = ['priority', 'keyword', 'label', 'enabled'];
  required.forEach(function(name, index) {
    if (headers[index] !== name) {
      throw new Error('1行目の列名または列順を確認してください。');
    }
  });

  const rows = values.slice(1);
  if (rows.length > CONFIG.MAX_RULES) {
    throw new Error('一度に監査できるのは200行までです。');
  }

  const issues = [];
  const activeRules = [];
  const priorityRows = new Map();
  const keywordRows = new Map();

  rows.forEach(function(row, index) {
    const rowNumber = index + 2;
    const priorityText = String(row[0] || '').trim();
    const keyword = String(row[1] || '').trim();
    const label = String(row[2] || '').trim();
    const enabledText = normalizeRuleText_(row[3]);
    const isBlank = !priorityText && !keyword && !label && !enabledText;
    if (isBlank) return;

    if (!['true', 'false'].includes(enabledText)) {
      issues.push({ row: rowNumber, type: 'ENABLED_FORMAT' });
      return;
    }
    if (enabledText === 'false') return;

    const priority = Number(priorityText);
    if (!Number.isInteger(priority) || priority < 1 || priority > 999) {
      issues.push({ row: rowNumber, type: 'PRIORITY_INVALID' });
    }
    if (!keyword || keyword.length > 100) {
      issues.push({ row: rowNumber, type: 'KEYWORD_INVALID' });
    }
    if (!label || label.length > 225 || /[\u0000-\u001F]/.test(label)) {
      issues.push({ row: rowNumber, type: 'LABEL_INVALID' });
    }
    if (!Number.isInteger(priority) || !keyword || !label) return;

    const normalizedKeyword = normalizeRuleText_(keyword);
    if (priorityRows.has(priority)) {
      issues.push({ row: rowNumber, type: 'PRIORITY_DUPLICATE', withRow: priorityRows.get(priority) });
    } else {
      priorityRows.set(priority, rowNumber);
    }
    if (keywordRows.has(normalizedKeyword)) {
      issues.push({ row: rowNumber, type: 'KEYWORD_DUPLICATE', withRow: keywordRows.get(normalizedKeyword) });
    } else {
      keywordRows.set(normalizedKeyword, rowNumber);
    }
    activeRules.push({ row: rowNumber, priority: priority, keyword: normalizedKeyword });
  });

  for (let i = 0; i < activeRules.length; i++) {
    for (let j = i + 1; j < activeRules.length; j++) {
      const a = activeRules[i];
      const b = activeRules[j];
      if (a.keyword !== b.keyword && (a.keyword.includes(b.keyword) || b.keyword.includes(a.keyword))) {
        issues.push({ row: b.row, type: 'KEYWORD_OVERLAP', withRow: a.row });
      }
    }
  }

  const summary = {
    dryRun: true,
    scannedRows: rows.length,
    activeRules: activeRules.length,
    issueCount: issues.length,
    issues: issues
  };
  Logger.log(JSON.stringify(summary));
  return summary;
}

function normalizeRuleText_(value) {
  return String(value || '').normalize('NFKC').trim().toLowerCase();
}

実行前の準備

1. 実データではなく、シートをコピーして少量のサンプルを入力します。

2. スプレッドシートの「拡張機能」からApps Scriptを開き、コードを貼り付けます。

3. auditGmailLabelRuleConflictsFree を選び、実行します。

4. 実行ログのissueCountと、issuesに出た行番号・種類を確認します。

5. 元のルール表を手作業で直し、issueCountが0になるまで再実行します。

結果の読み方

  • PRIORITY_INVALID:優先順位が整数ではない、または範囲外
  • PRIORITY_DUPLICATE:同じ優先順位が複数ある
  • KEYWORD_INVALID:キーワードが空欄、または長すぎる
  • KEYWORD_DUPLICATE:表記を正規化すると同じキーワードになる
  • KEYWORD_OVERLAP:「請求」と「請求書」のように包含関係がある
  • LABEL_INVALID:ラベル名が空欄、長すぎる、または制御文字を含む
  • ENABLED_FORMAT:TRUE/FALSE以外が入力されている

ログには行番号とエラー種別だけを出します。キーワードやラベル名をそのまま記録しないため、社内用の分類語がログへ残りにくい構成です。

制限と安全上の注意

  • このコードはGmail検索条件として正しいかを判定しません。
  • Gmail上に同名ラベルが存在するか、実際のメールが何件一致するかは確認しません。
  • キーワードの包含は候補を広めに出すため、意図した重なりも警告されます。
  • 大文字・小文字、全角・半角を正規化して比較しますが、言葉の意味までは判断しません。
  • 実際に自動ラベルを付ける処理へ進む場合も、少量のテスト、件数上限、停止条件、手動確認を別途用意してください。
  • メール本文、差出人、宛先、認証情報をログへ出さないでください。

停止条件

  • issueCountが1件以上なら自動適用へ進まない
  • ルールが200行を超えるなら対象を分割する
  • 権限要求や対象シートが想定と違う場合は実行を止める
  • 競合の優先順位を説明できない場合は担当者へ確認する

まとめ

Gmailの自動仕分けは、メールへ触る前にルール表の競合を減らすと安全に始められます。まずは読み取り専用のdry-runで行番号と問題の種類だけを確認し、意図しない多重一致を手作業で解消してください。

メール作業を「送信せず」下書きから整える有料Tipsを見る

Gmail下書きテンプレGAS Proを見る

GasおじのTips商品一覧を見る

GasおじのTips商品一覧を見る

※学習・検証用の例です。実データへ適用する前にコピー環境で確認してください。Google公式・Google公認の商品ではありません。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事のライター

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

このライターが書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    2 %獲得
    (59 円相当)
  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    2 %獲得
    (59 円相当)
  • Googleフォームの採点ミスを回答公開前に発見|GASで正解キーをdry-run監査

関連のおすすめ記事

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    2 %獲得
    (10 円相当)
    SmartSenior

    SmartSenior

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    2 %獲得
    (59 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    2 %獲得
    (59 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化