Gmailの整理を効率化！Google Apps Scriptでラベル管理と古いメールの削除を自動化する方法

※この記事は「身の回りの困りごとを楽しく解決！ by Works Human Intelligence Advent Calendar 2024」の参加記事です。

https://qiita.com/advent-calendar/2024/works-hi-1

## はじめに
### 背景
メールって、気付くと大量に溜まってしまって、Google Driveの容量を圧迫してしまったりしませんか？
不要なメールは減らしたいとは思いつつも、メールマガジンやアラートメール、ワンタイムパスワードのメールなど、結構な数のメールが日々届くため、毎日整理するのは大変です。
また、通知系のメールなどは、届かないのも困ってしまうので、これまではある程度溜まったら検索して一括削除、といったことをやっていました。

今回、もっと楽に整理をしたいと思い、Google Apps Script（GAS）を使って古いメールの削除を自動化する仕組みを構築しました。
この記事では、その仕組の内容とスクリプトを紹介します。

### 作成した仕組の特徴
とはいえ、GASを用いたメール整理自動化は、比較的ネット上にも情報が多い内容です。
自分が作成した仕組みは、もうちょっと応用的な、以下の特徴を持った仕組みです。

* **指定したメールアドレス**から届いたメールのみを対象とする
  * メールマガジンや通知メールなど、特定のメールを対象にできるようにしました
* 対象のメールアドレス毎に**保持期間を指定できる**
  * このメールマガジンは２週間、この通知メールは１年など、保持期間を指定できます。
* **Gmailアプリ上だけ**で完結できる
  * 対象のメールアドレスや保持期間をSpreadsheetで管理しますが、Spreadsheetを開かずにGmailアプリ上の操作だけで対象の追加、保持期間の変更などの操作を実施できます

特に３つ目が重要で、メール整理のためにSpreadsheetにメールアドレスを転記して・・・といった作業はやりたくありませんでした。
Gmailアプリ上で、しかもスマホからも操作できるようにしたため、整理がとても楽に行えるようになりました。

### 作成した仕組の使い方

仕組の使い方はこうです。
すべてGmailアプリ上で操作を行います。

1. 一定期間後に削除したいメールを選んで「Cleaner-xx（xxは期間指定）」というラベルを付与する
1. 一定時間ごとにスクリプトが動作し、「Cleaner-xx」のラベルが付いたものを取得、Spreadsheetに記録して「AutoClean-xx」というラベルに付け替える

基本的にはこれだけです。
<div>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571256/710864af-2264-6afd-1fee-3605b0b29035.png" width="250px"/>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571256/95ea239c-79fb-5243-bce0-65e304930904.png" width="250px"/>
</div>

画像のように、「Cleaner-14d」とラベルをつけると、14日間保持する設定になります（「Cleaner」ラベルは「Cleaner-1m」と同等です）。
スクリプトが動作したあとは、2枚目のようにAutoClean-xxにラベルが置き換わります。
また、このAutoClean-xxラベルは、対象のメールから届いたもの全てにラベルが付くため、「このメールは削除対象にしていたっけ？」といったことを調べる必要はありません。

ラベルを付けたものは、以下のようにSpreadsheetに転記されていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/571256/116dfd0a-59b5-5eb5-6d39-0dabe03c311d.png)

期間の指定はラベルの末尾xxで行います。
dは日数、mは月数、yは年数です。
Gmailの検索仕様のolder_thanの仕様に準ずるため、詳しくは以下のページを御覧ください。

https://support.google.com/mail/answer/7190?sjid=8129326710471810543-AP

期間指定は自由に増やすことが可能なので、「10日保持するようにしたい」と思ったら、「Cleaner-10d」というラベルを新たに作って付与するだけです。
Spreadsheetを開いて設定する、といったことはしなくて良いのです。
唯一、対象を除外する場合のみ、Spreadsheetを開いて対象行を削除してやる必要があります。
（そこもラベルで指定できるようにすることも可能ですが、頻繁にある作業ではないのでやめました）

## コードの概要
### 機能説明
- **`main`関数:**  
  日中定期的に起動し、ラベル「Cleaner-xx」を元にメールを整理し、管理情報をスプレッドシートに記録します。
- **`deleteThreads`関数:**  
  毎日0時頃に起動し、指定期間以上前のメールを検索してゴミ箱に移動します。

それぞれGASのトリガーを設定し、main関数の方は10分おきに実行、deleteThereads関数の方は1日おきに0時-1時に実行するようにしています。

---

## コード

実際のコードは以下のとおりです。

```javascript
// Compiled using gmail-cleaner 1.0.0 (TypeScript 4.9.5)
"use strict";
/**
 * Gmail Cleaner
 * 1. ラベル「Cleaner-xx」が付与されたメールアドレスを取得
 * 2. メールアドレスをスプレッドシートに記録
 * 3. 指定期間以上前のメールをゴミ箱に移動
 */
function main() {
    const sheet = SpreadsheetApp.getActiveSheet();

    // 削除対象のラベルを取得
    const labels = GmailApp.getUserLabels();
    const cleanerLabels = labels.filter((l) => l.getName().toLowerCase().includes("cleaner"));
    
    // ラベル「Cleaner-xx」が付与されたメールアドレスを取得
    cleanerLabels.forEach((label) => {
        const labelName = label.getName();
        const olderThan = labelName.split("-")[1] || "1m";
    
        // Cleaner-xxから付け替えるためのAutoClean-xxの取得・作成
        const autoCleanLabel = GmailApp.getUserLabelByName("AutoClean-" + olderThan) ||
            GmailApp.createLabel("AutoClean-" + olderThan);
        
        // ラベル「Cleaner-xx」が付与されたメールアドレスを取得
        const cleanerMailAddress = GmailApp.search(`label:${labelName}`);
        
        cleanerMailAddress.forEach((mail) => {
            mail.getMessages().forEach((message) => {
            
                // メールアドレスが記録されていなければスプレッドシートに記録
                const mailAddress = message.getFrom();
                var textFinder = sheet.createTextFinder(mailAddress);
                var cells = textFinder.findAll();
                
                // すでに記録されている場合は、AutoClean-xxを付け替える
                if (cells.length > 0) {
                    const nextAutoCleanLabel = GmailApp.getUserLabelByName("AutoClean-" + olderThan) ||
                        GmailApp.createLabel("AutoClean-" + olderThan);
                    cells.forEach((cell) => {
                        const range = sheet.getRange(cell.getRowIndex(), 2);
                        const prevOlderThan = range.getValue();
                        const prevAutoCleanLabel = GmailApp.getUserLabelByName("AutoClean-" + prevOlderThan) ||
                            GmailApp.createLabel("AutoClean-" + prevOlderThan);
                        range.setValue(olderThan);
                
                        // おなじラベルの場合はスキップ
                        if (prevAutoCleanLabel.getName() === nextAutoCleanLabel.getName()) {
                            return;
                        }
                        
                        // 前のラベルが付与されたスレッドを取得
                        const labelAddThreads = GmailApp.search(`from:(${mailAddress}) label:${prevAutoCleanLabel.getName()} -is:starred`);
                        if (labelAddThreads.length === 0) {
                            return;
                        }
                        
                        // 配列を100件ずつに分割（addToThreadsは1度に100件まで）
                        const chunk = 100;
                        for (let i = 0; i < labelAddThreads.length; i += chunk) {
                            const threads = labelAddThreads.slice(i, i + chunk);
                            // ラベルの付け替え
                            prevAutoCleanLabel.removeFromThreads(threads);
                            nextAutoCleanLabel.addToThreads(threads);
                        }
                    });
                }
                else {
                    sheet.appendRow([mailAddress, olderThan]);
                }
            });
            
            // ラベル「Cleaner-xx」を削除し、AutoClean-xxを付与
            mail.removeLabel(label);
            mail.addLabel(autoCleanLabel);
        });
    });
    
    // 削除対象のFromメールアドレスを取得
    const targetMailAddressRange = sheet.getDataRange();
    const targetMailAddressList = targetMailAddressRange.getValues();
    
    targetMailAddressList.forEach(([targetMailAddress, olderThan]) => {
    
        //検索条件：指定されたメールアドレス && ラベルなし && ☆スターなし
        const labelAddThreads = GmailApp.search(`from:(${targetMailAddress}) has:nouserlabels -is:starred`);
        if (labelAddThreads.length === 0) {
            return;
        }
        
        //検索条件に合致するメールに「AutoClean-xx」ラベルを付与
        const label = GmailApp.getUserLabelByName("AutoClean-" + olderThan) ||
            GmailApp.createLabel("AutoClean-" + olderThan);
            
        // 配列を100件ずつに分割
        const chunk = 100;
        for (let i = 0; i < labelAddThreads.length; i += chunk) {
            const threads = labelAddThreads.slice(i, i + chunk);
            label.addToThreads(threads);
        }
    });
}

/**
 * 指定期間以上前のメールをゴミ箱に移動
 */
function deleteThreads() {
    const sheet = SpreadsheetApp.getActiveSheet();
    const targetMailAddressRange = sheet.getDataRange();
    const targetMailAddressList = targetMailAddressRange.getValues();
    
    targetMailAddressList.forEach(([targetMailAddress, delayDays]) => {
    
        //検索条件：指定されたメールアドレス && 受信日が指定期間より前 && ☆スターなし
        const deleteThreads = GmailApp.search(`from:(${targetMailAddress}) older_than:${delayDays} -is:starred`);
        console.log(targetMailAddress, delayDays, deleteThreads.length, "件");
        
        //検索条件に合致するメールをゴミ箱に移動
        // 配列を100件ずつに分割
        const chunk = 100;
        for (let i = 0; i < deleteThreads.length; i += chunk) {
            const threads = deleteThreads.slice(i, i + chunk);
            GmailApp.moveThreadsToTrash(threads);
        }
    });
}

```

### 制約事項
GASはいくつかの制約事項があります。
今回のスクリプトで影響を受けるのはこのあたりです。

* タイムアウト時間（6分 or 30分）
* Gmail APIの1日の最大実行回数

基本的には削除できずにエラーが出てメールが溜まってしまうだけなので、致命的な影響はないと思います。
気になる方は、実行数の管理なども加えてあげると良いでしょう。

## 終わりに
今回はネット上で見かける自動化の仕組みを、より自分の使いやすいようにカスタマイズをしてみました。
ネット上の情報は参考になったり便利なものも多いですが、そのままでは使いづらいことも多いです。

自分の欲しい機能とのギャップをいかに埋めるか、試行錯誤して解決できたときの満足感はひとしおですね。
今後も、日常のちょっとした手間を簡単に解消する方法を、日々探していきたいと思います。

参考

https://tech.torico-corp.com/blog/delete-old-gmails-via-google-apps-script/