邮件→公开邮件
中级
这是一个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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\\\"/g, \""\")\n .replace(/'/g, \"'\");\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)可能需要您自行付费。
相关工作流推荐
邮件 → tg 公开
按需邮件摘要:从Gmail到Telegram(使用GPT-4.1-mini)
Code
Gmail
Telegram
+4
16 节点Vlad Arbatov
个人效率
每日简报 - 任务与事件
通过 Gmail 使用 Todoist、Google 日历和 GPT-4o 的自动化每日简报
Code
Gmail
Merge
+5
18 节点AK Pasnoor
个人效率
每日安全新闻
每日技术与网络安全简报:RSS、OpenAI GPT-4o 和 Gmail
If
Code
Gmail
+7
19 节点Calistus Christian
个人效率
每日WhatsApp摘要与群组级别控制
WhatsApp群组摘要工作流
If
Set
Code
+13
39 节点Luís Philipe Trindade
个人效率
基于OpenAI助手的Gmail自动回复草稿生成
基于OpenAI助手的Gmail自动回复草稿生成
Set
Code
Gmail
+7
23 节点Hichul
工单管理
基于 YouTube 视频的自主博客发布
使用 ChatGPT、Sheets、Apify、Pexels 和 WordPress 从 YouTube 视频自主发布博客
If
Set
Code
+18
80 节点Oriol Seguí
内容创作