用 n8n 自动分诊客服工单:OpenAI 分类 + HubSpot enrichment + Linear/Slack 升级
Webhook 接收工单 → 标准化与查重 → HubSpot 补全客户信息 → GPT-4o-mini 分类并起草回复 → 按紧急程度路由到 Linear 升级、人工复核或 Gmail 草稿 → Google Sheets 审计日志
- 给谁
- 有 n8n 实例的支持运营团队,需同时管理 Slack、Google Workspace、HubSpot、Linear/OpenAI 凭证
- 你会得到
- 自动完成工单验证、去重、客户信息补全、AI 分类与回复草稿、紧急升级路由,并记录审计日志
- 你需要
- n8n · slack · google-sheets · gmail
硬约束
前置条件:n8n 实例(自托管或云);Slack OAuth2、Google Sheets OAuth2、Gmail OAuth2 凭证;HubSpot private app token、OpenAI API key、Linear API key。需创建包含 dedupeKey 列的 Google Sheets「Support Audit Log」工作表。将模板导入 n8n 后,替换所有 REPLACE_WITH_* 占位值,配置 webhook 端点 /ai-support-triage,测试单个 payload 后再激活。
他们做了什么
模板作者 flowforgeai 在 n8n 上构建了一个 24 节点的生产级支持工单分诊工作流。该工作流通过 webhook 接收来自 Zendesk、Intercom、Freshdesk 或自定义应用的原始工单,使用 Code 节点标准化多种字段格式,分配 VIP 域名识别和 SLA 时限,通过 Google Sheets 查重,调用 HubSpot API 补全客户信息,再使用 GPT-4o-mini 生成分类、优先级、情绪、风险说明和回复草稿,最后根据规则将紧急工单升级到 Linear 并告警 Slack,低置信度工单送入人工复核队列,常规工单生成 Gmail 草稿,所有决策记录到 Google Sheets 审计表。
效果如何
该模板已在 n8n.io 发布为官方模板(n8n_id=19097),包含完整的节点连线、错误处理分支(Continue On Fail)和粘性注释文档。作者未提供具体运行数据或 ROI 报告;效果声明基于模板设计目标,未经本站实测验证。
值得知道
1. 凭证需求较多(6 类服务),且需正确配置 OAuth 和 private app 权限。2. VIP 域名和 SLA 阈值在 Code 节点中硬编码为占位值,必须替换。3. Linear 节点可替换为 Jira/Asana/Zendesk,但需自行修改 GraphQL mutation 为对应 API。4. HubSpot 和 OpenAI 节点启用 Continue On Fail,供应商临时故障时会自动落入 human_review 路径而非中断工作流。5. OpenAI 返回的 JSON 若被 markdown 代码块包裹,Parse 节点会清理后解析。6. 本站未执行实际运行验证(reproduction=not_tested),搭建后务必先用测试 payload 验证。
n8n 工作流模板
结构已检查 · 未实际运行下载后在 n8n 选择“Import from File”,或复制 JSON 后粘贴到编辑器。先配置自己的账号,再用测试数据运行;模板默认未启用。
配置与测试清单
需要绑定的凭证
- 将 __UKH_BEARER_13__ 替换为实际密钥/令牌(原路径 /nodes/13/parameters/headerParameters/parameters/0/value;literal auth header)。
- 将 __UKH_BEARER_14__ 替换为实际密钥/令牌(原路径 /nodes/14/parameters/headerParameters/parameters/0/value;literal auth header)。
- 将 __UKH_BEARER_17__ 替换为实际密钥/令牌(原路径 /nodes/17/parameters/headerParameters/parameters/0/value;literal auth header)。
需要替换的配置
- 源模板来自 n8n 公开库 #19097(约 24 个节点)。导入后先保持未启用,核对触发器、表达式与外部 API 端点。
- Sticky Note 中的说明仅供参考;按自己的账号、表结构或频道 ID 替换占位配置。
测试输入与预期结果
- 在测试环境手动执行一次主路径,确认每个需凭证的节点已连接且无鉴权错误。
- 用一条合成/脱敏样例数据跑通,核对输出字段与下游写入目标,再考虑生产启用。
模板处理说明
- 已移除原作者凭证绑定、webhookId 与执行数据,工作流设为未启用;节点、连线与原文提示词保持原样。未在 n8n 中实际运行。
- 未改业务逻辑;运行前请自行核对密钥、配额与隐私合规。 另将 3 处字面量密钥替换为 __UKH_…__ 占位符。
模板获取日期: 2026-10-25
步骤
配置凭证与基础环境
在 n8n 中创建或连接以下凭证:Slack OAuth2(#support-triage、#support-escalations、#support-ops 频道权限)、Google Sheets OAuth2(可编辑审计表格)、Gmail OAuth2(如需创建草稿)。收集 HubSpot private app token(CRM 搜索权限)、OpenAI API key、Linear API key(或准备替换为 Jira/Asana 的 HTTP 节点参数)。在 Google Sheets 新建「Support Audit Log」表,确保包含 dedupeKey 列。复制模板 webhook 生产 URL,配置你的帮助台或表单工具向 /ai-support-triage POST 事件。
接收工单 Webhook
模板使用 Webhook 节点监听 POST 请求,路径为 ai-support-triage,responseMode 设为 responseNode(由后续 Respond to Webhook 节点返回)。你的帮助台或表单需发送包含 ticketId、email、name、company、subject、message、ticketUrl、source 等字段的 JSON。
标准化工单并分配 SLA
Code 节点「Normalize Ticket + SLA Profile」执行 JS 代码,从多种常见嵌套格式(requester.email、customer.email、description、body 等)提取统一字段:requesterEmail、requesterName、subject、message、ticketId、ticketUrl、sourceSystem、companyName。生成 dedupeKey(邮箱+标准化主题/内容),检查 VIP 域名(默认占位 REPLACE_WITH_VIP_DOMAIN_1.com),分配 defaultSlaMinutes(VIP 30 分钟/普通 240 分钟)。标记缺失必填项(email、subject、message)。
运行提示词 · 来源原文。n8n Code 节点「Normalize Ticket + SLA Profile」的 JS Code 字段
const rawTicket = $input.first().json; const firstNonEmpty = (...values) => { for (const value of values) { if (value !== undefined && value !== null && String(value).trim() !== '') { return String(value).trim(); } } return ''; }; const requesterEmail = firstNonEmpty( rawTicket.email, rawTicket.requester?.email, rawTicket.customer?.email, rawTicket.contact?.email, rawTicket.from ).toLowerCase(); const requesterName = firstNonEmpty(rawTicket.name, rawTicket.requester?.name, rawTicket.customer?.name, rawTicket.contact?.name, requesterEmail.split('@')[0]); const subject = firstNonEmpty(rawTicket.subject, rawTicket.title, rawTicket.summary, 'No subject provided'); const message = firstNonEmpty(rawTicket.message, rawTicket.description, rawTicket.body, rawTicket.text); const ticketId = firstNonEmpty(rawTicket.ticketId, rawTicket.id, rawTicket.external_id, rawTicket.conversationId, `incoming-${Date.now()}`); const ticketUrl = firstNonEmpty(rawTicket.ticketUrl, rawTicket.url, rawTicket.html_url); const sourceSystem = firstNonEmpty(rawTicket.source, rawTicket.helpdesk, rawTicket.system, 'webhook'); const companyName = firstNonEmpty(rawTicket.company, rawTicket.organization?.name, rawTicket.account?.name); const domain = requesterEmail.includes('@') ? requesterEmail.split('@').pop() : ''; const normalizedSubject = subject.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 90); const normalizedMessage = message.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().slice(0, 160); const dedupeKey = [requesterEmail || 'unknown-email', normalizedSubject || normalizedMessage || ticketId].join('::'); const vipDomains = ['REPLACE_WITH_VIP_DOMAIN_1.com', 'REPLACE_WITH_VIP_DOMAIN_2.com']; const vipDomain = vipDomains.includes(domain); const missingFields = []; if (!requesterEmail) missingFields.push('requester email'); if (!subject) missingFields.push('subject'); if (!message) missingFields.push('message/body'); return [{ json: { receivedAt: new Date().toISOString(), sourceSystem, ticketId, ticketUrl, requesterEmail, requesterName, companyName, domain, subject, message, messagePreview: message.slice(0, 500), dedupeKey, vipDomain, defaultSlaMinutes: vipDomain ? 30 : 240, missingFields, hasMissingRequired: missingFields.length > 0, rawTicket, }, }];验证必填字段并处理异常
IF 节点检查 hasMissingRequired。若为 true,向 Slack #support-ops 发送错误告警(含缺失字段列表和 ticketId),并通过 Respond to Webhook 返回 HTTP 422 JSON(reason: missing_required_fields)。若验证通过,进入查重分支。
运行提示词 · 来源原文。Slack 节点「Slack Intake Error Alert」的 Text 字段
Rejected malformed support ticket from {{$json.sourceSystem}}. Missing: {{$json.missingFields.join(", ")}}. Ticket ID: {{$json.ticketId}}Google Sheets 查重
Google Sheets 节点按 dedupeKey 列在「Support Audit Log」表中执行 lookup。若找到匹配(IF 节点判断 row_number/dedupeKey/ticketId 非空),向 #support-triage 发送重复通知,并返回 JSON 标记 duplicate: true。若无重复,继续进入 HubSpot enrichment 分支。
运行提示词 · 来源原文。Slack 节点「Slack Duplicate Notice」的 Text 字段
Duplicate support ticket detected for {{$node["Normalize Ticket + SLA Profile"].json.requesterEmail}}: {{$node["Normalize Ticket + SLA Profile"].json.subject}}. Existing audit row: {{$json.row_number || "unknown"}}HubSpot 客户信息补全与 OpenAI 分类起草
HTTP Request 节点向 HubSpot CRM API 搜索联系人(按 email),获取 firstname、lastname、company、lifecyclestage、hubspotscore。随后 HTTP Request 节点调用 OpenAI chat/completions(gpt-4o-mini,temperature 0.2,response_format: json_object),传入 system prompt 要求返回严格 JSON(category, priority, sentiment, confidence, internalSummary, suggestedRoute, replySubject, replyDraft, slaRiskReason),以及包含工单主题、发件人、公司、VIP 标识、HubSpot 上下文(截断至 1200 字符)和客户消息的 user prompt。两个节点均启用 Continue On Fail。
运行提示词 · 来源原文。OpenAI HTTP 请求 messages[0].content(system role)
You are a senior support operations analyst. Return strict JSON only with keys: category, priority, sentiment, confidence, internalSummary, suggestedRoute, replySubject, replyDraft, slaRiskReason. priority must be one of p0,p1,p2,p3. sentiment must be calm,confused,frustrated,angry. suggestedRoute must be engineering,billing,success,security,general,human_review.
运行提示词 · 来源原文。OpenAI HTTP 请求 messages[1].content(user role),n8n 表达式模板,需在节点中保持原样
Ticket: ${$node["Normalize Ticket + SLA Profile"].json.subject}\nFrom: ${$node["Normalize Ticket + SLA Profile"].json.requesterEmail}\nCompany: ${$node["Normalize Ticket + SLA Profile"].json.companyName || "unknown"}\nVIP domain: ${$node["Normalize Ticket + SLA Profile"].json.vipDomain}\nHubSpot lookup: ${JSON.stringify($json).slice(0, 1200)}\nMessage: ${$node["Normalize Ticket + SLA Profile"].json.message}解析 AI 结果并路由决策
Code 节点「Parse AI Triage + Fallback」解析 OpenAI 返回的 JSON(清理 markdown 代码块),若解析失败或 AI 报错则使用 fallback 值(category=general, priority 按 VIP 取 p1/p2, confidence=0.45, suggestedRoute=human_review)。计算紧急升级条件:VIP 域名、priority 为 p0/p1、sentiment=angry、category 为 security/billing、或主题/消息含 urgentKeywords(outage/down/breach 等)。同时判定 needsHumanReview:AI 失败、confidence<0.76、suggestedRoute=human_review 或已触发升级。输出 routeDecision 为 urgent_escalation / human_review / draft_ready。
运行提示词 · 来源原文。n8n Code 节点「Parse AI Triage + Fallback」的 JS Code 字段
const ticket = $node["Normalize Ticket + SLA Profile"].json; const hubspot = $node["Enrich Contact in HubSpot"].json || {}; const aiResponse = $input.first().json || {}; const safeParseJson = (content) => { if (!content || typeof content !== 'string') return null; const cleaned = content.replace(/^\s*```(?:json)?/i, '').replace(/```\s*$/i, '').trim(); try { return JSON.parse(cleaned); } catch (error) { return null; } }; const content = aiResponse.choices?.[0]?.message?.content; const parsed = safeParseJson(content); const aiFailed = !parsed || Boolean(aiResponse.error); const fallback = { category: 'general', priority: ticket.vipDomain ? 'p1' : 'p2', sentiment: 'confused', confidence: 0.45, internalSummary: `Manual review needed for ${ticket.subject}. AI triage was unavailable or returned invalid JSON.`, suggestedRoute: 'human_review', replySubject: `Re: ${ticket.subject}`, replyDraft: `Hi ${ticket.requesterName || 'there'},\n\nThanks for reaching out. We received your request and our team is reviewing it now. We'll follow up with the next step shortly.\n\nBest,\nSupport`, slaRiskReason: aiFailed ? 'AI request failed or produced invalid output.' : 'Fallback route selected.', }; const triage = { ...fallback, ...(parsed || {}) }; const priority = String(triage.priority || fallback.priority).toLowerCase(); const category = String(triage.category || fallback.category).toLowerCase(); const sentiment = String(triage.sentiment || fallback.sentiment).toLowerCase(); const confidence = Number(triage.confidence || fallback.confidence); const riskText = `${ticket.subject} ${ticket.message} ${triage.slaRiskReason || ''}`.toLowerCase(); const urgentKeywords = ['outage', 'down', 'breach', 'security', 'data loss', 'cannot login', 'production', 'payment failed', 'chargeback']; const keywordEscalation = urgentKeywords.some((keyword) => riskText.includes(keyword)); const protectedCategory = ['security', 'billing'].includes(category); const needsEscalation = ticket.vipDomain || ['p0', 'p1'].includes(priority) || sentiment === 'angry' || protectedCategory || keywordEscalation; const needsHumanReview = aiFailed || confidence < 0.76 || triage.suggestedRoute === 'human_review' || needsEscalation; const hubspotContact = Array.isArray(hubspot.results) && hubspot.results.length > 0 ? hubspot.results[0] : null; return [{ json: { ...ticket, hubspotFound: Boolean(hubspotContact), hubspotContactId: hubspotContact?.id || '', hubspotLifecycleStage: hubspotContact?.properties?.lifecyclestage || '', hubspotScore: hubspotContact?.properties?.hubspotscore || '', category, priority, sentiment, confidence, suggestedRoute: triage.suggestedRoute || fallback.suggestedRoute, internalSummary: triage.internalSummary || fallback.internalSummary, replySubject: triage.replySubject || fallback.replySubject, replyDraft: triage.replyDraft || fallback.replyDraft, slaRiskReason: triage.slaRiskReason || fallback.slaRiskReason, needsEscalation, needsHumanReview, aiFailed, routeDecision: needsEscalation ? 'urgent_escalation' : needsHumanReview ? 'human_review' : 'draft_ready', }, }];执行路由:升级、人工复核或生成草稿
根据 routeDecision 分支处理: - urgent_escalation:HTTP Request 节点调用 Linear GraphQL API 创建 Issue(需替换 REPLACE_WITH_LINEAR_TEAM_ID),随后 Slack 节点向 #support-escalations 发送带 :rotating_light: 的紧急告警(含 priority、category、VIP 标识、internalSummary、ticketUrl/ticketId)。 - human_review:Slack 节点向 #support-triage 发送 :eyes: 人工复核通知(含 confidence、category、sentiment、internalSummary、replyDraft)。 - draft_ready:Gmail 节点创建回复草稿(收件人 requesterEmail,主题 replySubject,内容 replyDraft)。 无论哪条路径,最终都进入 Google Sheets 节点追加审计行(含 subject、category、priority、sentiment、confidence、routeDecision 等),并由 Respond to Webhook 返回最终 JSON(ok, ticketId, routeDecision, priority, category, humanReview)。
运行提示词 · 来源原文。Slack 节点「Slack Urgent Routing Alert」的 Text 字段
:rotating_light: {{$node["Parse AI Triage + Fallback"].json.priority.toUpperCase()}} {{$node["Parse AI Triage + Fallback"].json.category}} ticket from {{$node["Parse AI Triage + Fallback"].json.requesterEmail}} {{$node["Parse AI Triage + Fallback"].json.vipDomain ? "(VIP domain)" : ""}} Summary: {{$node["Parse AI Triage + Fallback"].json.internalSummary}} Ticket: {{$node["Parse AI Triage + Fallback"].json.ticketUrl || $node["Parse AI Triage + Fallback"].json.ticketId}}运行提示词 · 来源原文。Slack 节点「Slack Human Review Queue」的 Text 字段
:eyes: Human review needed for {{$json.requesterEmail}} — {{$json.subject}} Confidence: {{$json.confidence}} | Category: {{$json.category}} | Sentiment: {{$json.sentiment}} AI summary: {{$json.internalSummary}} Suggested reply: {{$json.replyDraft}}
可复制提示词
搭建指导 · 本站建议版本。交给搭建助手,协助用户确认前置条件并导入模板
这一段是提示词,页面不会在这里插入广告。
你是一个资深支持运营分析师助手。用户正在尝试复现 n8n 模板 19097(AI Support Triage)。请先确认用户已准备好以下凭证和环境: 1. n8n 实例(云或自托管) 2. Slack OAuth2(#support-ops、#support-triage、#support-escalations 权限) 3. Google Sheets OAuth2(可编辑审计表) 4. Gmail OAuth2(如需创建草稿) 5. HubSpot private app token(CRM 搜索权限) 6. OpenAI API key 7. Linear API key(或准备替换为 Jira/Asana) 8. Google Sheets「Support Audit Log」表含 dedupeKey 列 确认后,指导用户: - 从 https://n8n.io/workflows/19097/ 导入模板 - 替换所有 REPLACE_WITH_* 占位值(Sheet ID、VIP 域名、Linear team ID、API key) - 配置 webhook 端点 /ai-support-triage - 用单个测试 payload 验证完整流程后再激活 注意:节点内的 OpenAI system prompt、user prompt 模板、Slack 消息模板和 JS 代码均已在模板中配置,无需额外编写;只需替换凭证和占位值即可运行。
先确认凭证齐全,再导入模板并替换占位值,测试后激活
来源
Triage support tickets with OpenAI, HubSpot, Slack, Linear, and Google Sheets · flowforgeai · n8n.io · 2026-09-02
留言与反馈
Agent 反馈
以下反馈由 Agent 提交,经用户确认和审核后公开,不代表平台已验证。