Gemini と Vertex AI を使用して Gmail メッセージを分析してラベル付けする

このソリューションでは、Vertex AI と Gemini を使用して Gmail メッセージを分析し、感情に基づいてラベル付けします。

コーディング レベル: 中級
所要時間: 30 分
プロジェクト タイプ: Google Workspace アドオン

  • サイドバーで Gmail を拡張する Google Workspace アドオン。
    図 1: Sentiment Analysis アドオンは、Gmail にサイドバーを表示します。ユーザーは Gemini にプロンプトを送信して、感情に基づいてメッセージを分析し、ラベルを適用できます。
  • ニュートラルな感情を含む Gmail メッセージ。
    図 2: アドオンが Gmail メッセージに「中立的なトーン 😐」というラベルを付けます。
  • ポジティブな感情を含む Gmail メッセージ。
    図 3: アドオンが Gmail メッセージに「HAPPY TONE 😊」というラベルを付けます。
  • 不満の感情を含む Gmail メッセージ。
    図 4: アドオンが Gmail メッセージに「UPSET TONE 😡」というラベルを付けます。

目標

  • ソリューションの機能を理解します。
  • ソリューション内で Google サービスが何を行うかを理解する。
  • 環境を設定します。
  • Google Apps Script プロジェクトを設定します。
  • スクリプトを実行します。

このソリューションについて

感情分析 Google Workspace アドオンのスクリーンショット

このソリューションは、Gmail メッセージの感情に基づいてラベルを適用する Google Workspace アドオンです。アドオンは、メッセージの内容を分析するために、Vertex AI を使用して Gemini 2.5 Flash モデルにプロンプトを送信し、次のいずれかの感情を返します。

  • 好ましい
  • 陰性
  • どちらとも言えない

Gemini からの回答に基づいて、アドオンは対応する Gmail ラベルをメッセージに適用します。

Vertex AI API へのリクエストを制限するため、このアドオンは Gmail ユーザーの受信トレイにある最新の 10 件のメッセージのみを分析し、ラベルを適用します。割り当てと上限の詳細については、Vertex AI のドキュメントをご覧ください。

仕組み

このソリューションは Google Apps Script で構築されており、次の Google サービスとプロダクトを使用します。

  • Vertex AI API - Gemini 2.5 Flash モデルに Gmail メッセージのコンテンツを分析して感情を特定するよう指示します。
  • Apps Script サービス:

    • Gmail サービス - Gmail メッセージのラベルを取得して適用します。必要に応じて、アドオンのテスト用のサンプル メッセージを作成します。
    • カードサービス - Gmail のサイドバーに表示されるアドオンのユーザー インターフェースを作成します。
    • URL Fetch サービス - 感情分析のために Vertex AI API に接続します。
    • スクリプト サービス - Vertex AI API を呼び出すために、getOAuthToken メソッドを使用してアドオンの OAuth 2.0 アクセス トークンを取得します。

前提条件

環境の設定

このセクションでは、Google Cloud コンソールと Apps Script で環境を構成して設定する方法について説明します。

Google Cloud コンソールで Cloud プロジェクトを構成する

このセクションでは、Cloud プロジェクトで Vertex AI API を有効にして OAuth 同意画面を構成する方法について説明します。

Vertex AI API を有効にする

  1. Google Cloud コンソールで、Google Cloud プロジェクトを開き、Vertex AI API を有効にします。

    API の有効化

  2. 正しい Cloud プロジェクトで API を有効にしていることを確認し、[次へ] をクリックします。

  3. 正しい API を有効にしていることを確認し、[有効にする] をクリックします。

OAuth 同意画面を構成する

Google Workspace アドオンには同意画面の構成が必要です。アドオンの OAuth 同意画面を構成すると、Google がユーザーに表示する内容が定義されます。

  1. Google Cloud コンソールで、メニュー > > [ブランディング] に移動します。

    [ブランディング] に移動

  2. をすでに構成している場合は、[ブランディング]、[ユーザー]、[データアクセス] で次の OAuth 同意画面の設定を構成できます。[ まだ設定されていません] というメッセージが表示された場合は、[使ってみる] をクリックします。
    1. [アプリ情報] の [アプリ名] に、アプリの名前を入力します。
    2. [ユーザー サポートメール] で、ユーザーが同意について問い合わせる際に使用するサポートのメールアドレスを選択します。
    3. [続行] をクリックします。
    4. [対象] で [内部] を選択します。
    5. [続行] をクリックします。
    6. [連絡先情報] で、プロジェクトに対する変更の通知を受け取るメールアドレスを入力します。
    7. [続行] をクリックします。
    8. [完了] で、Google API サービスのユーザーデータに関するポリシーを確認し、同意する場合は [Google API サービス: ユーザーデータに関するポリシーに同意します] を選択します。
    9. [続行] をクリックします。
    10. [作成] をクリックします。
  3. 現時点では、スコープの追加はスキップできます。今後、Google Workspace 組織外で使用するアプリを作成する場合は、[ユーザータイプ] を [外部] に変更する必要があります。次に、アプリに必要な認可スコープを追加します。詳細については、OAuth 同意画面を構成するの完全なガイドをご覧ください。

Apps Script プロジェクトを作成して設定する

アドオン用の Apps Script プロジェクトを作成して設定するには、次の手順を行います。

  1. 次のボタンをクリックして、Gemini と Vertex AI を使用した Gmail の感情分析 Apps Script プロジェクトを開きます。
    Apps Script プロジェクトを開く

  2. [概要] をクリックします。

  3. 概要ページで [コピーを作成] コピーを作成するためのアイコン をクリックします。

  4. Cloud プロジェクトの番号を取得します。

    1. Google Cloud コンソールで、メニュー アイコン > [IAM と管理] > [設定] に移動します。

      [IAM と管理] の [設定] に移動

    2. [プロジェクト番号] フィールドで、値をコピーします。
  5. Cloud プロジェクトを Apps Script プロジェクトに接続します。

    1. コピーした Apps Script プロジェクトで、[プロジェクトの設定] プロジェクト設定のアイコン をクリックします。
    2. [Google Cloud Platform(GCP)プロジェクト] で、[プロジェクトを変更] をクリックします。
    3. [GCP プロジェクト番号] に、Cloud プロジェクト番号を貼り付けます。
    4. [プロジェクトを設定] をクリックします。

アドオンをテストする

アドオンを試すには、テスト デプロイをインストールしてから、Gmail でアドオンを開きます。

  1. Apps Script のテスト用デプロイを作成してインストールします。
    1. コピーした Apps Script プロジェクトで、[エディタ] をクリックします。
    2. Code.gs ファイルを開き、[実行] をクリックします。メッセージが表示されたら、スクリプトを承認します。
    3. [Deploy](デプロイ)> [Test deployments](テスト デプロイ)をクリックします。
    4. [インストール] > [完了] をクリックします。
  2. Gmail を起動します。

    Gmail にアクセス

  3. 右側のサイドバーで、アドオン Sentiment Analysis を開きます。

  4. メッセージが表示されたら、アドオンを承認します。

  5. 省略可: アドオンでテストするメッセージを作成するには、[サンプル メールを生成] をクリックします。受信トレイに 3 件のメッセージが表示されます。表示されない場合は、ページを更新してください。

  6. ラベルを追加するには、[メールを分析] をクリックします。

アドオンは受信トレイの最新の 10 件のメッセージを確認し、メッセージの内容に基づいて次のいずれかのラベルを適用します。

  • HAPPY TONE 😊
  • ニュートラルなトーン 😐
  • UPSET TONE 😡

コードを確認する

このソリューションの Apps Script コードを確認します。

ソースコードを表示

コード.gs

gmail-sentiment-analysis/Code.gs
/*
Copyright 2024 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Triggered when the add-on is opened from the Gmail homepage.
 *
 * @param {Object} e - The event object.
 * @returns {Card} - The homepage card.
 */
function onHomepageTrigger(e) {
  return buildHomepageCard();
}

Cards.gs

gmail-sentiment-analysis/Cards.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Builds the main card displayed on the Gmail homepage.
 *
 * @returns {Card} - The homepage card.
 */
function buildHomepageCard() {
  // Create a new card builder
  const cardBuilder = CardService.newCardBuilder();

  // Create a card header
  const cardHeader = CardService.newCardHeader();
  cardHeader.setImageUrl('https://fonts.gstatic.com/s/i/googlematerialicons/mail/v6/black-24dp/1x/gm_mail_black_24dp.png');
  cardHeader.setImageStyle(CardService.ImageStyle.CIRCLE);
  cardHeader.setTitle("Analyze your Gmail");

  // Add the header to the card
  cardBuilder.setHeader(cardHeader);

  // Create a card section
  const cardSection = CardService.newCardSection();

  // Create buttons for generating sample emails and analyzing sentiment
  const buttonSet = CardService.newButtonSet();

  // Create "Generate sample emails" button
  const generateButton = createFilledButton('Generate sample emails', 'generateSampleEmails', '#34A853');
  buttonSet.addButton(generateButton);

  // Create "Analyze emails" button
  const analyzeButton = createFilledButton('Analyze emails', 'analyzeSentiment', '#FF0000');
  buttonSet.addButton(analyzeButton);

  // Add the button set to the section
  cardSection.addWidget(buttonSet);

  // Add the section to the card
  cardBuilder.addSection(cardSection);

  // Build and return the card
  return cardBuilder.build();
}

/**
 * Creates a filled text button with the specified text, function, and color.
 *
 * @param {string} text - The text to display on the button.
 * @param {string} functionName - The name of the function to call when the button is clicked.
 * @param {string} color - The background color of the button.
 * @returns {TextButton} - The created text button.
 */
function createFilledButton(text, functionName, color) {
  // Create a new text button
  const textButton = CardService.newTextButton();

  // Set the button text
  textButton.setText(text);

  // Set the action to perform when the button is clicked
  const action = CardService.newAction();
  action.setFunctionName(functionName);
  textButton.setOnClickAction(action);

  // Set the button style to filled
  textButton.setTextButtonStyle(CardService.TextButtonStyle.FILLED);

  // Set the background color
  textButton.setBackgroundColor(color);

  return textButton;
}

/**
 * Creates a notification response with the specified text.
 *
 * @param {string} notificationText - The text to display in the notification.
 * @returns {ActionResponse} - The created action response.
 */
function buildNotificationResponse(notificationText) {
  // Create a new notification
  const notification = CardService.newNotification();
  notification.setText(notificationText);

  // Create a new action response builder
  const actionResponseBuilder = CardService.newActionResponseBuilder();

  // Set the notification for the action response
  actionResponseBuilder.setNotification(notification);

  // Build and return the action response
  return actionResponseBuilder.build();
}

Gmail.gs

gmail-sentiment-analysis/Gmail.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Analyzes the sentiment of the first 10 threads in the inbox
 * and labels them accordingly.
 *
 * @returns {ActionResponse} - A notification confirming completion.
 */
function analyzeSentiment() {
  // Analyze and label emails
  analyzeAndLabelEmailSentiment();

  // Return a notification
  return buildNotificationResponse("Successfully completed sentiment analysis");
}

/**
 * Analyzes the sentiment of emails and applies appropriate labels.
 */
function analyzeAndLabelEmailSentiment() {
  // Define label names
  const labelNames = ["HAPPY TONE 😊", "NEUTRAL TONE 😐", "UPSET TONE 😡"];

  // Get or create labels for each sentiment
  const positiveLabel = GmailApp.getUserLabelByName(labelNames[0]) || GmailApp.createLabel(labelNames[0]);
  const neutralLabel = GmailApp.getUserLabelByName(labelNames[1]) || GmailApp.createLabel(labelNames[1]);
  const negativeLabel = GmailApp.getUserLabelByName(labelNames[2]) || GmailApp.createLabel(labelNames[2]);

  // Get the first 10 threads in the inbox
  const threads = GmailApp.getInboxThreads(0, 10);

  // Iterate through each thread
  for (const thread of threads) {
    // Iterate through each message in the thread
    const messages = thread.getMessages();
    for (const message of messages) {
      // Get the plain text body of the message
      const emailBody = message.getPlainBody();

      // Analyze the sentiment of the email body
      const sentiment = processSentiment(emailBody);

      // Apply the appropriate label based on the sentiment
      if (sentiment === 'positive') {
        thread.addLabel(positiveLabel);
      } else if (sentiment === 'neutral') {
        thread.addLabel(neutralLabel);
      } else if (sentiment === 'negative') {
        thread.addLabel(negativeLabel);
      }
    }
  }
}

/**
 * Generates sample emails for testing the sentiment analysis.
 *
 * @returns {ActionResponse} - A notification confirming email generation.
 */
function generateSampleEmails() {
  // Get the current user's email address
  const userEmail = Session.getActiveUser().getEmail();

  // Define sample emails
  const sampleEmails = [
    {
      subject: 'Thank you for amazing service!',
      body: 'Hi, I really enjoyed working with you. Thank you again!',
      name: 'Customer A'
    },
    {
      subject: 'Request for information',
      body: 'Hello, I need more information on your recent product launch. Thank you.',
      name: 'Customer B'
    },
    {
      subject: 'Complaint!',
      body: '',
      htmlBody: `<p>Hello, You are late in delivery, again.</p>
<p>Please contact me ASAP before I cancel our subscription.</p>`,
      name: 'Customer C'
    }
  ];

  // Send each sample email
  for (const email of sampleEmails) {
    GmailApp.sendEmail(userEmail, email.subject, email.body, {
      name: email.name,
      htmlBody: email.htmlBody
    });
  }

  // Return a notification
  return buildNotificationResponse("Successfully generated sample emails");
}

Vertex.gs

gmail-sentiment-analysis/Vertex.gs
/*
Copyright 2024-2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Replace with your project ID
const PROJECT_ID = '[ADD YOUR GCP PROJECT ID HERE]';

// Location for your Vertex AI model
const VERTEX_AI_LOCATION = 'us-central1';

// Model ID to use for sentiment analysis
const MODEL_ID = 'gemini-2.5-flash';

/**
 * Sends the email text to Vertex AI for sentiment analysis.
 *
 * @param {string} emailText - The text of the email to analyze.
 * @returns {string} - The sentiment of the email ('positive', 'negative', or 'neutral').
 */
function processSentiment(emailText) {
  // Construct the API endpoint URL
  const apiUrl = `https://${VERTEX_AI_LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${VERTEX_AI_LOCATION}/publishers/google/models/${MODEL_ID}:generateContent`;

  // Prepare the request payload
  const payload = {
    contents: [
      {
        role: "user",
        parts: [
          {
            text: `Analyze the sentiment of the following message: ${emailText}`
          }
        ]
      }
    ],
    generationConfig: {
      temperature: 0.9,
      maxOutputTokens: 1024,
      responseMimeType: "application/json",
      // Expected response format for simpler parsing.
      responseSchema: {
        type: "object",
        properties: {
          response: {
            type: "string",
            enum: ["positive", "negative", "neutral"]
          }
        }
      }
    }
  };

  // Prepare the request options
  const options = {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    },
    contentType: 'application/json',
    muteHttpExceptions: true, // Set to true to inspect the error response
    payload: JSON.stringify(payload)
  };

  // Make the API request
  const response = UrlFetchApp.fetch(apiUrl, options);

  // Parse the response. There are two levels of JSON responses to parse.
  const parsedResponse = JSON.parse(response.getContentText());
  const sentimentResponse = JSON.parse(parsedResponse.candidates[0].content.parts[0].text).response;

  // Return the sentiment
  return sentimentResponse;
}

appsscript.json

gmail-sentiment-analysis/appsscript.json
{
  "timeZone": "America/Toronto",
  "oauthScopes": [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/gmail.addons.execute",
    "https://www.googleapis.com/auth/gmail.labels",
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/userinfo.email"
  ],
  "addOns": {
    "common": {
      "name": "Sentiment Analysis",
      "logoUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/sentiment_extremely_dissatisfied/v6/black-24dp/1x/gm_sentiment_extremely_dissatisfied_black_24dp.png"
    },
    "gmail": {
      "homepageTrigger": {
        "runFunction": "onHomepageTrigger",
        "enabled": true
      }
    }
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、Cloud プロジェクトを削除することをおすすめします。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。メニュー アイコン > [IAM と管理] > [リソースの管理] をクリックします。

    Resource Manager に移動します。

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ