メインコンテンツへスキップ
見出し画像

大事なメールだけ、Slackに届く。Gmail Push × ルール振り分けの作り方

    こんにちは!Accel-Hack (アクセルハック)開発チームです。

    社内AIアシスタント「Airi」の開発プロセスをお届けする開発記ブログ。
    第21回目の今回は、Google 連携の第二弾――メール連携(gmail機能)をお届けします。

    前回の Vol.20 のカレンダーは、「こちらから尋ねる」機能でした。

    今回はその逆、「向こうから飛んでくる」情報を扱います。

    採用応募や重要な取引先からのメールを、メールボックスを常に見張ることなく、必要なものだけ Slack の担当チャンネルへ自動で届ける。

    その仕組みを支えるのが、Gmail の Push 通知(Webhook) です。



    1. メールは「見張る」より「届かせる」

    大事なメールを見逃さない、と聞いてまず思いつくのは「定期的にメールボックスを確認する」方式でしょう。

    しかしこれは、無駄な確認が多いうえ、確認の間隔だけ通知が遅れます。

    そこで Airi が採ったのは、Gmail の Push 通知です。

    新しいメールが届いた「その瞬間」に、Gmail のほうから「新着があったよ」と Airi に知らせてくれる。

    Airi は普段じっと待っていて、通知が来たときだけ動けばいい。
    ポーリング(見張り)から Push(受け身)へ。

    この切り替えが、速さと効率の両方を手に入れる鍵でした。


    2. Push は「新着があったよ」しか教えてくれない

    ただし、この Push 通知には大きな特徴があります。
    メールの本文は、一切入っていないのです。

    Gmail の Push(裏側では Google Pub/Sub が使われています)で届くのは、たったこれだけ。

    type GmailPushData = {
      emailAddress: string; // どのアカウントに
      historyId: number; // どこまで進んだか、を示す通し番号
    };

    「誰のメールボックスに動きがあったか」と「その履歴番号」だけ。
    つまり Push は「何か届いたよ」という呼び鈴にすぎません。

    中身を知るには、こちらから取りに行く必要があります。

    そして「前回どこまで読んだか」も、自分で覚えておかなければなりません。


    3. historyId で“差分だけ”を取る

    そこで活躍するのが、Vol.7 の Cloudflare KV です。
    Airi は、アカウントごとに「前回どこまで処理したか」を示す historyId を KV に保存しておきます。

    Push が来たら、KV の古い historyId と、今回届いた新しい historyId を比べます。

    const oldHistoryId = await getGmailHistoryId(data.emailAddress);
    if (!oldHistoryId) {
      // 初回は「今ここから見張ります」と印をつけるだけ(過去メールは処理しない)
      await setGmailHistoryId(data.emailAddress, newHistoryId);
      return;
    }
    // 前回からの差分(新しく追加されたメール)だけを取りに行く
    const history = await fetchHistory(data.emailAddress, oldHistoryId);
    for (const item of history.messagesAdded) {
      const message = await fetchMessage(data.emailAddress, item.message.id);
      await processGmailMessage(data.emailAddress, message);
    }
    await setGmailHistoryId(data.emailAddress, newHistoryId); // 印を進める

    ポイントは2つ。
    まず初回は印をつけるだけで、過去のメールを一気に処理しません(連携した瞬間に大量通知が飛ぶ事故を防ぎます)。

    そして毎回、前回からの差分だけを取りに行くので、同じメールを二度処理しません。

    なお、監視対象(watch リスト)に無いアドレスの通知は、そもそも無視します。


    4. ルールで振り分ける

    新着メールを取得できたら、最後は振り分けです。

    すべてのメールを Slack に流しては、ただのノイズになってしまいます。

    Airi は、メールの from(差出人)や subject(件名)を、あらかじめ決めたルールと照らし合わせます。

    for (const rule of gmailConfig) {
      if (matchConditions(emailAddress, from, subject, rule.conditions)) {
        await logics[rule.logic](message);
        return; // 最初に一致したルールだけを実行する
      }
    }

    ルールと、そのときの通知先・処理内容は、すべて設定(gmailConfig)にまとまっています。

    この流れと、ルールのイメージを図にすると次のとおりです。

    画像

    「最初に一致したルールだけを実行して終わり」という割り切りも効いています。

    条件の順序が優先順位になるので、挙動が読みやすい。

    新しい振り分けを足したくなっても、設定にルールを1つ書き加えるだけ。
    コード本体に手を入れる必要はありません。


    5. おわりに

    今回は、Gmail 連携を題材に、「見張る」から「届かせる」への発想転換と、それを支える実装をお届けしました。

    Push で呼び鈴を受け、historyId で差分だけを取り、ルールで必要なメールだけを振り分ける。

    本文の来ない通知、履歴の自前管理といった Gmail ならではのクセはありますが、一つずつ丁寧に捌けば、重要な連絡を見逃さない静かな番人ができあがります。

    さて、ここまで Google との連携を見てきました。

    次回は開発者にとって身近なもう一つの外部サービス、GitHub です。

    「Vol.22: Slackの“あなた”とGitHubの“あなた”をつなぐ。GitHub OAuth連携で作る紐付け基盤」 として、GitHub App を使った OAuth 認証と、Slack ユーザーと GitHub アカウントを紐付ける基盤づくりをお届けします。
    お楽しみに!



    この記事は以下の体制で作成されました。

    • ドラフト作成・執筆: Airi (社内AIアシスタント)

    • アイデア出し・レビュー: CTO

    • デザイン: 事務スタッフ

    Accel-Hackでは、テクノロジーを駆使して世の中の課題を解決していく仲間を募集しています。
    私たちの取り組みに少しでも興味を持っていただけたら、ぜひ採用ページや会社サイトも覗いてみてくださいね。


    あなたへのおすすめ