使用 Gemini 和 Vertex AI 分析及標記 Gmail 郵件

這項解決方案會使用 Vertex AI 和 Gemini 分析 Gmail 郵件,並根據郵件的情緒為郵件加上標籤。

程式碼程度:中級
時間長度:30 分鐘
專案類型:Google Workspace 外掛程式

  • Google Workspace 外掛程式,可在側欄擴充 Gmail 功能。
    圖 1:「情緒分析」外掛程式會在 Gmail 中顯示側欄,使用者可以提示 Gemini 分析郵件情緒並套用標籤。
  • 情緒為中性的 Gmail 郵件。
    圖 2:外掛程式為 Gmail 郵件加上「語氣中性 😐」標籤。
  • 含有正面情緒的 Gmail 郵件。
    圖 3:外掛程式會為 Gmail 郵件加上「HAPPY TONE 😊」標籤。
  • 含有負面情緒的 Gmail 郵件。
    圖 4:外掛程式會為 Gmail 郵件加上「UPSET TONE 😡」標籤。

目標

  • 瞭解解決方案的功能。
  • 瞭解解決方案中的 Google 服務功能。
  • 設定環境。
  • 設定 Google Apps Script 專案。
  • 執行指令碼。

認識這項解決方案

情緒分析 Google Workspace 外掛程式的螢幕截圖

這項解決方案是 Google Workspace 外掛程式,可根據 Gmail 郵件的情緒套用標籤。為分析郵件內容,外掛程式會使用 Vertex AI 提示 Gemini 2.5 Flash 模型,並傳回下列其中一種情緒:

  • 正面
  • 負面
  • 普通

根據 Gemini 的回覆,外掛程式會為郵件套用相應的 Gmail 標籤。

為限制對 Vertex AI API 的要求,這個外掛程式只會分析 Gmail 使用者收件匣中最近的 10 封郵件,並套用標籤。如要進一步瞭解配額和限制,請參閱 Vertex AI 說明文件

運作方式

這項解決方案是以 Google Apps Script 建構而成,並使用下列 Google 服務和產品:

必要條件

設定環境

本節說明如何在 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. 在「App Information」(應用程式資訊) 下方的「App name」(應用程式名稱) 欄位中,輸入應用程式名稱。
    2. 在「使用者支援電子郵件」中,選擇支援電子郵件地址,方便使用者在同意聲明方面有任何疑問時與您聯絡。
    3. 點選 [下一步]
    4. 在「觀眾」下方,選取「內部」
    5. 點選 [下一步]
    6. 在「聯絡資訊」下方,輸入可接收專案異動通知的電子郵件地址
    7. 點選 [下一步]
    8. 在「完成」下方,詳閱《Google API 服務:使用者資料政策》,然後選取「我同意《Google API 服務:使用者資料政策》」
    9. 按一下 [繼續]。
    10. 按一下「Create」(建立)。
  3. 目前可以略過新增範圍。 日後為 Google Workspace 機構以外的使用者建立應用程式時,請務必將「使用者類型」變更為「外部」。然後新增應用程式需要的授權範圍。詳情請參閱完整的「設定 OAuth 同意畫面」指南。

建立及設定 Apps Script 專案

如要為外掛程式建立及設定 Apps Script 專案,請完成下列步驟:

  1. 按一下下列按鈕,開啟「Gmail Sentiment Analysis with Gemini and Vertex AI」 Apps Script 專案。
    開啟 Apps Script 專案

  2. 按一下「總覽」

  3. 在總覽頁面中,按一下「建立副本」圖示 建立副本的圖示

  4. 取得 Cloud 專案編號:

    1. 在 Google Cloud 控制台中,依序前往「選單」圖示 >「IAM 與管理」 >「設定」

      前往「IAM & Admin Settings」(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. 在右側邊欄中,開啟「情緒分析」外掛程式。

  4. 如果系統顯示提示,請授權使用外掛程式。

  5. 選用:如要建立訊息來測試外掛程式,請按一下「產生範例電子郵件」。收件匣中會顯示三則訊息。如果沒有看到,請重新整理頁面。

  6. 如要新增標籤,請按一下「分析電子郵件」

外掛程式會檢查收件匣中最新的 10 封郵件,然後根據郵件內容套用下列其中一個標籤:

  • 快樂語氣 😊
  • 中性語氣 😐
  • UPSET TONE 😡

檢查程式碼

請查看這項解決方案的 Apps Script 程式碼:

查看原始碼

Code.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. 在專案清單中選取要刪除的專案,然後按一下「Delete」(刪除) 圖示
  3. 在對話方塊中輸入專案 ID,然後按一下「Shut down」(關閉) 即可刪除專案。

後續步驟