【GAS】GmailをAI要約してDiscordに通知するシステムの作成

作成のきっかけ

皆さんは連絡ツール、何を使用していますか?おそらく大半の人が、「LINE」や「Instagram」を使用しているかと思われます。東京工科大学の2025年5月13日掲載の、新⼊⽣の「コミュニケーションツール」利⽤実態調査によると、1位から3位まではLINE(98.3%)、InstagramのDM(51.3%)、Discord(23.9%)となっているようです。私もよく使用するツールですが、目をつけたのは「Discord」です。Discordは世界でユーザー数が6億人をこえるコミュニケーションツールであり、ゲームを中心とする利用者が多いサービスです。Discordの便利さなどは省略しますが、Discordの便利な機能として、連携サービスのWeb Hookと呼ばれる機能があります。この機能を使用することで、Gmailで受信したメールをDiscordで表示することができるのではないかと考えたわけです。これによって自分宛に届いたメールを複数の人に共有できたり、わざわざメールアプリを開かなくてもDiscordだけでメールの内容を確認するところまで完結できるわけです。すごく便利ですよね?早速構築しましょう!

構築方法

技術スタック

  • 実行環境 : Discord ver 0.0.371 (0.0.371)
  • バックエンド処理 : Google Apps Script (GAS)
  • AIモデル : Google Gemini API (Google AI Studioより取得)
  • 通知の送信先 : Discord Webhook
  • メールソース : Gmail

システムアーキテクチャ

今回作成したシステムの動作は以下の通りです。

1, GASのトリガーが5分ごとに起動。

まずはGmailにメールが届いているかを確認することから始まります。GASには、何分おきにプログラムを実行するかという「トリガー」機能があります。今回は5分おきにメール届いてないかチェックする設定にしました。

2, Gmailから特定の条件(送信元アドレスなど)に一致する未読メールを取得

皆さんのメールアドレスには、日々いろんなメールが来ますよね?そんなメールを全部Discordに送ると、負担も大きくなり、必要なメールに気づかない可能性があります。そのため、今回は特定のアドレスから届いたメールのみをDiscordに表示するようにしています。

3, メール本文を整形し、Gemini APIに送信。

受け取ったメールからAPIに負担がかからない程度まで字数を減らし、AIに投げるプロンプトに先ほどの内容を埋め込む処理を行います。今回はメールが長すぎた場合、「文頭から4000字まで」としています。こうすることで、処理を軽くし、トークン制限を超えないようにしています。

4, Geminiが「要約」と「アクションURLの抽出」を実行

APIに送信したら、Geminiがその内容を受け取り、メールを要約する処理を行います。今回構築したシステムでは、「重要なポイントを箇条書き3点以内。挨拶不要」と「重要なURL(申込、登録など)があれば「 [用途]: URL」の形式で抽出。なければ表示しない。」の2つを指示しています。特にURLに関しては、「登録はこちら!」などのURLをDiscordから一発で開けたら便利だと感じたため、Geminiに重要だと判断したURLを抽出するようにしています。

5, 結果を整形し、Discord WebhookへPOST送信

Geminiが要約した内容をDiscordが表示できる形式に変換し、Discordのサーバーに送り付けます。基本AIが出力するテキストデータは非常に読みづらいため、メールの件名のタイトル化、URLのリンク化、色付け(青色)、送信元,日時,要約のフィールド分割を行い視認性を向上させています。

6, 処理済みメールにラベルを付与してループを防止

Discordにデータを送った後、同じメールが再度Discordに送られることを防止するために、処理が終わったメールにはラベルを付与することによって、ループを防止しています。ラベルの付与に関してはGmailのラベル機能を、「データベースのフラグ」代わりに使用しています。

構築手順

1, DiscordのウェブフックURLの貼り付け

まずは通知を送りたいサーバーのチャンネルからウェブフックURLの取得を行います。取得にはチャンネル名横の歯車マークから、連携サービス、ウェブフックを作成ボタンをクリックします。

作成したウェブフックをクリックして、ウェブフックURLをコピーをクリックしてください。ポップアップなどは出てきませんが、ちゃんとコピーできているので安心してください。コピーしたURLはどこかにペーストして残しておきましょう。

2, Google Gemini APIの取得

続いてAPIの取得を行います。今回は私がメインで使用しているGeminiをしようと思います。GeminiのAPIは「Google AI Studio」から取得することができます。取得は下のリンクをご利用ください。

https://aistudio.google.com/app/u/0

APIの取得は「Get API Key」というところから行います。左下にある鍵マークを探してみてください。APIキーのページまで移動できたら、右上のAPIキーを作成をクリックしてください。「キー名の設定」についてはわかりやすい名前を入力するといいです。「プロジェクトを選択」に関してはDefault Gemini Projectで大丈夫です。作成が完了したら作成したAPIの「キー」をクリックしてみてください。「AIza…」と始まる長いものがAPIキーとなっています。こちらもウェブフックURLと同様にコピーして残しておきましょう。

3, 通知したい送信元のメールアドレスの記録

最後に通知したい送信元のメールアドレスを記録してください。こちらは〇〇@gmail.comの形式で大丈夫です。

4, プログラムへの埋め込み

ここからはプログラムの構築を行います。今回の構築はGASを使用します。GASはGoogle App Scriptというサービスの略称で、ブラウザで動作するVS codeと考えてもらっていいです。アクセスは下のURLをご利用ください。

https://script.google.com/home

プロジェクトの作成方法については、ネットに上がっているものを参照していただければと思います。今回作成したプログラムを貼り付けますので、もし真似したい方はこちらのコードをそのままコピーして使用してください。

const WEBHOOK_URL = 'ここにDiscordのウェブフックURLを貼り付け';
const TARGET_FROM = '[email protected]'; 
const GEMINI_API_KEY = 'ここに取得したGeminiのAPIキーを貼り付け';

function sendGmailToDiscordWithAI() {
  const query = `from:${TARGET_FROM} is:unread -label:DiscordSent`;
  const threads = GmailApp.search(query, 0, 5);
  
  if (threads.length === 0) return;

  let label = GmailApp.getUserLabelByName("DiscordSent");
  if (!label) {
    label = GmailApp.createLabel("DiscordSent");
  }

  for (const thread of threads) {
    const messages = thread.getMessages();
    const message = messages[messages.length - 1];
    
    // 重複チェック
    const labels = thread.getLabels();
    if (labels.some(l => l.getName() === "DiscordSent")) continue;

    const subject = message.getSubject();
    const from = message.getFrom();
    const date = Utilities.formatDate(message.getDate(), Session.getScriptTimeZone(), "MM/dd HH:mm");
    const body = message.getPlainBody();
    const threadPermalink = thread.getPermalink();

    // AI要約を実行
    let aiContent = "";
    try {
      aiContent = summarizeByGemini(body);
    } catch (e) {
      // エラーの詳細をDiscordに表示させる
      aiContent = "⚠️ **AIエラー発生**\n```" + e.message + "```\n" + body.substring(0, 100) + "...";
      console.error(e);
    }

    const payload = {
      embeds: [{
        title: `📩 ${subject}`,
        url: threadPermalink,
        color: 3447003,
        fields: [
          { name: "送信元", value: from, inline: true },
          { name: "受信日時", value: date, inline: true },
          { name: "🤖 AI要約 & アクション", value: aiContent } 
        ],
        footer: { text: "クリックしてGmailで全文を読む" }
      }]
    };

    const options = {
      method: "post",
      contentType: "application/json",
      payload: JSON.stringify(payload)
    };

    UrlFetchApp.fetch(WEBHOOK_URL, options);
    thread.addLabel(label);
  }
}

// Gemini APIを呼び出す関数
function summarizeByGemini(text) {
  const model = 'gemini-flash-latest';
  const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_API_KEY}`;
  
  const cutText = text.substring(0, 4000);

  const prompt = `
    以下のメール本文を読み、次の2つの情報を日本語で出力してください。
    1. 【内容の要約】重要なポイントを箇条書き3点以内。挨拶不要。
    2. 【アクション用URL】重要なURL(申込、登録など)があれば「🔗 [用途]: URL」の形式で抽出。なければ表示しない。
    
    【メール本文】
    ${cutText}
  `;

  const payload = {
    contents: [{ parts: [{ text: prompt }] }]
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    muteHttpExceptions: true // エラーでも止まらずレスポンスを受け取る
  };

  const response = UrlFetchApp.fetch(apiUrl, options);
  const responseCode = response.getResponseCode();
  const responseText = response.getContentText();
  const json = JSON.parse(responseText);

  // 成功(200)以外なら、Googleから返ってきたエラーメッセージを投げる
  if (responseCode !== 200) {
    let errorMsg = `Status: ${responseCode}`;
    if (json.error && json.error.message) {
      errorMsg += `\nMessage: ${json.error.message}`;
    } else {
      errorMsg += `\nBody: ${responseText}`;
    }
    throw new Error(errorMsg);
  }

  if (json.candidates && json.candidates.length > 0) {
    return json.candidates[0].content.parts[0].text;
  } else {
    throw new Error("応答データが空でした: " + responseText);
  }
}

GASに貼り付けが完了したらCtrl+s or cmd+sで保存し、左上の実行をクリックしてください。エラーメッセージが出てこなければ成功です!

5, GASのトリガー設定

エラーが出ないことを確認したら、続いてプログラムを実行するために「トリガー」を設定します。詳しくは「システムアーキテクチャ」の1で解説しています。設定は左側のメニューバーからタイマーのマークがある「トリガー」をクリックします。右下の「トリガーを追加」から、以下のように設定してください。

関数とデプロイとイベントソースについてはいじらないようにしてください。トリガーのソースと時間の間隔については、個人で調節してもらって大丈夫です。設定が終わったら、「保存」をクリックしてください。最初はアカウントにログインして権限を付与する作業が発生しますが、個人で設定を変える部分はないため、デフォルトのままで大丈夫です。

検証

それでは早速、今回作成したシステムがきちんと動作するのか試してみましょう!メールに関しては、登録したアカウントから自分送ってみます。まずは、AIがきちんと要約できるのかから検証しましょう。

しっかり動作していますね!送信元に関しては伏せておきます。メールの内容をしっかり読んで、3行でまとめるというプロンプトにきちんとしたがっているという印象です。メールにURLは埋め込んでいないため、URLの記入欄は消えているのも高得点です!

では続いて、メールにURLを埋め込んだ時、きちんとURLを認識して表示することはできるのか?試してみましょう!そして今回は、少し意地悪してフェイクのURLを入れてみます。よくメールで、資料はこちら!などそこまで重要なURLが貼り付けられていることありますよね?これを果たして認識できるのか…?今回送るメールはこちら。

会議のお知らせにしてはかなりアバウトになってしまいましたが、とりあえず問題ないです。そしてDiscordを見てみると?

こちらもちゃんと動作していますね!先ほどと同じく内容を3行にまとめられているのに加えて、会議の日程や資料の事前ダウンロード、アンケートへの回答など、重要な情報を漏らさず要約できているのも素晴らしいです。そして、URLは2つ表示するというのも高得点です。きちんと何がどのURLなのかを表記するのも素晴らしいです。ということで、今回のシステム無事完成、と言っていいのではないでしょうか?

学び

今回の作成を通して、以下の知見を得られました。

  • 生成APIの実用的な取り組み

単なるチャットボットではなく、システムの一部としてAIを機能させられる方法を得られました。

  • APIのバージョン管理

今回の作成で使用いたGemini APIはクラウドAIですが、クラウドAIはモデルの入れ替わりが早いため、柔軟なコード設計が必要であることがわかりました。作成中、指定したモデルバージョンが古かったことや、利用できなくなるエラーに直面しました。そのため、特定のバージョン番号に依存せず、常に最新の軽量モデルを指すエイリアスgemini-flash-latest を採用することで、メンテナンス性を向上させました。

  • フェイルセーフ機能の実装

作成にあたり、ブラウザの検証とは違い、エラーメッセージが取得できないというエラーがありました。そのため、APIエラー時やAIの応答が不安定な場合でもシステムが留まらないよう、try-catch構文でエラーを捕捉し、Discord側には「⚠️ AIエラー」と表示しつつ、メール原文の一部を表示するフェイルセーフを実装しました。

作成にAIを搭載することは過去に数度ありましたが、今回はDiscordを使用するという私史上前例のない開発だったため、上記の内容以外にも数多くのトラブルがありました。しかし、先人の知識やAIとの開発を行うことによって、より素早く構築することができたと感じています。またAIを搭載したシステムは、今後より需要が高まり、できることの幅が広がるという点を考慮すると、今回の作成の経験はより多くのシステム開発に役立てられると言えます。

今後の計画

今回作成したシステムはより、便利に使用できるよう以下の機能を追加しようと考えています。

1, 添付ファイル(PDFなど)の中身の読み取り

現在はまだ試していませんが、メールといえば添付ファイルがあることが多いですよね?PDFファイルなどが添付されていれば、OCRで読み取って要約することでより便利に使うことができるのではないかと考えています。ただAPIへのトークンが超えないかなども考慮する必要があるため、より技術力を磨いて挑戦しようと思います。

2, 複数のチャンネルを活用したメールの仕分け機能

現在は一つのチャンネルにあたり、一つのメールアドレスから受信したメールのみを表示するようにしています。しかし言い換えると、一つのアドレスからのメールしか表示できないともいえます。これでは少し利便性に欠けますよね?ということで、今後はより多くのメールアドレスを登録し、複数のチャンネルに分けて受信したメールを表示できるようにすると、メールアプリの代用になる日も遠くはないかもしれません。

3, Slackなどのサービスへの展開

今回はDiscordを使用したシステムですが、やはりDiscordだけでなく、より多くのサービスでも利用できるようになると便利ですよね?例えばビジネスシーンでは定番のSlackや、日常使いで定番のLINEでも同じように動作できるようにしていこうと思います。

宣伝

このサイトでは、他にも技術的な取り組みを定期的に更新しております。もしご興味がありましたら、ぜひご覧ください!

より詳細な作成方法について

今回のシステムの作成について、万が一問題が発生した場合や、ご不明点などございましたら、ぜひ下記のコメントをご利用ください。

コメントする

Back to top