【GAS】Gmailの内容を疑似リアルタイムでスプレッドシートに出力する方法

## 初めに
今回、社内でGmailに送られる特定のメールの中身を自動で抽出するタスクを担当することになったので、実装内容等を備忘録としてまとめておきます。

## 要件
- Gmailに届く特定のメールの中身をスプレッドシートに反映
- 可能な限りリアルタイムでのデータ転記

## 実装手段
調べたところ、Gmailに送られるメールの中身を自動で抽出して出力する方法として、主に以下の二つがあることが分かりました。

| 方法                                 | リアルタイム性             | 工数 
|--------------------------------------|----------------------------|--------|
| GAS + 時間トリガー（1分ごとチェック）                | ◎（擬似リアルタイム）      | 低     |
| GCP + Pub/Sub + Cloud Functions [参考URL](https://codelabs.developers.google.com/codelabs/intelligent-gmail-processing?hl=ja#2)     | ◎（完全リアルタイム）      | 高     |

今回、筆者が採用したのは工数を考慮して、**GAS + 時間トリガー（1分ごとチェック）** です。

２つ目の方法はリアルタイムでの転記が可能になりますが、インフラの構築やGCPサービスの学習コストも必要でややtoo muchかなと思います。また、要件としてリアルタイムでの転記がありますが、1分ごとのチェックでも十分という社内の合意があったので採用しました。もしリアルタイムでの取得が必須なのであれば２つ目の方法での実装が必要となるでしょう。

## GASでの実装
ここでは、**GAS + 時間トリガー（1分ごとチェック）** の疑似リアルタイムでスプレッドシートに出力する大まかな実装の流れを紹介します。

### 1. メールの中身を取得するGASを作成する ###
下記は過去3分以内のメールを取得する例です。
```
  const timeOffset = 3 * 60 * 1000;
  const targetTime = new Date(now.getTime() - timeOffset);
  const unixTimestamp = Math.floor(targetTime.getTime() / 1000);
  // 3分以内に届いたメールを取得
  const threads = GmailApp.search(unixTimestamp);
```

### ２. GASのトリガーを設定 ###
次に、GASのトリガー機能を使って、画像のように1分おきに関数が実行されるように設定します。なお、**GASで設定できる最小の実行間隔は「1分」** となっています。

![スクリーンショット 2025-04-07 162932.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3904365/cf93d7d1-aa03-40eb-9e9b-ad9b75d6be38.jpeg)
#### :warning: GASのトリガーには「1日の合計実行時間（例：無料アカウントは90分まで：[参考](https://developers.google.com/apps-script/guides/services/quotas?hl=ja#:~:text=%E3%83%95%E3%82%A3%E3%83%BC%E3%83%89%E3%83%90%E3%83%83%E3%82%AF%E3%82%92%E9%80%81%E4%BF%A1-,Google%20%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E5%89%B2%E3%82%8A%E5%BD%93%E3%81%A6,-bookmark_border)）」という制限があります。
※ GASの「1日の合計実行時間」の上限（90分）を考慮すると、1分おきにトリガーを設定する場合、1回あたりの処理は約3.75秒以内に収める必要があります。そのため、定期的な実行時間をチェックし、効率化を行うことが大切でしょう。また、エラー発生時にすぐ気づけるように、エラーログの通知を有効にしておくこともおすすめです。

:::note info
1日の上限実行時間：5,400秒（＝90分）
1日の起動回数　：1,440回（1分おき × 24時間）
→ 1回あたりの上限時間：5,400 ÷ 1,440 = 約3.75秒
:::


### ⏰ なぜ実行間隔は「１分おきの実行」で「3分前のメール」を取得するのか？
GASのトリガーには「1日の合計実行時間（例：無料アカウントは90分まで）」という制限があります。
ただし、そのままだと重複処理の可能性があるため、処理済みのメールにはラベルを付けて除外するようにしています（詳細は後述）。

## 重複を防ぐ
###  Gmailスレッドに「転記済み」ラベルを付与する仕組み
メールの処理が完了した後に、そのスレッドに「転記済み」というラベルを付与することで、次回以降の処理で 重複して同じメールを処理しないようにしています。

✅ 処理済みかどうかをチェック
以下のようなコードで 現在のスレッドに「転記済み」ラベルが付いているかどうかを確認します：
```
if (thread.getLabels().some(l => l.getName() === "転記済み")) return;
```
- thread.getLabels() でスレッドに付いているラベルを取得
- その中に "転記済み" という名前のラベルがあれば return してスキップします

✅ 処理が終わったスレッドにラベルを追加
処理が完了したら、以下のようにラベルを付けます：
```
thread.markRead();        // 未読メールの場合は既読にしておく（任意）
thread.addLabel(label);   // 「転記済み」ラベルを付ける
```
ここで使われている label は、事前にこのように定義・取得されています。
```
let label = GmailApp.getUserLabelByName("転記済み");
if (!label) {
  label = GmailApp.createLabel("転記済み");
  console.log("ラベル '転記済み' を作成しました。");
}
```
- 最初に「転記済み」という名前のラベルが存在するか確認
- なければ GmailApp.createLabel() を使って ラベルを自動作成します


## おわりに
今回は、GASの時間トリガーを活用して、擬似リアルタイムでGmailのメールを自動処理し、スプレッドシートに転記する方法をご紹介しました。

- メールの中身から目的の内容を抽出して、スプレッドシートに転記する関数を作成する
- Apps Script（GAS）のエディタにスクリプトを設定し、時間主導型トリガーで1分ごとに関数を実行する
- 処理漏れを防ぐために「3分前」までを対象
- 重複処理を防ぐために Gmailスレッドへの「転記済み」ラベルの付与

この構成であれば、ノーインフラ・低工数で運用開始できます。
リアルタイム性が求められる場合は、GCP構成（Pub/Sub + Cloud Functions）の検討が必要になるでしょう。