GmailからLINEにメッセージを「すぐに」届けるための技術

この記事はmhidakaが建立した Advent Calendar 2023の12日目記事です。

こんにちは @tomorrowkey です ひつじさんから特にメンションきていませんが、オッと思ったので書いてみます。

モチベーション

息子が小学生になり、学校からの連絡がメールで届くのですが、Gmailは有象無象のメールがくるので、まぁ気づけません。
うちはだいたいコミュニケーションのハブをLINEにおいており、通知もきちんと消化できている実績があるので、そこに通知すれば見逃さずに済みそうです。
というわけで、よく見かける GAS + LINE Notify で該当のラベルがついたらLINEに通知するようにしました。

初代LINE通知くん

仕組みとしては、GASのスケジュール実行によって定期的にGmail APIを叩いて、該当のラベルの新しいメールが存在するかどうかを確認し、もし新しいメールがあったら、LINE Notifyを叩くといったものになっています。

この仕組みは本当によく見かけるもので、検索してみると山程記事がヒットします。

www.google.com

これはパッと見すごくシンプルでよさそうですが、運用してみると困難な点がいくつか見つかります。

2つの困難な点

リアルタイム性がない

よりリアルタイム性を高めるためには定期的に実行されるペースを短くする必要があります。例えば10分のインターバルで繰り返し実行されるように設定した場合、最大で10分の遅延がおこりえます。

メールが届いたときに即時でLINEにも通知がくるのがベストですが、単純にこのインターバルの時間を短くしてしまうと、今度はGmail APIを叩きすぎてAPI Rate Limitに引っかかってしまいます。

つまりリアルタイムの通知は不可能で、いい塩梅を見つけなければならないのです。

すでに転送済みのメールかどうかわからない

このGASのスクリプトは定期的に実行され、Gmail APIを叩いてメール検索に該当するメールを転送します。しかし、そのメールは前回GASスクリプトを実行したときに転送済みのメールではないでしょうか?同じ内容のメールがLINEに通知されてはノイズになってしまうため、同じメールが繰り返し通知されないようにしなければなりません。

このアプローチとしては「GASスクリプトGoogle Spreadsheetとリンクし通知済みのメールのタイムスタンプを記録する」「繰り返し間隔をもとに検索クエリを工夫する」の2つがあります。

前者アプローチは確実性がありますが、スクリプトの実装が少し複雑になってしまい、メンテナンスが難しくなります。後者のアプローチは、実装の複雑度はあがりませんが、スクリプトの実行タイミングが不安定であることを考えると、取りこぼすメールがでてくる可能性があります。

2代目LINE通知くんを想像する

よくあるLINE通知のスクリプトを使うことでLINEへのメッセージ転送を運用していましたが、ベストな運用ができないことが見えてきたところで、どうやったら改善できるか考えてみました。

2つの困難を一挙に解決するアプローチ

作った仕組みを実際に運用してみると、設計のタイミングで見えてこない困難が見えてくるのは、よくあることです。大切なのはこれでダメだったとへこたれず、よりよいアプローチを考えることですね。ただ困難が見つかるだけではなく、その周りの利用している仕組みに対しても解像度があがっているので、解決するアプローチも見つけやすいので、がんばって考えます。

1代目の仕組みは抽象的にとらえると、Pull型の仕組みになっているため、スクリプトを定期的に実行しなければなりません。定期的に実行するがゆえ、転送したかどうかの管理をする必要があります。では、Pull型の仕組みをPush型の仕組みにできれば、リアルタイム性と転送管理の両方を解決できます。

Gmail APIをながめてみたり、他の人がどんな実装しているのか眺めながらうんうん唸っていたのですが、そもそもメールには古来よりPush型の仕組みがあるなと思い出しました。*1そう「メール転送」です。Gmailにもメール転送の仕組みがあり、メールフィルターの定義で特定のメールアドレスへメールを転送できます。

メール転送をどうやってWebリクエストに変換する?

GmailからPush型のイベントを発火できる見込みがたちました。あとは、このメールの転送をいかにLINE Notify APIにつなげるかです。メールの転送をうけつけることをプリミティブに行うならメールサーバをたてて、そこからフックできそうです。ですが、インフラから作っていては、実現したいことに対してオーバーテクノロジーなように感じます。

こういうときにはSaaSに頼ります。 Mailgun というメール送信のためのSaaSがあります。このサービスには実はメールを送るだけではなく、メールを受け付ける機能もついています。しかもそのメール受信をきっかけに特定のURLを叩くことができます。つまりMailgunを使えば、メール転送をHTTPリクエストに変換できそうです。*2

2代目LINE通知くんの仕組み

困難な点を改善できそうな目論見ができたので、全体像を描いてみます。

処理の流れが1方向にだけ進み、とてもシンプルなものになりました。

ここからは細かい設定やコードの実装例を挙げます。

GAS

依存の関係性からまずはGASスクリプトから作るとよいです。 実装例は次のとおりです。*3

function doPost(event) {
  const token = 'YOUR LINE NOTIFY TOKEN HERE'
  const mailBody = event.parameters['body-plain'][0]

  const url = 'https://notify-api.line.me/api/notify'
  const options = {
    method: 'post',
    payload: `message=\n${mailBody}`,
    headers: {
      Authorization: `Bearer ${token}`,
    }
  }

  UrlFetchApp.fetch(url, options)
}

注意点としては、MailgunからのHTTPリクエストを受け付ける必要があるので、WebAppとしてデプロイし、認証なしでアクセスできるように設定します。

デプロイ完了したときのURLを手元に控えておきます。

Mailgun

Mailgunではメール受信をHTTPリクエストに変換する設定をします。 新しい受信ルールを作成します。

さきほど控えておいたGAS WebAppのURLをForwardのテキストに入力します。

注意点としては、すでにMailgunを他のプロダクトに使用している場合、メール受信の条件(Expression)も設定し、通知すべきものとそうではないものを区別する必要があります。

Gmail

Gmailではメール転送を設定します。転送先のメールアドレスはMailgunで無料で割り当てられるドメインを使います。

メールアドレスのユーザー名にあたる部分は自由に決めてしまって問題ありません。 MailgunのExpressionを設定する場合はこのユーザー名も含めて条件を設定するとよさそうですね。

Gmailへの設定方法は公式ヘルプやさまざまなメディアで紹介されているので、そちらを参照してください。

support.google.com

さいごに

以上で2代目LINE通知くんの仕組みが完成しました。初代のよくみる構成と違ってメールSaaSの設定がある点が面倒ですが、そこにある課題を見事に解決できたので、とても満足しています。こういう「ちょっとうまくいかない」を技術で解決できたときの快感はいくつになってもいいもんだなと思いますね。特にメール転送という古来からある仕組みを活用できたところが個人的にポイント高かったです。

来年もこういったものづくりを続けていきたいなと思います。

*1:正確にはSlackへのメール通知の設定しているシーンを見て、思い出しました

*2:メール送信によく使われるSendGridにも同等の機能があるので、そちらでも実現できます。

*3:私は google/clasp を使いTypeScriptで書いていることもあり、このコードの動作は確認していません