Google Apps ScriptでのGmailの検索はメールではなくスレッドの検索となる

2017.07.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Google Apps ScriptでGmailの「特定のフィルターにマッチした受信メール」のみをチャットに転送するスクリプトを作成しようとしました。 その際、フィルターにマッチする、と言う動作に対する誤解から少しはまったのでメモしておきます。

ざっくり結論を言うと、「GmailApp#searchで検索できるのはメールではなくスレッドなので、クエリにマッチしていないメールが返って来る可能性がある」です。

メールの検索

Google Apps ScriptでGmailのメールを検索する際には以下のように記述します。

var threads = GmailApp.search("label:google-app-script-test");//Gmailのフィルタークエリを入力

Class GmailApp | Apps Script | Google Developers

問題はこの検索が、厳密には「メールに対する検索」ではなく、「スレッドに対する検索」だということです。(この動作はGmailをWeb画面から直接操作しても同じです。)

スレッドとは、メールの返信によるやりとりで受信した複数のメールを1つのフォルダのような単位でまとめたものです。

スクリーンショット 2017-07-08 1.11.25

この構造のため、特定のメールにだけマッチするクエリを入力しても、スレッド内のいずれかのメールにクエリがマッチすると、スレッド内のメールが全て返却されてしまいます。

以下のようなイメージです。 スクリーンショット 2017-07-08 1.41.35

上記例では「黄色いメール」だけを取得しようとしていますが、「黄色いメールと一緒にスレッドに入っている赤や青のメール」まで取得できてしまっています。

何が問題なのか

例えば、初めに記載したような「特定のフィルターにマッチしたメールのみをチャットに転送するスクリプト」を作るとします。 フィルターの設定は、「送信元がAさんだった場合」とします。 メールの送受信の流れが以下だった場合について考えてみます。

  1. Aさんがメールを送信する。(TO:自分、CC:Bさん)
  2. Bさんが1のメールに返信する。(TO:Aさん、CC:自分)

この時、スレッドには以下のメールが含まれます。

  • Aさんが自分宛てに最初に送ったメール
  • BさんがAさん宛てに返信したメール
  • この状態で、「Aさんが自分宛てに送ったメール」を検索すると、自分宛てではないBさんが送ったメールも取得できてしまいます。

    どうすればいいのか

    ドキュメントを見た限り、Google Apps Scriptから提供されている機能だけではメール単位での検索はできないようでした。 そのため、自力でフィルタを実装する必要があります。

    例えば以下のコードは、「件名」「本文」「送信元」「送信先」での絞り込みをしています。

    function myFunction() {
      var threads = GmailApp.search("from:'@example.com' ");
      
      threads.forEach(function(thread) {
        var messages = thread.getMessages();
        messages.forEach(function(message) {
          
          var subject = message.getSubject();
          var body = message.getBody();
          var to = message.getTo();
          var from = message.getFrom();
          
          //本文に「重要」が含まれているメールのみを対象とする。
          if(!subject.match("重要")){
            return;
          }
          
          //本文に「このメールは見なくても良いメールです。」が含まれていないメールのみを対象とする。
          if(body.match("このメールは見なくても良いメールです。")){
            return;
          }
          
          //送信先や送信元は単純にメールアドレスの文字列ではなく「稲葉純<inaba@example.com>のようなフォーマットです。
          //送信先のドメインが「example.com」のメールのみを対象とする。
          if(!to.match(".*@example.com>$")){
             return;
          }
          
          //送信元のアドレスが「info@example.com」でないメールのみを対象とする。
          if(from.match("^info@exampmle.com>$")){
            return;
          }
    
          Logger.log(message.getId());
        });
      });  
    }

    これくらいならなんとかなりますが、GmailApp#searchに渡せるクエリと同等の検索機能を実装しようとするとなかなか骨が折れそうです。

    また、スクリプトの実行は5分間でタイムアウトしてしまうため、処理対象の件数を減らすために自力でフィルタを実装してもなおGmailApp#searchを利用する際にはクエリでの絞り込みを行うべきです。

    ちなみにGmailではWeb画面でメールを確認する際にスレッド表示をしないように設定することも可能ですが、設定を変えてもGoogle Apps Scriptでの動作は変わりませんでした。

    感想

    「変数にGmailのフィルタ用のクエリを入力しておくと、フィルタにマッチしたメールがチャットに転送されるアプリ」はなかなか骨が折れそうだと思いました。 厳密にはGmailでの検索は「スレッドに対する検索」なので、実現出来てはいるのですが、やはりフィルタはメールに対してかけたいです。

    実装する場合、まず上記のように複雑でないフィルタを実装し、もし複数条件で使用したくなった場合は転送用のスクリプトを複数作成するのが簡単かなと思いました。

    弊社ブログでは他にもGoogle Apps Scriptについて色々紹介しています。

    Google Apps Script | Developers.IO

    私からは以上です。