2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gmailフィルタをスプレッドシートで一括管理する完全ガイド

Posted at

Gmailフィルタをスプレッドシートで一括管理する完全ガイド

はじめに

Gmailのフィルタは、受信メールを自動で仕分け・ラベル付けしたり転送したりする強力な機能ですが、通常はウェブの設定画面から1件ずつ作成・編集する必要があります。フィルタが増えると管理が大変になりがちです。本ガイドでは Google Apps Script (GAS) を用いて、Googleスプレッドシート上でGmailフィルタを一括管理し、Gmail API(高度なサービス)を活用してフィルタの一括作成・更新・削除を行う方法を解説します。スプレッドシートを「フィルタ設定の台帳」として使うことで、多数のフィルタを効率よく整理・編集できます。

以下の内容を順を追って説明します。

  • Gmail API(高度なサービス)の有効化方法
  • スプレッドシートの準備とフォーマット例
  • フィルタ一括管理用のGASスクリプト(コピー&ペーストでそのまま動作するコード)
  • コードの詳細な解説(フィルタの作成・更新・削除処理)
  • Gmail APIのクォータ制限とエラーハンドリングのベストプラクティス
  • 運用上のベストプラクティス(フィルタ数上限への対策や管理手法)

このガイドに沿って設定すれば、スプレッドシートでフィルタ条件やアクションを編集し、スクリプト実行によりGmailに反映させるという一連の流れを実現できます。

準備

1. Gmail API(高度なサービス)の有効化

今回のスクリプトでは、標準のGmailAppサービスでは扱えない「フィルタ」を操作するためにGmail APIの高度なサービスを利用します (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。高度なサービスを使うには、スクリプトエディタでの有効化と、Google Cloud側で該当APIを有効化する手順が必要です。具体的には次のように進めます (How to modify this existing post of script and/or steps so it functions to automatically empty Gmail Trash? - Web Applications Stack Exchange):

  1. スプレッドシートを開き、メニューから 拡張機能 > Apps Script を選択してスクリプトエディタを開きます。
  2. スクリプトエディタ画面で 「サービス」 または 「リソース > 高度な Google サービス」 を開き、Gmail API を探して「オン」にします。
  3. (旧エディタの場合)同じダイアログ内に 「Google Cloud プロジェクト」 へのリンクが表示されるのでクリックします。ブラウザでGoogle Cloud Consoleが開いたら、該当プロジェクトで Gmail API を検索し、「有効にする」をクリックします (How to modify this existing post of script and/or steps so it functions to automatically empty Gmail Trash? - Web Applications Stack Exchange)。

以上で、GASからGmail APIを呼び出す準備が整います。これによりスクリプト内でGmail.Users.Settings.Filtersといったクラスが使用可能になります。もしこれらの手順を怠ると、スクリプト実行時に「Gmail API is not enabled」等のエラーが発生しますので注意してください。

2. スプレッドシートの準備とフォーマット

次に、Gmailフィルタの設定内容を記述するスプレッドシートを用意します。シートにはフィルタの条件や動作を表す列を作成し、1行が1つのフィルタに対応するようにします。以下はフォーマット例です。

  • シート名: 「Gmailフィルタ一覧」(任意の名前で構いません)
  • ヘッダー行 (1行目): 各列にフィルタ項目のタイトルを付けます。例として以下の列を用意します。
A列 (FilterID) B列 (From) C列 (To) D列 (Subject) E列 (HasWords) F列 (NotWords) G列 (HasAttach) H列 (Label) I列 (MarkRead) J列 (Archive) K列 (Delete) L列 (ForwardTo) M列 (Important) N列 (Category)
(フィルタID) 送信者(From)条件 宛先(To)条件 件名(Subject)条件 含む言葉(本文など) 含まない言葉 添付ファイル有無 適用するラベル名 既読にする 受信トレイをスキップ 削除する 転送先アドレス 重要マーク指定 カテゴリ振り分け

2行目以降に各フィルタの設定を入力します。主な列の意味は以下の通りです。

  • FilterID (フィルタID): 既存フィルタの一意なID文字列。新規作成の場合は空白のままにし、既存フィルタを更新する場合はそのフィルタのIDを入れます。フィルタIDは後述の方法で取得できます。
  • From / To / Subject: フィルタ条件としての送信者、宛先、件名です。必要な条件のみ入力し、不要な列は空白にしてください。複数のアドレスやキーワードを指定したい場合、Gmailの検索演算子 (ORAND 等) が利用可能です (Create Bulk Gmail Filters from Sheets - xFanatical)。例えばFromalice@example.com OR bob@example.comと書けば、いずれかのアドレスからのメールが条件にマッチします。
  • HasWords / NotWords: メール本文や件名等に「含む語句」「含まない語句」の条件です。こちらも必要に応じて入力します(複数語句はスペース区切り、フレーズは引用符で括るなど、Gmail検索と同じ形式で指定可能です)。
  • HasAttach (添付あり): 添付ファイルの有無で条件指定する場合にTRUE/FALSEを入力します。TRUEなら「添付あり」が条件になります。不要なら空白のままで構いません。
  • Label (ラベル): フィルタのアクション「ラベルを付ける」で適用するラベル名を指定します。ここにはGmail上に存在するラベル名を正確に記入してください。スクリプトはこの名前に対応するラベルIDを自動取得します。ラベルが存在しない場合はスクリプトが新規作成します。
  • MarkRead (既読にする): この列にTRUEを入れるとフィルタ動作で「メールを既読にする」を有効化します。空白またはFALSEなら既読にはしません。
  • Archive (受信トレイをスキップ): TRUEの場合、フィルタ動作で「受信トレイをスキップ(アーカイブ)」を有効にします (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。
  • Delete (削除): TRUEの場合、フィルタ動作で「削除」を有効にします(ゴミ箱に移動)。
  • ForwardTo (転送): 転送先メールアドレスを記入すると、「そのアドレスに転送」を有効にします。ただし、このアドレスはあらかじめGmailの設定で転送用アドレスとして登録済みである必要があります(未登録の場合、APIでフィルタ作成時にエラーになります)。
  • Important (重要マーク): alwaysneverなどと指定すると「常に重要マークを付ける」「絶対に重要マークを付けない」をそれぞれ設定できます。空白の場合、特に指定しません。※注意: Gmailの仕様上、「常に」と「絶対に」の両方を同時に設定することはできません。どちらか片方のみ指定してください (Create Bulk Gmail Filters from Sheets - xFanatical)。
  • Category (カテゴリ): プライマリ、ソーシャル、プロモーションなど特定のカテゴリタブに振り分ける場合にそのカテゴリ名を指定します(例: promotions)。指定しない場合は空白で構いません。認識可能なカテゴリは Gmailの5カテゴリ(Primary, Social, Updates, Promotions, Forums)のみです (Create Bulk Gmail Filters from Sheets - xFanatical)。誤った値を入れるとフィルタ作成APIがエラーを返すので注意してください。

以上は一例です。必要に応じて列を追加・削除したり、利用しない列は無視しても構いません。シートの1行に書かれた条件すべてを満たすメールに対し、同じ行のアクション列で指定された動作が適用されるフィルタが作成されます。

フィルタIDの取得方法: 既存のフィルタを更新したい場合、そのフィルタのIDを調べてシートに入力する必要があります。フィルタIDはGASから次のコードを一度実行することで確認できます。

// Gmailのフィルタ一覧と各IDをログ出力する
function listFilters() {
  const filters = Gmail.Users.Settings.Filters.list('me').filter;
  filters.forEach(f => console.log(`FilterID: ${f.id}, Criteria: ${f.criteria}, Action: ${f.action}`));
}

実行後、GASのログにすべてのフィルタIDと内容が出力されます。その中から該当のフィルタを見つけてIDをコピーし、シートのFilterID列に貼り付けてください (Gmailのフィルタをスプレッドシートで管理する #spreadsheet - Qiita) (Gmailのフィルタをスプレッドシートで管理する #spreadsheet - Qiita)。手間がかかる場合は、まず既存フィルタをスプレッドシートに一覧出力するようなスクリプトを書くのも良いでしょう。

フィルタ管理スクリプトの実装

準備ができたら、いよいよGASのコードを作成します。以下に本ガイドの主題であるフィルタ一括管理スクリプトを示します。このコードをそのままApps Scriptエディタにコピーして使用できます。

/***************************************
 * Gmailフィルタ一括管理スクリプト
 * (スプレッドシートとGmail APIを使用)
 ***************************************/

// スプレッドシートとシート名の指定
const SPREADSHEET_ID = '【ここにスプレッドシートIDを入力】';  
const SHEET_NAME = 'Gmailフィルタ一覧';  // シート名を合わせる

/**
 * スプレッドシートのフィルタ定義を読み込み、Gmailのフィルタを一括で作成・更新・削除するメイン関数
 */
function syncGmailFilters() {
  const ss = SpreadsheetApp.openById(SPREADSHEET_ID);
  const sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) {
    throw new Error(`シート「${SHEET_NAME}」が見つかりません。`);
  }

  // シートのデータ範囲を取得(1行目はヘッダーとしてスキップ)
  const data = sheet.getDataRange().getValues();
  if (data.length < 2) {
    console.log("シートにフィルタ定義がありません。");
    return;
  }
  const headers = data[0];
  const rows = data.slice(1);

  // Gmail上の既存フィルタ一覧を取得
  const existingFilters = Gmail.Users.Settings.Filters.list('me').filter || [];
  // 既存フィルタIDをキーにしたマップを作成
  const existingFilterMap = {};
  existingFilters.forEach(f => { existingFilterMap[f.id] = f; });

  // Gmail上の既存フィルタIDの集合(後で削除判定に使用)
  const existingFilterIds = new Set(existingFilters.map(f => f.id));

  // Gmail上のラベル一覧を取得し、ラベル名→IDのマップを作成
  const labelMap = {};
  const labelList = Gmail.Users.Labels.list('me').labels || [];
  labelList.forEach(label => { labelMap[label.name] = label.id; });

  // 操作結果を記録するための配列(後でシートに書き戻し)
  const results = [];

  // シート上の各フィルタ定義について処理
  rows.forEach((row, index) => {
    // 行番号(ヘッダーからのオフセットではなくシート上の行番号に直す: ヘッダーが1行なので +2)
    const rowNumber = index + 2;
    try {
      // シートの列データを読み取り
      const filterId    = row[headers.indexOf('FilterID')]    || "";  // フィルタID(空なら新規)
      const from        = row[headers.indexOf('From')]        || "";
      const to          = row[headers.indexOf('To')]          || "";
      const subject     = row[headers.indexOf('Subject')]     || "";
      const hasWords    = row[headers.indexOf('HasWords')]    || "";
      const notWords    = row[headers.indexOf('NotWords')]    || "";
      const hasAttach   = row[headers.indexOf('HasAttach')]   || "";
      const labelName   = row[headers.indexOf('Label')]       || "";
      const markRead    = row[headers.indexOf('MarkRead')]    || "";
      const archive     = row[headers.indexOf('Archive')]     || "";
      const del         = row[headers.indexOf('Delete')]      || "";
      const forwardTo   = row[headers.indexOf('ForwardTo')]   || "";
      const important   = row[headers.indexOf('Important')]   || "";
      const category    = row[headers.indexOf('Category')]    || "";

      // シート行が空(全列未入力)ならスキップ
      const isEmpty = row.slice(0, headers.length).every(val => val === "" || val === false);
      if (isEmpty) {
        return; // 何も設定がなければ何もしない
      }

      // フィルタ作成/更新用オブジェクトを構築
      const filterResource = Gmail.newFilter();
      const criteria = Gmail.newFilterCriteria();
      const action   = Gmail.newFilterAction();

      // --- [1] フィルタ条件 (criteria) の設定 ---
      if (from)       criteria.from = from;
      if (to)         criteria.to = to;
      if (subject)    criteria.subject = subject;
      if (hasWords)   criteria.query = hasWords;
      if (notWords)   criteria.negatedQuery = notWords;
      if (hasAttach)  criteria.hasAttachment = true;
      // ※サイズや日時など追加の条件が必要なら criteria.size や criteria.sizeComparison も指定可能

      // 条件が何も指定されていない場合はエラー(Gmailは空のフィルタ条件を許可しない)
      const noCriteria = !from && !to && !subject && !hasWords && !notWords && !hasAttach;
      if (noCriteria) {
        throw new Error("フィルタ条件が1つも指定されていません。From/To/Subject/HasWordsなど最低1つは指定してください。");
      }

      filterResource.criteria = criteria;

      // --- [2] フィルタ動作 (action) の設定 ---
      const labelIds = [];
      if (labelName) {
        // ラベル名が指定されている場合、そのIDを取得(なければ新規作成)
        let labelId = labelMap[labelName];
        if (!labelId) {
          // ラベルが存在しない場合は新規作成する
          const newLabel = Gmail.Users.Labels.create({ name: labelName }, 'me');
          labelId = newLabel.id;
          // マップに追加して次回以降使えるように
          labelMap[labelName] = labelId;
          console.log(`ラベル '${labelName}' を新規作成しました (ID: ${labelId})`);
        }
        labelIds.push(labelId);
      }
      if (labelIds.length > 0) {
        action.addLabelIds = labelIds;
      }
      // 受信トレイをスキップ(アーカイブ)
      if (archive === true || archive === "TRUE" || archive === "true") {
        action.removeLabelIds = action.removeLabelIds || [];
        action.removeLabelIds.push("INBOX");
      }
      // ゴミ箱に移動(削除)
      if (del === true || del === "TRUE" || del === "true") {
        action.removeLabelIds = action.removeLabelIds || [];
        action.removeLabelIds.push("TRASH");
      }
      // 既読にする
      if (markRead === true || markRead === "TRUE" || markRead === "true") {
        action.markRead = true;
      }
      // スターを付ける(必要なら)
      // if (...) { action.star = true; }
      // 常に重要 or 重要にしない
      if (important) {
        if (important.toString().toLowerCase() === "always") {
          action.alwaysMarkAsImportant = true;
        } else if (important.toString().toLowerCase() === "never") {
          action.neverMarkAsImportant = true;
        }
      }
      // カテゴリ振り分け
      if (category) {
        action.categoryLabel = category.toUpperCase(); // Gmail APIは大文字表記のカテゴリラベルを期待
      }
      // 転送
      if (forwardTo) {
        action.forward = forwardTo;
      }

      filterResource.action = action;

      if (filterId && filterId !== "") {
        // --- [3] 既存フィルタの更新(削除して新規作成) ---
        if (existingFilterIds.has(filterId)) {
          // 同じIDの既存フィルタがある場合は一度削除してから新規作成
          Gmail.Users.Settings.Filters.remove('me', filterId);
          const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
          // シートのFilterIDを新しいIDに置き換え
          sheet.getRange(rowNumber, headers.indexOf('FilterID')+1).setValue(newFilter.id);
          results.push([ "UPDATED", `フィルタID:${filterId} を更新しました(新ID:${newFilter.id})` ]);
        } else {
          // シートにIDがあるが既存になければ、新規として作成(IDセルは古い値を無視)
          const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
          sheet.getRange(rowNumber, headers.indexOf('FilterID')+1).setValue(newFilter.id);
          results.push([ "CREATED", `新規フィルタを作成しました(ID:${newFilter.id})` ]);
        }
      } else {
        // --- [4] 新規フィルタの作成 ---
        const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
        // 作成後、取得したフィルタIDをシートに書き込んで今後の更新に備える
        sheet.getRange(rowNumber, headers.indexOf('FilterID')+1).setValue(newFilter.id);
        results.push([ "CREATED", `新規フィルタを作成しました(ID:${newFilter.id})` ]);
      }
    } catch (err) {
      // エラー発生時は結果配列に記録し、処理を継続
      const msg = err.message || err.toString();
      results.push([ "FAILED", `行${rowNumber}: ${msg}` ]);
      console.error(`行${rowNumber}の処理中にエラー: ${msg}`);
    }
  }); // forEach row

  // --- [5] スプレッドシートに存在しないフィルタをGmailから削除 ---
  // (スプレッドシート上に定義がない既存フィルタを削除する処理。必要に応じて有効化)
  const deleteUnlisted = false;  // この機能を使う場合は true に変更
  if (deleteUnlisted) {
    existingFilters.forEach(f => {
      const fid = f.id;
      // シート上のFilterID一覧に存在しないものを削除
      const listedIds = rows.map(r => r[headers.indexOf('FilterID')]);
      if (listedIds.indexOf(fid) === -1) {
        Gmail.Users.Settings.Filters.remove('me', fid);
        results.push([ "DELETED", `シートに無いためフィルタID:${fid} を削除しました` ]);
      }
    });
  }

  // --- [6] 処理結果をシートの隣シートやログに出力(必要に応じて) ---
  // ここでは結果をログ出力。必要ならシートに書き込むことも可能。
  results.forEach(r => console.log(`${r[0]}: ${r[1]}`));
}

上記のスクリプトは、スプレッドシートに入力されたフィルタ定義とGmail上の既存フィルタを突き合わせ、以下の動作を行います。

  • 新規作成: FilterID列が空で有効な条件が指定されている行について、新しくフィルタを作成し、得られたフィルタIDをシートに書き戻します。
  • 更新: FilterID列に既存フィルタIDが書かれている行について、そのフィルタを一旦削除し、新たな条件で再作成します(結果としてフィルタIDは更新されます)。シートのFilterIDも新しいIDに更新します。
  • エラー処理: 各行の処理でエラーが発生しても、他の行の処理は継続します。エラー内容はログや結果配列に記録されます。
  • 既存削除(オプション): スプレッドシートに存在しないフィルタを自動削除する機能をオプションで含めています。deleteUnlisted変数をtrueにすると、現在Gmailに存在するフィルタのうちシート上に定義がないものを削除します。(※誤って必要なフィルタを消さないよう注意してください)

次の節で、このスクリプトの各部分について詳しく解説します。

コードの解説

上記コードを理解し、自分の要件に合わせてカスタマイズできるよう、ポイントごとに説明します。

(A) 既存フィルタ・ラベルの読み込み

  const existingFilters = Gmail.Users.Settings.Filters.list('me').filter || [];
  // ...(省略)...
  const labelList = Gmail.Users.Labels.list('me').labels || [];

Gmail.Users.Settings.Filters.list('me')で現在ログインユーザー('me'指定)の全フィルタ一覧を取得しています (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。結果オブジェクトのfilterプロパティにフィルタ配列が含まれるため、それをexistingFiltersに格納しています。同様にGmail.Users.Labels.list('me')で全ラベルを取得し、labelListに格納しています。このようにまとめて一覧取得することで、後続の処理で個別に存在確認する際に毎回APIを呼ぶ必要がなく、APIコールの最適化になります。

※Gmail API経由ではラベル名ではなくラベルIDでフィルタのラベル指定を行う必要があります。例えば「INBOX」「TRASH」などシステムラベルも含め、すべて固有のIDで管理されています。そこで、labelMapにラベル名→IDのマップを作成し、ユーザーがシートで指定したラベル名から対応するIDを簡単に参照できるようにしています。

(B) フィルタ定義の読み取りとフィルタオブジェクト構築

      const filterResource = Gmail.newFilter();
      const criteria = Gmail.newFilterCriteria();
      const action   = Gmail.newFilterAction();

各シート行について処理する中で、新しいフィルタ設定用のオブジェクトを作成しています。Gmail.newFilterCriteria()Gmail.newFilterAction()は高度なGmailサービスで提供されるフィルタ条件・動作オブジェクトのファクトリーメソッドです。これに対し、シートの各列の値をセットしていきます。

      if (from)       criteria.from = from;
      if (to)         criteria.to = to;
      if (subject)    criteria.subject = subject;
      if (hasWords)   criteria.query = hasWords;
      if (notWords)   criteria.negatedQuery = notWords;
      if (hasAttach)  criteria.hasAttachment = true;
      // ...
      filterResource.criteria = criteria;

上記はフィルタ条件 (criteria) の設定です。例えばFrom列が非空ならcriteria.fromにその値を設定します。それ以外にもcriteria.queryはGmail検索クエリに相当する条件全文を指定できますが、ここではシンプルに「含む言葉」をqueryに、「含まない言葉」をnegatedQueryに割り当てています。criteria.hasAttachmentは真偽値で指定し、TRUEをセットすると「添付ファイルあり」にマッチする条件になります。これらのcriteriaプロパティに何も設定しない場合、その条件は無視されます。

なお、いずれの条件も指定されなかった場合は無効なフィルタになるため、コード内で検出してエラーを投げています。最低1つは条件を設定する必要がある点に注意してください。

      const labelIds = [];
      if (labelName) {
        // ...(ラベルID取得または新規作成)
        labelIds.push(labelId);
      }
      if (labelIds.length > 0) {
        action.addLabelIds = labelIds;
      }
      // アーカイブ(INBOX除去)
      if (archive === true || archive === "TRUE" || archive === "true") {
        action.removeLabelIds = action.removeLabelIds || [];
        action.removeLabelIds.push("INBOX");
      }
      // ゴミ箱(TRASH)
      if (del === true || del === "TRUE" || del === "true") {
        action.removeLabelIds = action.removeLabelIds || [];
        action.removeLabelIds.push("TRASH");
      }
      // 既読
      if (markRead === true || markRead === "TRUE" || markRead === "true") {
        action.markRead = true;
      }
      // 重要マーク
      if (important) { ... }
      // カテゴリ
      if (category) {
        action.categoryLabel = category.toUpperCase();
      }
      // 転送
      if (forwardTo) {
        action.forward = forwardTo;
      }
      filterResource.action = action;

こちらはフィルタ動作 (action) の設定です。Gmail APIのフィルタ動作は、addLabelIds(付与するラベルID配列)、removeLabelIds(外すシステムラベルID配列)、markRead(既読フラグ)、star(☆スター付与)、forward(転送アドレス)、alwaysMarkAsImportant/neverMarkAsImportantcategoryLabel(カテゴリ適用)等で指定します (Create a native filter in Gmail using Google Apps Script - Stack Overflow) (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。

上記コードではシートから読み取った各フラグに応じてこれらを設定しています。

  • ラベル付け: action.addLabelIdsにラベルIDの配列を設定します。ラベル名はシートから取得した後、事前に用意したlabelMapでIDに変換しています。なければGmail.Users.Labels.createで新規作成し (Create a native filter in Gmail using Google Apps Script - Stack Overflow)、そのIDを使用しています。こうすることで、スクリプト実行前にラベルを手動準備していなくても、自動的に不足ラベルを作ってくれます。
  • アーカイブ: Gmailでは「受信トレイをスキップ」は内部的にINBOXラベルを外すという操作になります (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。そのためaction.removeLabelIds"INBOX"を指定しています(removeLabelIdsは配列なので、他の動作と共存できるようにpushで追加しています)。
  • 削除: 「ゴミ箱に移動」も同様に、システムラベル"TRASH"を付与(正確にはスレッドからINBOXを外しTRASHを付与する)形で実現します (Gmailのフィルタをスプレッドシートで管理する #spreadsheet - Qiita)。したがってremoveLabelIds"TRASH"を追加しています。
  • 既読: action.markRead = trueを設定するだけでOKです。
  • 重要マーク: コードでは列の値が文字列 "always"(大文字小文字区別なし)ならalwaysMarkAsImportant"never"ならneverMarkAsImportantをtrue設定しています。GmailのフィルタUI上ではチェックボックスで2択になっていますが、APIでは2つのプロパティで制御する点に注意してください(両方trueは論理矛盾なので避ける)。
  • カテゴリ振り分け: Gmail APIではカテゴリを指定する場合、例えばcategoryLabel = "PROMOTIONS"のように大文字表記のカテゴリ名を指定します。今回は便宜上、シートでは小文字のpromotions等で指定できるようにし、スクリプト側で.toUpperCase()しています。
  • 転送: 転送先アドレス文字列をaction.forwardに設定します。このアドレスは既に述べたようにGmail設定で有効化済みである必要があります。未許可のアドレスを指定するとフィルタ作成API呼び出し時にエラーとなるでしょう。

以上で、filterResource.criteriafilterResource.actionが揃ったことになり、フィルタ作成の準備が整います。

(C) フィルタの新規作成・更新

      if (filterId && filterId !== "") {
        // (既存フィルタ更新処理)
        if (existingFilterIds.has(filterId)) {
          Gmail.Users.Settings.Filters.remove('me', filterId);
          const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
          // ...(省略)...
          results.push([ "UPDATED", `フィルタID:${filterId} を更新しました...` ]);
        } else {
          // (IDがあるが既存に存在しない場合:新規作成扱い)
          const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
          // ... 
          results.push([ "CREATED", `新規フィルタを作成しました...` ]);
        }
      } else {
        // (新規フィルタ作成処理)
        const newFilter = Gmail.Users.Settings.Filters.create(filterResource, 'me');
        // ...
        results.push([ "CREATED", `新規フィルタを作成しました...` ]);
      }

ここではフィルタIDの有無で分岐し、既存フィルタの更新か新規作成かを判断しています。

  • FilterIDが空であれば新規作成: Gmail.Users.Settings.Filters.create(filterResource, 'me') を呼び出し、フィルタを作成します (Create a native filter in Gmail using Google Apps Script - Stack Overflow) (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。成功すると、新しいフィルタオブジェクト(ID含む)が返ってくるので、シートの該当行のFilterIDセルをsetValueで更新しています。これにより、一度作成したフィルタは次回以降そのIDでシートと対応づけられます。

  • FilterIDが指定されている場合: そのIDがexistingFilterIds(実際にGmail上に存在したID一覧)に含まれているかを調べます。含まれていれば更新操作、含まれていなければ(シートにIDが書かれているがGmail上に見当たらない場合)何らかの不整合なので、新規作成として扱っています。

    • 更新: Gmail API自体には「フィルタの更新(Update)」メソッドは存在しないため、一旦削除して新規に作り直す方法を取ります (Gmailのフィルタをスプレッドシートで管理する #spreadsheet - Qiita)。Gmail.Users.Settings.Filters.remove('me', filterId)で該当IDのフィルタを削除し (Gmail.Users.Settings.Filters (Gmail API v1-rev20240520-2.0.0))、続いてcreateで新規作成します。その際フィルタIDは変わってしまうため、シートに保持していた古いIDを新IDに置き換えています。これでユーザーにとっては「同じ場所にあるフィルタを編集した」ことと同義になります。

    • 削除+新規という実装上、処理の順番によっては一瞬フィルタが消えている時間が生じます。その間に該当するメールが届くとフィルタ適用漏れになる可能性があります。気になる場合は削除せずにそのまま新規作成するといった方法もあります(古いフィルタが残ってしまいますが、あとでまとめて削除するなど)。今回は単純化のため、確実に1つだけ残す実装としています。

  • それぞれの結果としてresults配列に「CREATED」「UPDATED」などのステータスとメッセージを蓄積しています。これはデバッグログ出力や後でユーザー確認用に使うためのものです。

(D) 既存フィルタの削除(オプション)

  const deleteUnlisted = false;  // ... trueにすると有効
  if (deleteUnlisted) {
    existingFilters.forEach(f => {
      const fid = f.id;
      const listedIds = rows.map(r => r[headers.indexOf('FilterID')]);
      if (listedIds.indexOf(fid) === -1) {
        Gmail.Users.Settings.Filters.remove('me', fid);
        results.push([ "DELETED", `シートに無いためフィルタID:${fid} を削除しました` ]);
      }
    });
  }

この部分は、スプレッドシート上に定義されていないGmailフィルタを自動削除する処理です。デフォルトではdeleteUnlisted = falseとして無効化してあります。必要に応じてtrueに変更して利用してください。

仕組みとしては、先ほど取得したexistingFilters一覧をループし、それぞれのIDがシート上のFilterID一覧に存在しない場合にFilters.removeで削除しています。シートがフィルタの完全なソースオブトゥルース(真実の一覧)である場合、この処理を有効にすることで手動でフィルタを削除する手間が省け、シートから行を消せば次回同期時に自動でGmailからも消えるようになります。

一方で、Gmail上に手動で作成したフィルタや、管理対象外のフィルタも消してしまう恐れがあります。運用方針によってはdeleteUnlistedを使わず、必要な削除はシートにそのフィルタIDを一時的に書いて「Delete」列にチェックを入れる等の方法で明示的に削除処理する方が安全かもしれません。

(E) エラーハンドリングと結果出力

    } catch (err) {
      // エラー発生時...
      results.push([ "FAILED", `行${rowNumber}: ${msg}` ]);
      console.error(`行${rowNumber}の処理中にエラー: ${msg}`);
    }

各フィルタ行の処理はtry ... catchで囲まれており、万一API呼び出しやロジックでエラーが起きてもスクリプト全体が途中停止しないようにしています。catch内ではエラーメッセージをresultsに追加し、ログにも出力しています。例えば転送先が未許可だった場合や、フィルタの条件指定が不正だった場合(カテゴリ名の間違いなど)、ここに落ちてメッセージが記録されます。後で結果を確認して、該当行を修正した上で再度スクリプトを実行することで問題を解決できます (Create Bulk Gmail Filters from Sheets - xFanatical)。

  // ...(処理後)
  results.forEach(r => console.log(`${r[0]}: ${r[1]}`));

今回は簡略のため、結果はスクリプト実行ログに出力しています。必要に応じて、スプレッドシート内に「結果」用の別シートやカラムを作り、そこに書き込むようにすることもできます(その場合はsheet.getRange().setValues(results)などでまとめて書き込みすると良いでしょう)。実行後に**「何が作成/更新/削除されたか」「どの行で失敗したか」**を確認できるようにすることで、管理がより確実になります。

Gmail APIの制限とエラーハンドリング

Gmail APIを利用する上で知っておくべき**使用制限(クォータ)**や、スクリプトを安定稼働させるためのエラーハンドリングについて解説します。

Gmail APIの主なクォータ制限

GoogleはGmail APIに対して一定のレート制限1日あたりの使用量制限を設けています。例えば:

通常の用途で数十~数百件程度のフィルタを操作する分には、これら上限に達することはまずありません。ただし、一度に極端に大量のAPI呼び出しを行うと**「User-rate limit exceeded」(ユーザーのレート上限超過)**のエラーが返される可能性があります。その対策として以下が挙げられます。

  • ループで多数のcreateremoveを実行する際、小休止を入れる: 例えば50件処理ごとにUtilities.sleep(1000)(1秒待機)を入れるなどして急激な連続発行を避けます。
  • エラー時のリトライ処理: 一時的なエラー(例えばGoogle側の一時的な問題やRate Limit超過)であれば、catch内で少し待ってから再試行するロジックを組むこともできます。簡易的にはUtilities.sleep(5000)で5秒待ってもう一度Filters.createを呼ぶ、といった処理です。ただし無限リトライにならないよう回数制限を設けましょう。
  • バッチリクエストの活用: Gmail APIはHTTPリクエストをまとめて投げるBatchエンドポイントを提供しています。Apps Scriptで直接扱うのは高度ですが、RESTリクエストを自前で構築して一括送信することで効率化できます (Google Apps and Gmail Limits: What Everyone Needs to Know)。フィルタ数が極端に多い場合は検討してもよいでしょう。

また、Gmail API固有の制限としてフィルタは最大1,000件までしか作成できません ([Gmail API] JavaでGmailのフィルタを生成・削除する #OAuth2.0 - Qiita)。これはGmailサービス自体の制約で、APIを使ってもそれ以上は追加できません。ユーザーが使えるフィルタ数には限りがあるため、上限近くになったら以下の対策が必要です。

  • 類似条件の集約: 複数の似た条件をOR条件で1つのフィルタにまとめることを検討します。例えば10人のアドレスからのメールをすべて同じラベルに振り分けたいなら、それぞれにフィルタを作るより、Fromに(addr1 OR addr2 OR ... OR addr10)とまとめて1件のフィルタにできます (複数アドレスを指定するGmail検索演算子をGASで一括作成する方法 | 好奇心の導くままに)。このようにすればフィルタ件数を大幅に節約できます。
  • 不要フィルタの定期的な削除: 以前は必要だったが今は使っていないフィルタがあればシートからエントリを削除し、同期時にGmail上からも削除することで総数を減らします。
  • フィルタルールの見直し: 同じ動作(例えば同じラベルを付ける)のフィルタが多数ある場合、条件を包括的に見直してまとめたり、逆に非常に複雑な条件で1つにしているがために漏れがある場合は2つに分割したりと、フィルタ内容自体の最適化も有効です。

予期しうるエラーと対策

スクリプト実行時に遭遇しやすいエラーの例と対処法をまとめます。

  • 転送アドレス未設定エラー: action.forwardに指定したアドレスがGmail側で承認されていないと、GoogleJsonResponseException: badRequest 等のエラーが発生します。この場合はGmailの設定画面で対象アドレスを転送許可リストに追加し、確認コードによる承認を完了してください。その後再度スクリプトを実行すれば成功します。
  • ラベル名のtypoや無効なカテゴリ: 存在しないラベル名を指定した場合、スクリプトでは自動で作成するためエラーにはなりません。ただしカテゴリ(Category列)に誤った値を入れると「無効な引数」エラーになります (Create Bulk Gmail Filters from Sheets - xFanatical)。これはシートの入力ミスですので、正しいスペルに修正して再実行してください。
  • フィルタ条件の不備: criteriaに何も指定しない状態でFilters.createを呼ぶとエラーになります。本コードではその状況を事前に検出してエラーにしています。このメッセージが出た場合は、該当行に最低1つの条件列を入力してください。
  • フィルタ数上限エラー: フィルタ数が1000に達している状態で新規作成を試みるとエラーになります(「Too many filters」等のメッセージ)。この場合は上限を超えないように不要なフィルタを削除してください。前述のdeleteUnlistedオプションを活用して一括削除するのもよいでしょう。
  • 認可エラー: Gmail APIが正しく有効化されていない場合や、別ユーザーアカウントのフィルタを操作しようとすると、API call to gmail.users.settings.filters.create failed (delegation denied) のようなエラーが出ます (How to modify this existing post of script and/or steps so it functions to automatically empty Gmail Trash? - Web Applications Stack Exchange) (How to modify this existing post of script and/or steps so it functions to automatically empty Gmail Trash? - Web Applications Stack Exchange)。このガイドの手順1を再確認し、スクリプトのプロジェクトに対してGmail APIを有効化してください。特にGoogle Workspace管理者がAPIアクセス制限をしている場合、そのポリシーも確認が必要です。

上記のようなエラーに対しては、スクリプト内で適宜try/catchしながら対処メッセージを出力しています。大量のフィルタを扱う場合は、一部の失敗で全体が中断しないようにしておくことが重要です。本コードのように行単位でエラーハンドリングしておけば、残りのフィルタ適用処理は続行されるので安心です (Create Bulk Gmail Filters from Sheets - xFanatical)。

運用のベストプラクティス

最後に、スプレッドシート+スクリプトでGmailフィルタを運用管理していく上でのポイントやコツを紹介します。

  • フィルタ設定のバックアップ: 万一シートを誤って消してしまった場合に備え、定期的にフィルタ一覧をエクスポートしてバックアップしておきましょう。Filters.listで取得できる内容を別シートに保存しておく、あるいはシート自体を定期コピーする運用でも構いません。

  • テスト環境で検証: いきなり本番のメールで動かす前に、テスト用のGmailアカウントやテスト用フィルタでスクリプトを試し、期待通り動作するか確認しましょう。特に削除系の処理(フィルタ削除やメールの削除を行うフィルタ設定)は細心の注意を払い、問題ないことを確かめてください。

  • スクリプト実行のタイミング: フィルタ設定の変更は基本的にそれほど頻繁には起きないと思いますが、もし日常的に変更する場合は、定期実行トリガーを設定して自動同期することも可能です。例えば1日に1回深夜にsyncGmailFilters()を走らせてシート内容をGmailに反映する、といった運用です。ただしトリガー実行中にシートを編集していると不整合が起きる可能性もあるため、実行前後のタイミングでは編集しないルールを作ると良いでしょう。

  • 検索演算子の活用: Gmailの検索演算子を駆使することで、細かな条件も1つのフィルタで表現できます (Create Bulk Gmail Filters from Sheets - xFanatical)。たとえば「特定のドメインからのメールかつ件名に‘ProjectX’を含む場合にラベルYを付与しアーカイブする」といった複合条件も、From欄に@example.com、HasWords欄にProjectXと入れ、アクション列を設定すれば対応可能です。複数条件を組み合わせてフィルタの数自体を減らす工夫をすると、上限に余裕が生まれ将来的な運用が楽になります。

  • フィルタの即時反映と既存メールへの適用: スクリプトで作成・更新したフィルタは即座に有効になります。ただし注意点として、Gmailフィルタは新着メールに対してのみ適用され、既に受信トレイに存在する過去メールには遡って適用されません (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。もし「フィルタを作ったので過去のメールにもさかのぼってラベル付けしたい」場合は、別途GASで検索クエリを実行し該当メールに対してGmailApp.moveThreadToArchive()GmailApp.applyLabel(label)を実行する必要があります。それはまた別のスクリプトになりますので、必要であれば作成してください。

  • Gmail UIとの併用: スプレッドシート管理に移行した後は、極力手動でGmailの設定画面からフィルタを追加・変更しない運用が望ましいです。もし手動で変更した場合は、シートに反映されず整合性が崩れます。その場合、再度listFilters()で確認しシートを更新するか、手動変更した内容をシートにも写してください。基本的には「フィルタの単一編集はシート上で行い、それをスクリプトで適用」という手順に統一するのがおすすめです。

  • フィルタの命名やメモ: Gmailフィルタ自体には名前やメモ欄が無いため、管理シート上で自由列を作ってフィルタの目的やメモを書いておくとわかりやすくなります。例えば「プロジェクトX関連」「ニュースレター整理用」など説明を入れておけば、後から見ても用途を思い出しやすくなります。スクリプト上はそれら説明列は無視して動作するようにしておけば支障ありません。

以上が、Gmailフィルタをスプレッドシート+GAS+Gmail APIで一括管理するためのポイントです。特にGmailフィルタ数の上限(1000件)については公式に明言されており ([Gmail API] JavaでGmailのフィルタを生成・削除する #OAuth2.0 - Qiita)、大量のフィルタを扱う組織では頭を悩ませるところですが、適切にルールを整理していけば上限回避も可能です。

おわりに

本ガイドでは、実践的なGASスクリプトを用いてGmailフィルタの一括管理を行う方法を詳しく解説しました。スプレッドシートで視覚的にフィルタ設定を一覧できるため、フィルタの追加・変更・削除が容易になるだけでなく、複数人でのレビューや変更履歴の管理(シートのバージョン管理機能を使う)も行いやすくなります。

Gmail APIの高度なサービスを活用することで、通常のGmailAppではできない高度な操作も可能になります。Google公式の説明にもある通り、この高度なサービスを有効化すれば「メールフィルタの作成も可能」だと示唆されています (Create a native filter in Gmail using Google Apps Script - Stack Overflow)。実際そのとおりで、手動操作を自動化することで業務効率が格段に上がるケースも多いでしょう。

ぜひ本記事のコードを自分の環境に取り込んで試してみてください。そして運用を通じて使いやすいように改良し、あなたのメール整理術を一歩先に進めましょう。必要に応じてGoogleの公式ドキュメント (Gmail.Users.Settings.Filters (Gmail API v1-rev20240520-2.0.0))やコミュニティの情報も参照しながら、安全かつ効率的なメールフィルタ管理を実現してください。お疲れさまでした!

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?