8
n8n 中文网amn8n.com

基于HubSpot上下文的AI邮件回复与Slack审批

高级

这是一个CRM, AI Summarization领域的自动化工作流,包含 19 个节点。主要使用 If, Code, Gmail, Slack, Filter 等节点。 基于HubSpot上下文的AI邮件回复与Slack审批

前置要求
  • Google 账号和 Gmail API 凭证
  • Slack Bot Token 或 Webhook URL
  • HubSpot API Key
  • 可能需要目标 API 的认证凭证
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "09423a3357ff64bdcc082268b9d577001317edbe377a3ccfb0b131ffb9554b30"
  },
  "nodes": [
    {
      "id": "6897a614-cd5a-4e76-99fb-7094b4692dd2",
      "name": "Google Gemini 聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        2352,
        464
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "1b011a66-c09a-4a6c-b666-89a5301f54cc",
      "name": "回复消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3184,
        240
      ],
      "webhookId": "597ba693-ad1e-4a19-9c41-8f8a82e7849f",
      "parameters": {
        "message": "={{ $('Draft Reply (AI Agent)').item.json.output }}",
        "options": {
          "appendAttribution": false
        },
        "emailType": "text",
        "messageId": "={{ $('Watch Gmail (New Inbound)').first().json.threadId }}",
        "operation": "reply"
      },
      "typeVersion": 2.1
    },
    {
      "id": "287d6e80-9e28-4188-87a8-c46def152c1e",
      "name": "监控 Gmail(新进邮件)",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        544,
        256
      ],
      "parameters": {
        "filters": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "4b25d17f-ce32-47d2-a27d-ca52c68a5e46",
      "name": "过滤器:允许的发件人",
      "type": "n8n-nodes-base.filter",
      "position": [
        752,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c0511cfb-d540-4b31-b665-f6038d6f8bbe",
              "operator": {
                "type": "string",
                "operation": "notContains"
              },
              "leftValue": "={{ $json.From }}",
              "rightValue": "n8n.io"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "61b5b40e-988e-4482-b729-0669e9081fb2",
      "name": "起草回复(AI 代理)",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2352,
        256
      ],
      "parameters": {
        "text": "=You are a helpful, concise customer support/sales assistant. Draft a ready-to-send email reply.\n\nDO NOT output JSON, arrays, or anything under CONTEXT. Only output the email.\n\n# INPUTS\n\nMy name (for signature): John Bolton\nFrom: {{$('Watch Gmail (New Inbound)').first().json.From}}\nSubject: {{$('Watch Gmail (New Inbound)').first().json.Subject}}\nCustomer message:\n{{$('Watch Gmail (New Inbound)').first().json.snippet}}\n\n# CONTEXT (do not quote or restate; summarize only if helpful)\nContact (HubSpot JSON):\n{{ JSON.stringify($('Find Contact by Email').first().json.properties || {}, null, 2) }}\n\nCompanies (JSON, may be empty):\n{{ JSON.stringify($json.companies || []) }}\n\nDeals (JSON, may be empty):\n{{ JSON.stringify($json.deals || []) }}\n\nTickets (JSON, may be empty):\n{{ JSON.stringify($json.tickets || []) }}\n\n# WHAT TO DO\n- Acknowledge the sender and the exact topic in the Subject/body.\n- Answer their request directly and succinctly.\n- Offer 1–2 clear next steps or a single CTA.\n- Personalize using safe context only:\n  - Use contact name/company if present.\n  - If deals exist, mention at most the 1–2 most relevant (name, stage, amount, close date). Ignore IDs/owner/pipeline/internal fields.\n  - If tickets exist, reference subject/status briefly if relevant.\n- If context is missing, write a generic but professional reply (do not invent facts).\n\n# TONE\nFriendly, professional, plain language. Short paragraphs or brief bullets.\n\n# OUTPUT FORMAT (no extra commentary, no subject, just the email body)\n- Greeting with the person’s name if available.\n- 2–5 sentences answering the question; bullets allowed for steps.\n- Optional one-line context (deal/ticket) if helpful.\n- One clear CTA.\n- Polite sign-off with a sender name placeholder.\n\n# CONSTRAINTS\n- Never expose IDs, raw JSON, or internal property names.\n- Keep under ~150 words unless necessary.\n- If anything is unclear, end with exactly one clarifying question.\n\nGenerate the reply now.\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "81616dff-2202-4e66-9c2f-7e93403bf909",
      "name": "通过邮箱查找联系人",
      "type": "n8n-nodes-base.hubspot",
      "position": [
        1040,
        256
      ],
      "parameters": {
        "operation": "search",
        "authentication": "oAuth2",
        "filterGroupsUi": {
          "filterGroupsValues": [
            {
              "filtersUi": {
                "filterValues": [
                  {
                    "value": "={{ String($json.From || '').match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/i)?.[0] || '' }}",
                    "propertyName": "email|string"
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "properties": [
            "email",
            "firstname",
            "lastname",
            "jobtitle",
            "company",
            "country",
            "state",
            "city",
            "hs_language",
            "phone",
            "mobilephone",
            "lifecyclestage",
            "hs_lead_status",
            "hubspot_owner_id",
            "hs_email_last_open_date",
            "hs_email_last_reply_date",
            "hs_latest_meeting_activity",
            "hs_sequences_is_enrolled",
            "hs_sequences_enrolled_count",
            "createdate",
            "hs_lastmodifieddate",
            "hs_timezone",
            "notes_last_contacted",
            "hs_object_id"
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6e5e8866-223c-4cdc-b201-7e804d47b01d",
      "name": "设置记录类型",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        256
      ],
      "parameters": {
        "jsCode": "const input = $input.first();\nlet records = Array.isArray(input?.json?.records)\n  ? input.json.records\n  : [\"deals\",\"companies\",\"tickets\"];\n\nreturn records.map(name => ({ json: { record: name } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "94281ea8-a451-42a9-9fb6-b90e4b5dc42a",
      "name": "列出联系人关联",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1456,
        256
      ],
      "parameters": {
        "url": "=https://api.hubapi.com/crm/v4/objects/contacts/{{ $('Find Contact by Email').item.json.id }}/associations/{{ $json.record }}",
        "options": {
          "response": {}
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "70d7c8e3-bbc5-4be2-bc65-c5c168bcba84",
      "name": "构建批量读取请求",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        256
      ],
      "parameters": {
        "jsCode": "// Build batch/read requests for only: deals, companies, tickets\n\nconst PROPS = {\n  deals: [\n    \"dealname\",\n    \"amount\",\n    \"dealstage\",\n    \"pipeline\",\n    \"closedate\",\n    \"hubspot_owner_id\",\n    \"hs_lastmodifieddate\",\n  ],\n  companies: [\n    \"name\",\n    \"domain\",\n    \"industry\",\n    \"numberofemployees\",\n    \"annualrevenue\",\n    \"website\",\n    \"phone\",\n    \"city\",\n    \"state\",\n    \"country\",\n    \"hubspot_owner_id\",\n    \"createdate\",\n    \"hs_lastmodifieddate\",\n  ],\n  tickets: [\n    \"hs_ticket_id\",\n    \"subject\",\n    \"content\",\n    \"hs_pipeline\",\n    \"hs_pipeline_stage\",\n    \"hs_ticket_priority\",\n    \"hs_lastmodifieddate\",\n    \"createdate\",\n    \"closed_date\",\n  ],\n};\n\n// If the upstream node emits these three in order, this helps infer the object when not provided\nconst ORDER = [\"deals\", \"companies\", \"tickets\"];\n\nfunction toBatchRead(item, idx) {\n  const object = item.json.object || item.json.record || ORDER[idx];\n\n  const results = Array.isArray(item.json.results) ? item.json.results : [];\n  const ids = results.map(r => String(r.toObjectId)).filter(Boolean);\n\n  return {\n    json: {\n      object,\n      url: `https://api.hubapi.com/crm/v3/objects/${object}/batch/read`,\n      method: \"POST\",\n      headers: { \"content-type\": \"application/json\" },\n      body: {\n        properties: PROPS[object] || [],\n        archived: false,\n        inputs: ids.map(id => ({ id })),\n      },\n      hasInputs: ids.length > 0,\n      count: ids.length,\n    },\n  };\n}\n\nreturn $input.all().map(toBatchRead);\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ee30292a-fce0-4e18-a422-3d7a58b82e4e",
      "name": "批量读取对象",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1888,
        256
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "body": "={{ $json.body }}",
        "method": "POST",
        "options": {
          "response": {}
        },
        "sendBody": true,
        "contentType": "raw",
        "authentication": "predefinedCredentialType",
        "rawContentType": "={{ $json.headers['content-type'] }}",
        "nodeCredentialType": "hubspotOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "e588dad3-5cdc-47f8-b180-d97d8e0bbb0a",
      "name": "为 LLM 标准化 CRM 上下文",
      "type": "n8n-nodes-base.code",
      "position": [
        2096,
        256
      ],
      "parameters": {
        "jsCode": "// n8n Code node (JavaScript)\n// Input: three items (HubSpot batch/read responses) for deals, companies, tickets (order unknown)\n// Output: a single consolidated item with cleaned, LLM-ready fields\n\nconst items = $input.all().map(i => i.json);\n\n// --- helpers ---\nconst isNonEmpty = v => v !== null && v !== undefined && v !== '';\nconst stripNulls = obj =>\n  Object.fromEntries(Object.entries(obj).filter(([, v]) => isNonEmpty(v)));\n\nfunction detectType(block) {\n  const first = block?.results?.[0]?.properties || {};\n  if ('dealname' in first || 'dealstage' in first) return 'deals';\n  if ('hs_ticket_id' in first || 'hs_pipeline' in first) return 'tickets';\n  if ('name' in first || 'industry' in first) return 'companies';\n  return 'unknown';\n}\n\nfunction mapDeal(p) {\n  return stripNulls({\n    id: p.hs_object_id || p.id,\n    name: p.dealname,\n    stage: p.dealstage,\n    amount: isNonEmpty(p.amount) ? Number(p.amount) : undefined,\n    pipeline: p.pipeline,\n    closeDate: p.closedate,\n    ownerId: p.hubspot_owner_id,\n    createdAt: p.createdate,\n    lastUpdatedAt: p.hs_lastmodifieddate,\n  });\n}\n\nfunction mapCompany(p) {\n  // Derive a simple location string when possible\n  const parts = [p.city, p.state, p.country].filter(isNonEmpty);\n  const hq = parts.length ? parts.join(', ') : undefined;\n\n  return stripNulls({\n    id: p.hs_object_id || p.id,\n    name: p.name,\n    domain: p.domain,\n    website: p.website,\n    phone: p.phone,\n    industry: p.industry,\n    employees: isNonEmpty(p.numberofemployees) ? Number(p.numberofemployees) : undefined,\n    annualRevenue: isNonEmpty(p.annualrevenue) ? Number(p.annualrevenue) : undefined,\n    headquarters: hq,\n    ownerId: p.hubspot_owner_id,\n    createdAt: p.createdate,\n    lastUpdatedAt: p.hs_lastmodifieddate,\n  });\n}\n\nfunction mapTicket(p) {\n  return stripNulls({\n    id: p.hs_ticket_id || p.hs_object_id || p.id,\n    subject: p.subject,\n    description: p.content,\n    pipelineId: p.hs_pipeline,\n    stageId: p.hs_pipeline_stage,\n    priority: p.hs_ticket_priority,\n    createdAt: p.createdate,\n    lastUpdatedAt: p.hs_lastmodifieddate,\n    closedDate: p.closed_date,\n  });\n}\n\n// --- collect ---\nconst out = { deals: [], companies: [], tickets: [] };\n\nfor (const block of items) {\n  const t = detectType(block);\n  const rows = Array.isArray(block.results) ? block.results : [];\n  if (t === 'deals') {\n    out.deals = rows.map(r => mapDeal(r.properties || {})).filter(o => Object.keys(o).length);\n  } else if (t === 'companies') {\n    out.companies = rows.map(r => mapCompany(r.properties || {})).filter(o => Object.keys(o).length);\n  } else if (t === 'tickets') {\n    out.tickets = rows.map(r => mapTicket(r.properties || {})).filter(o => Object.keys(o).length);\n  }\n}\n\n// Optional high-level summary for the LLM\nout.summary = {\n  dealCount: out.deals.length,\n  companyCount: out.companies.length,\n  ticketCount: out.tickets.length,\n};\n\n// Emit a single consolidated item\nreturn [{ json: out }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "04448b5d-ac9c-417b-9246-3853e94303f0",
      "name": "等待响应 - 批准自动回复",
      "type": "n8n-nodes-base.slack",
      "position": [
        2768,
        256
      ],
      "webhookId": "ffb81691-54b1-43da-8b71-a4c45362901b",
      "parameters": {
        "select": "channel",
        "message": "={{ $('Watch Gmail (New Inbound)').first().json.From }} sent you the following message:\n\n{{ $('Watch Gmail (New Inbound)').first().json.snippet }}\n\n\nHere is an auto-generated reply (press \"Approve\" to send it):\n\n{{ $json.output }}",
        "options": {
          "limitWaitTime": {
            "values": {
              "resumeUnit": "days"
            }
          }
        },
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09H7HTHRMG",
          "cachedResultName": "all-n8n-slack-test"
        },
        "operation": "sendAndWait",
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "464de728-acf5-4664-9469-f4f660d29ec6",
      "name": "是否已批准?",
      "type": "n8n-nodes-base.if",
      "position": [
        2976,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "9313939d-ed39-4a91-b0c6-18512a9c4676",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.approved }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0eb866f6-f229-48da-bf53-a19b0439c7a9",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 1280,
        "height": 384,
        "content": "## 获取 CRM 信息"
      },
      "typeVersion": 1
    },
    {
      "id": "bb2fb3e1-dbcc-468f-9d0b-65e379aad792",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 384,
        "content": "## 获取接收的邮件"
      },
      "typeVersion": 1
    },
    {
      "id": "53796f4b-13c6-4af0-ad1b-199ac49ac096",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 528,
        "content": "## 撰写回复草稿"
      },
      "typeVersion": 1
    },
    {
      "id": "39b501df-7fc7-470f-8aa4-6dacc05eb255",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2688,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 704,
        "height": 384,
        "content": "## 批准并回复"
      },
      "typeVersion": 1
    },
    {
      "id": "86267d6e-aa6e-4521-a0fb-c823724b7e7d",
      "name": "工作流概览",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 468,
        "height": 624,
        "content": "## 带 HubSpot 上下文的 AI 邮件回复 + Slack 批准"
      },
      "typeVersion": 1
    },
    {
      "id": "f7cb3860-8006-401b-9c4a-2fbb3ca0aa68",
      "name": "便签6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3184,
        432
      ],
      "parameters": {
        "color": 7,
        "width": 376,
        "height": 232,
        "content": "### 💡 自定义此工作流"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {
    "Watch Gmail (New Inbound)": [
      {
        "To": "\"miha.ambroz@n8n.io\" <miha.ambroz@n8n.io>",
        "id": "199823d41f5aa56f",
        "From": "Miha Ambroz <miha.ambroz@pm.me>",
        "labels": [
          {
            "id": "INBOX",
            "name": "INBOX"
          },
          {
            "id": "IMPORTANT",
            "name": "IMPORTANT"
          },
          {
            "id": "CATEGORY_PERSONAL",
            "name": "CATEGORY_PERSONAL"
          },
          {
            "id": "UNREAD",
            "name": "UNREAD"
          }
        ],
        "Subject": "Hey",
        "payload": {
          "mimeType": "text/plain"
        },
        "snippet": "I forgot what I last ordered. Can you help me? Sent from Proton Mail Android",
        "threadId": "199823d41f5aa56f",
        "historyId": "585196",
        "internalDate": "1758826671000",
        "sizeEstimate": 4059
      }
    ]
  },
  "connections": {
    "If Approved?": {
      "main": [
        [
          {
            "node": "Reply to a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Record Types": {
      "main": [
        [
          {
            "node": "List Contact Associations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Read Objects": {
      "main": [
        [
          {
            "node": "Normalize CRM Context for LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Contact by Email": {
      "main": [
        [
          {
            "node": "Set Record Types",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Reply (AI Agent)": {
      "main": [
        [
          {
            "node": "Wait for Response - Approve Auto-Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Allowed Sender": {
      "main": [
        [
          {
            "node": "Find Contact by Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Reply (AI Agent)",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Build Batch Read Requests": {
      "main": [
        [
          {
            "node": "Batch Read Objects",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Contact Associations": {
      "main": [
        [
          {
            "node": "Build Batch Read Requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Watch Gmail (New Inbound)": {
      "main": [
        [
          {
            "node": "Filter: Allowed Sender",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize CRM Context for LLM": {
      "main": [
        [
          {
            "node": "Draft Reply (AI Agent)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Response - Approve Auto-Reply": {
      "main": [
        [
          {
            "node": "If Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 客户关系管理, AI 摘要总结

需要付费吗?

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

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

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

作者
Miha

Miha

@miha

Template Engineer @ n8n

外部链接
在 n8n.io 查看

分享此工作流