8
n8n 中文网amn8n.com

邮件 → tg 公开

高级

这是一个Personal Productivity, Multimodal AI领域的自动化工作流,包含 16 个节点。主要使用 Code, Gmail, Telegram, SplitInBatches, TelegramTrigger 等节点。 按需邮件摘要:从Gmail到Telegram(使用GPT-4.1-mini)

前置要求
  • Google 账号和 Gmail API 凭证
  • Telegram Bot Token
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "nCr0DSvP0NQHbFEn",
  "meta": {
    "instanceId": "93438a4980e64a583ea2325b4dc5eae4c4c531c30461a48c79a4b262b5c53893",
    "templateCredsSetupCompleted": true
  },
  "name": "邮件 → tg 公开",
  "tags": [
    {
      "id": "TIMIlK5hNxVuDAlg",
      "name": "public",
      "createdAt": "2025-08-11T13:46:00.810Z",
      "updatedAt": "2025-08-11T13:46:00.810Z"
    }
  ],
  "nodes": [
    {
      "id": "54461eef-b4f1-4e29-aa87-674842f889e4",
      "name": "获取多条消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        384,
        64
      ],
      "webhookId": "22795477-c7f5-4c56-90aa-dde033c00e5f",
      "parameters": {
        "filters": {
          "q": "=(from:____@____.com) OR (from:____@____.com) OR (from:____@____.com -\"____\") after:{{ $json.dateString }}"
        },
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "sqe7HFlBnGaSLwH9",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "2673b554-b1b8-422e-8075-1cedbd5d6235",
      "name": "Telegram 触发器",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -64,
        64
      ],
      "webhookId": "82fb11e5-394d-4a57-a1ef-5eae17c4c1bd",
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {
          "chatIds": "your_tg_id"
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "ki2yZMrnSvQn6aN0",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "cff69481-a8da-4ea1-b78a-3e6d94b54493",
      "name": "遍历项目",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        608,
        64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "37f59023-026e-44f5-b2b2-f0e37fe8c6d8",
      "name": "获取一条消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        832,
        64
      ],
      "webhookId": "4a621c46-2308-40f2-b56e-c6ec6ddf950d",
      "parameters": {
        "simple": false,
        "options": {},
        "messageId": "={{ $json.id }}",
        "operation": "get"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "sqe7HFlBnGaSLwH9",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "16d90be1-d6fb-487e-a836-643021da12e1",
      "name": "获取天数",
      "type": "n8n-nodes-base.code",
      "position": [
        160,
        64
      ],
      "parameters": {
        "jsCode": "// input: daysAgo (number of days)\nconst daysAgo = parseInt($json[\"message\"][\"text\"], 10);\nconst date = new Date();\ndate.setDate(date.getDate() - daysAgo);\nconst yyyy = date.getFullYear();\nconst mm = String(date.getMonth() + 1).padStart(2, '0');\nconst dd = String(date.getDate()).padStart(2, '0');\nreturn [{ dateString: `${yyyy}/${mm}/${dd}` }];"
      },
      "typeVersion": 2
    },
    {
      "id": "79126930-ced7-40e2-947b-466e9b1417fa",
      "name": "获取消息数据",
      "type": "n8n-nodes-base.code",
      "position": [
        1056,
        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": "7a00feb1-4fb2-43a3-8ea3-6c023e138a4c",
      "name": "合并",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        -128
      ],
      "parameters": {
        "jsCode": "/**\n * This code is intended for use in the \"Code\" node in n8n.\n * It merges the 'topics' arrays from multiple 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 item (item) in the incoming array (items),\n// extracts the 'topics' array and immediately flattens all 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 of the items.\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 at the output of the node for further use.\nreturn [{\n  json: {\n    topics: allTopics\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "222dd34e-19b9-46aa-98ba-8f014457fbd4",
      "name": "创建 TG 消息",
      "type": "n8n-nodes-base.code",
      "position": [
        1056,
        -128
      ],
      "parameters": {
        "jsCode": "const topics = $json.topics;\nconst list = topics.map((t, idx) =>\n  `${idx + 1}. *${t.title}*\\n\\n${t.descr}\\n\\n${t.subject}\\n→ ${t.from} - ${t.date}`\n).join('\\n\\n');\n\nreturn [{ json: { message: `${list}` } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "de2c745d-e575-4670-ac3a-fbdd6e5ba6a2",
      "name": "清理",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        -128
      ],
      "parameters": {
        "jsCode": "/**\n * This code is intended for use in the \"Code\" node in n8n.\n * It prepares text for sending to Telegram in 'HTML' mode.\n * 1. Fixes \"broken\" formatting (*...* and _..._) by adding a closing symbol if their count is odd.\n * 2. Converts *text* to <b>text</b> and _text_ to <i>text</i>, even if the text spans multiple lines.\n * 3. Escapes basic HTML characters (<, >, &) for safety.\n *\n * IMPORTANT: In the Telegram node, Parse Mode must be set to HTML.\n *\n * The code processes EACH incoming item in the array.\n */\n\nconst correctedItems = items.map(item => {\n  // FIXED: Use the 'text' key that comes from the \"Split\" node.\n  let text = item.json.text;\n\n  if (!text) {\n    return item;\n  }\n\n  // --- STEP 1: Fix unbalanced formatting characters ---\n  const fixUnbalanced = (str, char) => {\n    // Use new RegExp to create a dynamic regular expression\n    const count = (str.match(new RegExp(`\\\\${char}`, 'g')) || []).length;\n    if (count % 2 !== 0) {\n      return str + char;\n    }\n    return str;\n  };\n\n  text = fixUnbalanced(text, '*');\n  text = fixUnbalanced(text, '_');\n\n  // --- STEP 2: Convert to safe HTML ---\n  // First, escape basic characters in the entire text so they don't conflict with tags.\n  let safeText = text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;');\n\n  // Now replace Markdown with HTML tags.\n  // Use the 's' (dotAll) flag so '.' also matches newline characters.\n  // This is important for your multi-line italics.\n  safeText = safeText\n    .replace(/\\*(.*?)\\*/gs, '<b>$1</b>')\n    .replace(/_(.*?)_/gs, '<i>$1</i>');\n\n\n  // Update the 'text' field in the current item.\n  // In the Telegram node, use {{ $json.text }}\n  item.json.text = safeText;\n\n  // Return the modified item.\n  return item;\n});\n\n// Return the full array of processed items.\nreturn correctedItems;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "57984a3f-ccb0-4e14-bac8-4d83ad89bb44",
      "name": "拆分",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        -128
      ],
      "parameters": {
        "jsCode": "const CHUNK = 3500;\nconst txt = $json.message;\nconst parts = txt.match(/[\\s\\S]{1,3500}/g) || [];\nreturn parts.map(p => ({ json: { text: p } }));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f1b7cbb8-7ae7-4699-97c2-354a0b82684b",
      "name": "清洁",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        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) 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 format \"MM.DD\".\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": "a9eb6447-cc67-4364-9994-bcdb37824980",
      "name": "发送消息",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1856,
        -128
      ],
      "webhookId": "e96216d9-8481-459f-a004-1572609f86d4",
      "parameters": {
        "text": "={{ $json.text }}\n",
        "chatId": "your_tg_id",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false,
          "disable_web_page_preview": true
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "ki2yZMrnSvQn6aN0",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "452b5f0c-db00-4cfe-9229-c61162476228",
      "name": "向模型发送消息",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1504,
        136
      ],
      "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
    },
    {
      "id": "fc2f7ecd-5a40-4f15-8a4f-055280b762e3",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -224
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 240,
        "content": "## 试试这个!"
      },
      "typeVersion": 1
    },
    {
      "id": "e9af2dfe-5a7b-4a55-8d78-39d82048a7b4",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        592,
        16
      ],
      "parameters": {
        "color": 7,
        "width": 1168,
        "height": 272,
        "content": "## 遍历每条消息"
      },
      "typeVersion": 1
    },
    {
      "id": "27bbab62-1efb-4384-9d22-829a6515e855",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 192,
        "content": "## 清理文本并形成最终消息"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "56e514d0-6916-476f-992d-246d29f02f11",
  "connections": {
    "Clean": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Create TG message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split": {
      "main": [
        [
          {
            "node": "Sanitize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get days": {
      "main": [
        [
          {
            "node": "Get many messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get a message": {
      "main": [
        [
          {
            "node": "Get message data",
            "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
          }
        ]
      ]
    },
    "Telegram Trigger": {
      "main": [
        [
          {
            "node": "Get days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create TG message": {
      "main": [
        [
          {
            "node": "Split",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get many messages": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

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

需要付费吗?

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

工作流信息
难度等级
高级
节点数量16
分类2
节点类型7
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流