見出し画像

Gmail を定期的に削除する GAS を作ってみた

Gmail を定期的に削除する GAS を作ってみた

Gmail は容量が 15GB まで無料で利用できますが、それを超えると有料プランに切り替える必要があります。
この容量は Gmail の他に、Google ドライブ、Google フォトと共用の容量となっています。
不要なメールでこれらの容量を圧迫するのを防ぐために、定期的にメールを削除する必要に迫られましたが、
Gmail のフィルターを利用してメールを削除する場合は、受信時に削除されるため、メールを確認する前に削除されてしまう可能性があります。

そこで、メールを削除するまでの日数を指定することで、一定期間経過後にメールを削除する処理を GAS を利用して作成しました。

本記事では、前半にユーザーとして使うための方法を記載し、後半に GAS の説明をします。
ソースコード及び GAS 含むスプレッドシートは下記で公開しています。


ツールの概要

特徴

  • ユーザー定義のラベルに基づいてメールを削除します。

  • ネストされたラベルをサポートします。

  • スター付きのメールは削除されません。

  • 自動削除を設定した後、1 時間につき 100 件の対象メールが削除されます。

  • スプレッドシートの言語設定に基づき、日本語と英語で動作します。サポート外の言語の場合は英語で動作します。

使い方

1 Gmail でラベルを定義します。ラベル名の後には '@' と、メールを削除するまでの日数を指定します。
例えば、'general@365'のラベルがつけられたメールは、受信後 365 日以上経過した場合に削除されます。

カテゴリに分類されているメールについては、下記のように、カテゴリというラベルにネストしたラベルを用意します。
カテゴリ/プロモーション@30

2 下記のスプレッドシートを開き、自分の Google ドライブへコピーをします。

スプレッドシートをコピー後は、スプレッドシートのメニュー>設定を開き、言語と地域が日本以外の場合は日本に設定してください。

または、スプレッドシートを開いた後、本スクリプトを Google スプレッドシートのスクリプトエディタにコピーします。
対象は下記の 3 つのファイルです。

  • src/client.js

  • src/main.js

  • src/const.js

3 スプレッドシートを開き、メニューバーの一番右にある 'Gmail 削除ツール' をクリックします。

下記のメニューが表示されます。

  • 自動削除をセットする

  • 自動削除をストップする

  • 一回だけ削除する

最初に「一回だけ削除する」を実行し、動作を確認してください。
なお、初めて実行する際は、アクセス許可を求められますので、下記ページを参考に、許可をしてください。

認証を行った後、再度「一回だけ削除する」を実行してください。終了のダイアログが表示されれば、正常に動 作しています。
その後に「自動削除をセットする」を実行すると、自動削除が開始されます。

制限事項

  • タイムアウトを防ぐため、1 回の実行で最大 100 通削除します。

  • ラベルがネストされている場合、スクリプトは子ラベルのみを対象とします。

  • '@' がないラベルや '@' の後に正の整数以外があるラベルは処理されません。

GAS の概要

ソースコードは下記の GitHub にあります。

どのメールを削除するか?

メールを自動で削除するという性質上、ユーザーが意図していないメールを削除するおそれがあります。
そのため、ラベルがついているメールのみを削除するようにしました。
また、削除対象のラベルと、どの程度の期間経過したメールを削除するかについて、ユーザーが定義できるようにしたいと考えていました。

当初、スプレッドシートでリストを定義することを考えましたが、Gmail とスプレッドシートを行ったり来たりしながら設定するのは面倒くさいと考え、Gmail のラベル名自体に日数を入れ込んでおくという仕様にしました。
general@365 というように、ラベル名の後ろに@と削除するまでの日数を指定します。

関数の説明

下記の関数で実現しています。
これ以外に、スプレッドシートのメニューを表示する関数もありますが、本筋ではないため省略します。

ラベルのを取得し、削除するメインの関数

/**
 * Gmail deletion process
 * Deletes the target email based on the label
 */
function deleteMails() {
  // Get user labels
  const labels = GmailApp.getUserLabels();

  // Total number of deleted emails
  let count = 0;

  // Perform deletion process for each label name
  for (const label of labels) {
    count = deleteMailByLabel_(label.getName(), count);

    // If the deletion limit is reached, the process is terminated
    if (count >= LIMIT_DELETE_MAIL) {
      break;
    }
  }

  logAndToast_(MESSAGE_TABLE[lang].deleteMails.toast + count);
}

Gmail で使用しているラベルは、GmailApp.getLabels()で取得できます。すごく簡単です。
後は各ラベルで、削除対象のメールがあるかを判定していきます。

ラベルがついたメールを取得し、削除する関数

/**
 * Get and delete mail with specific label in gmail
 * If the label is nested, the parent label will not respond, and only the child label will be the target of this function.
 * If multiple labels are attached to the target mail, the stricter one will be deleted.
 *
 * @param {string} labelName Label name. There should be the number of days to delete after @.
 * @param {number} count Total number of deleted emails
 * @return {number} Total number of deleted emails
 */
function deleteMailByLabel_(labelName, count) {
  // Create search condition
  const condition = makeSearchCondition_(labelName);

  // If the search condition is null, the process is terminated
  if (condition === null) {
    return count;
  }

  // The total number of emails to be deleted is limited to LIMIT_DELETE_MAIL to prevent timeout
  const threads = GmailApp.search(condition, 0, LIMIT_DELETE_MAIL - count);

  logAndToast_(
    labelName +
      '\n' +
      MESSAGE_TABLE[lang].deleteMailByLabel.toast +
      threads.length
  );

  count += threads.length;

  // Delete all threads obtained by search
  GmailApp.moveThreadsToTrash(threads);

  return count;
}

後述する、検索条件を作成する関数を使って、ラベルがついたメールを取得します。
GAS の制限時間に引っかからないように、1 回の実行で削除する件数をカウントするために、削除するメールの件数をカウントしています。
threads として取得したメールを一括で削除する moveThreadsToTrash 関数があるので、まとめて削除しています。

ラベルがついたメールを取得するための検索条件を作成する関数

/**
 * Create search condition
 * Returns null if the label name does not meet the search criteria.
 *
 * @param {string} labelName Label name. There should be the number of days to delete after @.
 * @return {string|null} Search condition string if valid, null otherwise
 */
function makeSearchCondition_(labelName) {
  const labelInfo = checkLabelNameValidity_(labelName);

  if (labelInfo === null) {
    return null;
  }

  // If the label name is not in the CATEGORY_TABLE, it is a user label
  // CATEGORY_TABLE has labels under the category label, such as Social, New, Promotion, and Forum, without @
  const categoryLabelTable = CATEGORY_TABLE;
  if (!categoryLabelTable[labelInfo.name]) {
    categoryLabelTable[labelInfo.name] = 'label: ' + labelName;
  }

  const condition =
    categoryLabelTable[labelInfo.name] +
    ' older_than:' +
    labelInfo.days +
    'd -is:starred ';

  console.log('search condition:' + condition);

  return condition;
}

ラベルが条件を満たしいてるかの判定と、カテゴリを指定されているかの判定をした後に、検索条件を作成します。
デフォルトで用意されているカテゴリと、ユーザーが作るラベルでは検索演算子が異なるため、下記のようなカテゴリと検索演算子の対応表を作成し、不一致ならユーザーラベルと判断、テーブルに追加する方法にしています。

// category table
// User needs to create labels under the category label, such as Social, Updates, Promotions, and Forums with @.
// This table key is the label name without @, and the value is the search condition.
// If you want to add other languages, please add them here.
const CATEGORY_TABLE = {
  'カテゴリ/ソーシャル': 'category:social',
  'カテゴリ/新着': 'category:updates',
  'カテゴリ/プロモーション': 'category:promotions',
  'カテゴリ/フォーラム': 'category:forums',
  'categories/Social': 'category:social',
  'categories/Updates': 'category:updates',
  'categories/Promotions': 'category:promotions',
  'categories/Forums': 'category:forums',
};

ラベルが条件を満たしているかの判定をする関数

/**
 * Check the validity of the label name
 * Returns null if the label name does not meet the criteria.
 *
 * @param {string} labelName Label name. There should be the number of days to delete after @.
 * @return {object|null} labelInfo An object containing the name and days if valid, null otherwise
 * @property {string} labelInfo.name Label name
 * @property {number} labelInfo.days Number of days to delete
 */
function checkLabelNameValidity_(labelName) {
  const labelNameArray = labelName.split('@');

  // If the label name does not contain @, it is not a target
  if (labelNameArray.length === 1) {
    logAndToast_(
      labelName + '\n' + MESSAGE_TABLE[lang].checkLabelNameValidity.noAt
    );
    return null;
  }

  const days = labelNameArray[labelNameArray.length - 1];

  // If the number of days to delete is not a positive integer, it is not a target
  if (!Number.isInteger(Number(days)) || Number(days) <= 0) {
    logAndToast_(
      labelName + '\n' + MESSAGE_TABLE[lang].checkLabelNameValidity.noInteger
    );
    return null;
  }

  // Remove the number of days from the label name
  labelNameArray.pop();
  const name = labelNameArray.join('@');

  return { name: name, days: days };
}

ラベルに@が含まれているか?@の以降に数字があるか?を判定します。
削除対象となるラベルだった場合は、削除する日数も取得して返却します。

ログとトースト

/**
 * Log and toast
 *
 * @param {string} str String to log and toast
 */
function logAndToast_(str) {
  console.log(str);
  SpreadsheetApp.getActiveSpreadsheet().toast(str);
}

これは見ての通りです。

まとめ

GAS で Gmail を定期的に削除するツールを作成しました。
GAS では、Gmail を簡単に操作するためのサービスが色々ありますので、このように簡単なスクリプトで便利なツールを作ることができます。
Google の各種サービスを使用して、ちょっと面倒くさい作業がある場合や、定期的に実施したい作業がある場合、このようなツールを作成することで、作業の効率化ができます。
Google Workspace をお使いの方、Gmail の容量で困っている方、GAS の勉強をされている方の参考になれば幸いです。

この記事が気に入ったらサポートをしてみませんか?