利用 LLM 自动分类Gmail邮件汇总

作者:matrix 发布时间:2025-04-30 分类:零零星星

我邮箱订阅了很多博客或者 dev 相关的通知,偶尔看看的话还是有很多未读导致邮件堆积。但又不想退订 🙄 咋整
有邮件汇总服务就完美了,能自动分类和汇总, 标记已读和label。

zapier

zapier的自动化工作流配置可以达到效果,类似当年的 IFTTT, 现在他们都主要针对付费用户提供服务

我是等最后配置好了才发现使用的组件服务需要收费 😆 换其他

Google Apps script

https://script.google.com

Google的服务有很多,Apps script针对Google内部的各种服务集成的不错。

Apps script可以编写自定义脚本完成我需要的功能,语法是JavaScript。

新建项目

main.gs

// LLM配置
const OPENAI_API_URL = "https://openrouter.ai/api/v1/chat/completions"; // 使用 OpenRouter API URL
const OPENAI_API_KEY = "sk-XXXXXXXXXXXX"; //  API 密钥
const OPENAI_MODEL = "openai/gpt-4o-mini" //使用模型 准确率来说gpt-4o不错,deepseek v3也可以

// 汇总任务配置
const TASK_NAME = "daily.dev" //任务名称 作为汇总邮件标题的一部分
const QUERY = "informer@daily.dev is:unread" //邮件查询条件(Gmail搜索框中的查询文本)is:unread 表示查询未读邮件
const LABEL_NAME = "[script.google]AI汇总" //汇总的分类名
const MAX_EMAILS_PER_BATCH = 5; // 每次最多汇总邮件数 5
const MAX_EMAILS_BATCH = 10; // 本次任务最多处理邮件数 10
const TIME_ZONE = "GMT+8"


// 查询未读邮件并处理
function main() {
  try {
    let threads = GmailApp.search(QUERY); // 查询未读邮件

    if (threads.length === 0) {
      Logger.log("没有未读邮件。");
      return;
    }

    threads = threads.slice(0, MAX_EMAILS_BATCH);

    //限制汇总邮件数
    chunkArray(threads,MAX_EMAILS_PER_BATCH).forEach(limitedThreads => {

      // 2. 获取邮件内容
      let emailContents = [];

      limitedThreads.forEach(thread => {
        const messages = thread.getMessages();
        messages.forEach(message => {
          const subject = message.getSubject();
          const body = message.getPlainBody();
          const messageId = message.getId(); // 获取邮件的唯一 ID
          const threadId = message.getThread().getId(); // 获取邮件线程的 ID
          const emailLink = `https://mail.google.com/mail/u/0/#all/${threadId}`;

          emailContents.push(`<email><title>${subject}</title><content>${body}</content><link>${emailLink}</link></email>`);
        });
      });

      // 3. 调用 OpenRouter 的 AI 接口
      const aiResponse = callOpenAiAPI(emailContents.join("\n\n"));

      // 4. 发送汇总邮件
      let matches = aiResponse.match(/<content>([\s\S]+)<\/content>/);
      if(!matches || matches.length < 1){
        Logger.log("无内容: " + matches);
        return ;
      }
      sendSummaryEmail(matches[1]);

      // 5. 标记邮件为特定分类(标签)
      limitedThreads.forEach(thread => {
        // 获取或创建标签
        const labelName = LABEL_NAME; // 替换为你想要的标签名称
        let label = GmailApp.getUserLabelByName(labelName);
        if (!label) {
          label = GmailApp.createLabel(labelName); // 如果标签不存在,则创建
        }
        // 将标签应用到邮件线程
        label.addToThread(thread);

        thread.markRead();//标记已读
      });
    })

  } catch (error) {
    Logger.log("发生错误: " + error.message);
  }
}

function chunkArray(array, size) {
  const result = [];
  for (let i = 0; i < array.length; i += size) {
    result.push(array.slice(i, i + size));
  }
  return result;
}

// 调用 OpenRouter API
function callOpenAiAPI(content) {
  const prompts = `{邮件内容}是邮件提取工具生成的多封邮件的混合文本,你需要将其整理汇总为指定格式的HTML代码 参考{输出格式}。
 ## 规则
 - 提取每封邮件的所有关键信息和链接
 - 去除多余的广告或者软件下载链接等干扰
 - 链接 必定存在, 如果链接没有合适匹配,则使用邮件链接
 - 标题 必定存在,英文标题翻译为中文

{输出格式}:
\`\`\`
<content>
<p><a href="{链接}">1. {标题}</a></p>
<p><a href="{链接}">2. {标题}</a></p>
...
</content>
\`\`\`


{邮件内容}:
\`\`\`
${content}
\`\`\`
`;
  const payload = {
    model: OPENAI_MODEL, 
    prompt: prompts,
    // max_tokens: 500
  };

  console.log(prompts);

  const options = {
    method: "POST",
    contentType: "application/json",
    headers: {
      Authorization: `Bearer ${OPENAI_API_KEY}`
    },
    payload: JSON.stringify(payload)
  };

  const response = UrlFetchApp.fetch(OPENAI_API_URL, options);
  console.log(response.getContentText());
  const json = JSON.parse(response.getContentText());
  return json.choices[0].text.trim(); // 根据 api 的返回格式调整
}

function getActiveUserEmail() {
  var email = Session.getActiveUser().getEmail();
  Logger.log("Active User Email: " + email);
  return email;
}

// 发送汇总邮件
function sendSummaryEmail(summary) {
  const recipient = getActiveUserEmail();
  const today = new Date(); 
  const yesterdayDate = new Date(); 
  yesterdayDate.setDate(today.getDate() - 1); // 设置为前一天日期
  const formattedDate = Utilities.formatDate(yesterdayDate, TIME_ZONE, "yyyy-MM-dd"); // 格式化日期
  // 设置邮件主题,包含查询关键字和当前日期
  const subject = `[${TASK_NAME}]邮件汇总 - ${formattedDate}`;
  const body = `${summary}`;
  GmailApp.sendEmail(recipient,subject,body,{htmlBody: body});

}

说明:
我使用的 OpenRouter 服务,你完全可以更换为国内任何 ai 服务商

代码顶部配置项OPENAI_API_URL,OPENAI_API_KEY,OPENAI_MODEL 表示大模型服务商信息
QUERY 配置邮件搜索条件,也就是 Gmail 输入框的文本
LABEL_NAME 标记已读后需要移动到的分类名

测试运行

第一次运行会有 Google 应用的授权提示,允许即可。因为是自部署应用所以不会授权给外部。

图片5962-利用 LLM 自动分类Gmail邮件汇总

点击顶部的运行或者调试来测试,必须确保执行入口方法为 main

最后发送的汇总邮件样式很简单 可以自己调整。

定时执行

找到左侧菜单中「触发器」,添加每天执行的定时规则

配置完成,每天凌晨30分左右 Google 会自动发送汇总邮件,并且把已经处理的邮件标记已读。

参考:

https://developers.google.com/apps-script/reference/Gmail/gmail-app?hl=zh-cn#createdraftrecipient,-subject,-body