📨

【Google Apps Script】Gmailから「相手からの返信待ちメール」を抽出する

2022/07/25に公開

はじめに

夏。繁忙期。日はとうに暮れた。

またメールか。受信ボックスはとうに溢れている。

上司:「わたし宛てのメールに24時間以内に返信がなかったらリマインドしてね」

心の悪魔が囁く。

「あの、人をリマインド代わりに使うのやめてくれます?」

「ピシッ、ピシッ、ピシッ」。耳に伝うように来るのは、人間関係に亀裂が走る音...


筆者はGmailの受信トレイを都度アーカイブすることでToDo管理をしていますが、[1]
繁忙期になると受信トレイの一通一通まで追い切れなくなります。

ここにきて、

「わたし宛てのメールに24時間以内に返信がなかったらリマインドしてね」

と来るとさすがにキャパが足りません。そこでGoogle Apps Scriptで上司のリクエストに応えてみた、というお話です。

目標

「わたし宛てのメールに24時間以内に返信がなかったらリマインドしてね」

をGoogle Apps Scriptで実装する。

具体的には下記の条件をすべて満たすメールを抽出する。

抽出条件

  1. 送信日から24時間以上経過している。
  2. スレッドの最後のメールの宛先が特定のアドレスになっている。
  3. スレッドの最後のメールの差出人が自分
  4. 最終メールに☆マークがついていない
    ※「ご確認ありがとうございました。」のような要返信でないメールを除外するため。

使うもの

ベースとなる記事

Find Unanswered Emails with Apps Script
2014年5月の記事。これをアップデート・カスタムして実装します。

実装

1. 「24時間以上経過している」「特定の宛先の」メールを抽出

条件に該当するメールはGmailApp.search(query)で抽出できます。

引数のqueryは、Gmail上で検索するのと同じ書式でOK。

まずは、「24時間以上経過している」「特定の宛先の」 の2条件で下記のqueryを作ります。(2022/07/23に実行の場合)

in:sent before:2022/07/22 to:sample@example.com

これだと検索対象が前日以前の全期間になり膨大なので、検索範囲を1週間に限定し、

in:sent after:2022/07/16 before:2022/07/22 to:sample@example.com

とします。

ただ、これだとやや問題があって、日時指定でqueryを渡すと、タイムゾーンがUTCになるようです。(参考)
Gmail APIのドキュメントに記載のとおり、UNIX時間に変換することでGmailApp.searchでも日本時間に対応できます。

以上をもとにquery組み立てます。

 //特定の宛先のメールに検索を限定したい場合は指定する。
  const toEmailAddress = []

  //引数の日数分前の日付を返す関数
  const daysBefore = (days) => {
    const d = new Date();
    d.setDate(d.getDate() - days);
    const unixTime = Date.parse(d)/1000
    return unixTime.toFixed()
  }

  let query = `in:sent after:${daysBefore(7)} before:${daysBefore(1)}`
  if (toEmailAddress.length > 0) {
    toEmailAddress.forEach((email) => {
      query += ` to:${email}`
    }
    )
  }
  console.log(`Search query:${query}`)

queryができたら、GmailApp.search(query)でスレッドを検索します。

  const threads = GmailApp.search(query);

これでqueryに該当するGmailThreadクラスの配列が得られました。

2. 「最後の差出人が自分であるもの」「☆マークなし」のスレッドを抽出

続いて、thread.getMessages()[thread.getMessageCount() - 1]で、スレッドの最後のメールを取得し、差出人が自分のアドレスであるものに絞り込みます。

Session.getEffectiveUser().getEmail()は、スクリプトを実行するユーザーのメールアドレスを返します。

 //送信元メールアドレスの指定
  const userEmailAddress = Session.getEffectiveUser().getEmail();
  //有効なメールアドレスかどうか判定する正規表現
  const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-z.A-Z]+/g;

  const threadsToUpdate = [];
  for (let i = 0; i < threads.length; i++) {
    const thread = threads[i];
    const lastMessage = thread.getMessages()[thread.getMessageCount() - 1];
    const lastMessageSender = lastMessage.getFrom().match(emailRegex)[0];
    if (lastMessageSender == userEmailAddress && !lastMessage.isStarred()) {
      threadsToUpdate.push(thread);
    }
  }

3. ラベルの作成・追加

最後に抽出したメールスレッドにラベル付けを行います。

すでにラベルが存在する場合は、既存のラベルの削除し、ラベル付けを更新します。
ラベルの作成は、GmailApp.createLabelを用い、スレッドへのラベルの追加は、GmailLabel.addToThreads(threads)を用います。

 //古いラベルの削除
  const old_label = GmailApp.getUserLabelByName("返信待ち");
  if (old_label) {
    GmailApp.deleteLabel(old_label);
  }

  const label = GmailApp.createLabel("返信待ち");
  label.addToThreads(threadsToUpdate);

これをスクリプトトリガで日毎に実行すると、過去1週間~24時間のメールにラベルが付き、目的のメールを抽出できました。

おわりに

以上のコードをまとめて提示します。

function findUnansweredMail() {
  //特定の宛先のメールに検索を限定したい場合は指定する。
  const toEmailAddress = []

  //引数の日数分前の日付を返す関数
  const daysBefore = (days) => {
    const d = new Date();
    d.setDate(d.getDate() - days);
    const unixTime = Date.parse(d)/1000
    return unixTime.toFixed()
  }

  let query = `in:sent after:${daysBefore(7)} before:${daysBefore(1)}`
  if (toEmailAddress.length > 0) {
    toEmailAddress.forEach((email) => {
      query += ` to:${email}`
    }
    )
  }
  console.log(`Search query:${query}`)

  const threads = GmailApp.search(query);
  if (threads.length == 0) {
    return
  }

  //送信元メールアドレスの指定
  const userEmailAddress = Session.getEffectiveUser().getEmail();
  //有効なメールアドレスかどうか判定する正規表現
  const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-z.A-Z]+/g;

  const threadsToUpdate = [];
  for (let i = 0; i < threads.length; i++) {
    const thread = threads[i];
    const lastMessage = thread.getMessages()[thread.getMessageCount() - 1];
    const lastMessageSender = lastMessage.getFrom().match(emailRegex)[0];
    if (lastMessageSender == userEmailAddress && !lastMessage.isStarred()) {
      threadsToUpdate.push(thread);
    }
  }

  //古いラベルの削除
  const old_label = GmailApp.getUserLabelByName("返信待ち");
  if (old_label) {
    GmailApp.deleteLabel(old_label);
  }

  const label = GmailApp.createLabel("返信待ち");

  label.addToThreads(threadsToUpdate);
}

GmailAppの各種メソッドを覚えておくと、今回の例の他にも「最後のメッセージが自分宛てになっている」といった条件で未返信メールを抽出したりと、いろいろ応用が効きますね。Gmailで使える検索演算子が多少やっかいですが、フィルタ検索でも使えるので覚えておいて損はありません。

GmailをGoogle Apps Scriptで書く記事はネット上に多くありますが、業務上の実際の活用例としてご参考ください。

脚注
  1. こんな具合。少しづつgithubのissue管理に移行中。 ↩︎

Discussion