8
n8n 中文网amn8n.com

邮件→公开邮件

中级

这是一个Personal Productivity, Multimodal AI领域的自动化工作流,包含 13 个节点。主要使用 Code, Gmail, SplitInBatches, ScheduleTrigger, OpenAi 等节点。 使用GPT-4.1-mini从Gmail创建每日新闻简报摘要

前置要求
  • Google 账号和 Gmail API 凭证
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "4LGHvpa2PjTNwGc1",
  "meta": {
    "instanceId": "93438a4980e64a583ea2325b4dc5eae4c4c531c30461a48c79a4b262b5c53893",
    "templateCredsSetupCompleted": true
  },
  "name": "email → email public",
  "tags": [
    {
      "id": "TIMIlK5hNxVuDAlg",
      "name": "public",
      "createdAt": "2025-08-11T13:46:00.810Z",
      "updatedAt": "2025-08-11T13:46:00.810Z"
    }
  ],
  "nodes": [
    {
      "id": "8636c298-0e5a-494d-9bd1-beace2be380c",
      "name": "获取多条消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        368,
        64
      ],
      "webhookId": "e1b897aa-1e4d-4880-8be0-df0c5f5d577c",
      "parameters": {
        "filters": {
          "q": "=(from:____@____.com) OR (from:____@____.com) OR (from:____@____.com -\"____\") after:{{ $now.minus({ days: 1 }).toFormat('yyyy/MM/dd') }}"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "sqe7HFlBnGaSLwH9",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "d77aad78-85fa-4dd1-9227-6636b023dc04",
      "name": "遍历项目",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        592,
        64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "3f688cd5-ac4b-4965-a9d4-2220cee11440",
      "name": "获取消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        816,
        64
      ],
      "webhookId": "309187bc-88e5-4174-8aea-e3be7dc0b20e",
      "parameters": {
        "simple": false,
        "options": {},
        "messageId": "={{ $json.id }}",
        "operation": "get"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "sqe7HFlBnGaSLwH9",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "104254b9-51ea-4655-9e49-38dd3b200929",
      "name": "获取消息数据",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        64
      ],
      "parameters": {
        "jsCode": "function extractHtml(payload) {\n  if (!payload) return '';\n  if (payload.body && payload.body.data) {\n    return Buffer.from(payload.body.data, 'base64').toString('utf-8');\n  }\n  function findHtmlPart(parts) {\n    for (const part of parts) {\n      if (part.mimeType === 'text/html' && part.body && part.body.data) {\n        return Buffer.from(part.body.data, 'base64').toString('utf-8');\n      }\n      if (part.parts) {\n        const result = findHtmlPart(part.parts);\n        if (result) return result;\n      }\n    }\n    return '';\n  }\n  if (payload.parts) {\n    const html = findHtmlPart(payload.parts);\n    if (html) return html;\n  }\n  return '';\n}\n\nlet html = $json.html || '';\nif (!html && $json.payload) {\n  html = extractHtml($json.payload);\n}\nif (!html && $json.text) {\n  html = $json.text;\n}\n\n// Clean 'from': keep only the sender's name\nlet from = $json.from?.text || $json.from || '';\nconst match = from.match(/^\\\"?([^\\\"<]+)\\\"?\\s*<[^>]+>$/);\nif (match) {\n  from = match[1].trim();\n}\n\n// Convert date to DD.MM.YYYY format\nlet date = $json.date || '';\nlet formattedDate = date;\nif (date) {\n  const d = new Date(date);\n  const day = String(d.getDate()).padStart(2, '0');\n  const month = String(d.getMonth() + 1).padStart(2, '0');\n  const year = d.getFullYear();\n  formattedDate = `${day}.${month}.${year}`;\n}\n\nconst subject = $json.subject || '';\n\nreturn [{\n  html,\n  subject,\n  from,\n  date: formattedDate\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "31e5402b-7146-4955-ae7b-ee4dd3ecc583",
      "name": "合并",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        -128
      ],
      "parameters": {
        "jsCode": "/**\n * This code is intended for use in the \"Code\" node in n8n.\n * It merges the 'topics' arrays from several incoming items into a single array.\n *\n * Incoming data (items) have the following structure:\n * [\n * { json: { message: { content: { topics: [...] } } } },\n * { json: { message: { content: { topics: [...] } } } },\n * ...\n * ]\n */\n\n// We use the flatMap method, which is a combination of map and flat.\n// It iterates over each element (item) in the incoming array (items),\n// extracts the 'topics' array and immediately flattens all collected arrays into one.\nconst allTopics = items.flatMap(item => {\n  // We use optional chaining (?.) for safe access to the nested property.\n  // This prevents an error if 'message' or 'content' is missing in any element.\n  // If the path is not found, return an empty array [] so flatMap can work correctly.\n  return item.json?.message?.content?.topics || [];\n});\n\n// The \"Code\" node must return an array of objects.\n// We return one object containing the 'json' property with our merged 'topics' array.\n// This result will be available on the node's output for further use.\nreturn [{\n  json: {\n    topics: allTopics\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "df46e906-1a21-47c3-9168-900ee58c3730",
      "name": "清理",
      "type": "n8n-nodes-base.code",
      "position": [
        1264,
        64
      ],
      "parameters": {
        "jsCode": "/**\n * This code is intended for use in the \"Code\" node in n8n.\n * Its task is to prepare data from the Gmail node for the next node (LLM).\n * It does not parse HTML; it only passes it along with other fields.\n *\n * Incoming data (items) — this is the result of the Gmail node.\n * [\n * { json: { html: \"...\", subject: \"...\", from: \"...\", date: \"DD.MM.YYYY\" } }\n * ]\n *\n * The code converts the date from \"DD.MM.YYYY\" to \"MM.DD\" and passes\n * all the necessary fields (html, subject, from, date) to the next step.\n */\n\n// Use .map() to transform each incoming item.\nconst results = items.map(item => {\n  // Extract the required fields from the incoming JSON.\n  const { html, subject, from, date: originalDate } = item.json;\n\n  // Check for required fields.\n  if (!html || !originalDate) {\n    // If there is no data, return null to filter this item later.\n    return null;\n  }\n\n  // 1. Split the date string into components (day, month, year).\n  const [day, month] = originalDate.split('.').slice(0, 2);\n\n  // 2. Form a new string in the \"MM.DD\" format.\n  const newDate = `${month}.${day}`;\n\n  // 3. Return a new object in the structure expected by\n  // the next node (LLM).\n  return {\n    json: {\n      html,\n      subject,\n      from,\n      date: newDate\n    }\n  };\n});\n\n// Filter out empty results and return the array for the next node.\nreturn results.filter(item => item !== null);\n"
      },
      "typeVersion": 2
    },
    {
      "id": "4bd967a6-cd5c-4225-a785-fda4f227979a",
      "name": "创建模板",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        -128
      ],
      "parameters": {
        "jsCode": "/**\n * This code is intended for use in the \"Code\" node in n8n.\n * It takes an array of topics and creates a single,\n * well-formatted HTML email for sending.\n *\n * This node replaces the nodes \"Formatting\", \"Split\", and \"Sanitizer\".\n */\n\n// Get the array of topics from the previous node.\nconst topics = $json.topics;\n\n// Check if there are topics to process.\nif (!Array.isArray(topics) || topics.length === 0) {\n  return []; // If there are no topics, stop the workflow.\n}\n\n// --- Create HTML markup for each news item ---\nconst topicsHtml = topics.map((topic, index) => {\n  // Escape basic HTML characters in the data to avoid breaking the markup.\n  const escapeHtml = (unsafe) => {\n    if (!unsafe) return '';\n    return unsafe\n         .replace(/&/g, \"&amp;\")\n         .replace(/</g, \"&lt;\")\n         .replace(/>/g, \"&gt;\")\n         .replace(/\\\"/g, \"&quot;\")\n         .replace(/'/g, \"&#039;\");\n  };\n\n  const title = escapeHtml(topic.title);\n  const descr = escapeHtml(topic.descr).replace(/\\n/g, '<br>'); // Preserve line breaks in the description\n  const subject = escapeHtml(topic.subject);\n  const from = escapeHtml(topic.from);\n  const date = escapeHtml(topic.date);\n\n  // Build a nice HTML block for a single news item.\n  return `\n    <div style=\"margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid #eeeeee;\">\n      <h3 style=\"margin: 0 0 8px 0; font-size: 18px; color: #1a1a1a;\">\n        ${index + 1}. ${title}\n      </h3>\n      <p style=\"margin: 0 0 12px 0; font-size: 16px; color: #333333; line-height: 1.5;\">\n        ${descr}\n      </p>\n      <p style=\"margin: 0; font-size: 14px; color: #777777; font-style: italic;\">\n        ${subject}<br>\n        → ${from} - ${date}\n      </p>\n    </div>\n  `;\n}).join(''); // Join all HTML blocks into a single string.\n\n// --- Wrap everything in a full HTML document with styles ---\nconst finalHtml = `\n  <!DOCTYPE html>\n  <html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Ваш новостной дайджест</title>\n  </head>\n  <body style=\"margin: 0; padding: 0; background-color: #f7f7f7; font-family: Arial, sans-serif;\">\n    <table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">\n      <tr>\n        <td align=\"center\" style=\"padding: 20px;\">\n          <table width=\"600\" border=\"0\" cellpadding=\"20\" cellspacing=\"0\" style=\"background-color: #ffffff; border-radius: 8px;\">\n            <tr>\n              <td>\n                <h1 style=\"text-align: center; color: #1a1a1a;\">Новостной дайджест</h1>\n                ${topicsHtml}\n              </td>\n            </tr>\n          </table>\n        </td>\n      </tr>\n    </table>\n  </body>\n  </html>\n`;\n\n// Return the result. In the \"Send Email\" node use {{ $json.htmlBody }}\nreturn [{ json: { htmlBody: finalHtml } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "57cc3b99-7a91-492f-8d5c-cbf0da079d16",
      "name": "发送消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1264,
        -128
      ],
      "webhookId": "74e87896-9cca-4dc0-98bb-7e8f712c5d01",
      "parameters": {
        "sendTo": "your-email@example.com",
        "message": "={{ $json.htmlBody }}",
        "options": {},
        "subject": "Your-subject"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "sqe7HFlBnGaSLwH9",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "363983e4-7be4-4319-8fdb-dd8ff39200e5",
      "name": "计划触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        144,
        64
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 16
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "06729a53-d537-4a80-8aed-b1032b858390",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        -208
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 240,
        "content": "## 试试看!"
      },
      "typeVersion": 1
    },
    {
      "id": "00d59259-5d92-458d-9b8e-bc7bf0f58979",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 1152,
        "height": 256,
        "content": "## 遍历每条消息"
      },
      "typeVersion": 1
    },
    {
      "id": "351eb40c-ba0a-4ea6-9f14-d7eabbf956e7",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 192,
        "content": "## 清理文本并形成最终消息"
      },
      "typeVersion": 1
    },
    {
      "id": "bddb5d4d-629a-4bfd-bf26-b60451949144",
      "name": "向模型发送消息",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1488,
        144
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a specialist in analyzing email newsletters. Create a brief summary of the email in JSON format:\n\n`{ \"topics\": [ { \"title\": ..., \"descr\": ..., \"subject\": ..., \"from\": ..., \"date\": ... } ] }`\n\nFor each news item within a block, create a separate topic with a brief one-sentence description, even if they are listed or under a single headline. Do not combine them into one topic.\n\nHowever, if the email is from ____, combine each block (for example, \"____\") by listing the topics in the description.\n\nIf the email is from ____, ignore the sections \"____\" and \"____\".\n\nAll values in your JSON must be in ____ language, except for the subject. The subject—{{ $json.subject }}—must remain untranslated.\n\nHere is the subject: {{ $json.subject }}\nHere is the from: {{ $json.from }}\nHere is the date: {{ $json.date }}\n\nHere is the email:\n{{ $json.html }}"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "id": "UZtYaio4OAcJGlV9",
          "name": "OpenAi account"
        }
      },
      "typeVersion": 1.8
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "53fdca83-a1b5-4866-bed8-0b3322de6bc5",
  "connections": {
    "Clean": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Create template",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get a message": {
      "main": [
        [
          {
            "node": "Get message data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create template": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get message data": {
      "main": [
        [
          {
            "node": "Clean",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get many messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many messages": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 个人效率, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量13
分类2
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流