Respuesta de Correo Electrónico con IA Basada en Contexto de HubSpot y Aprobación en Slack
Este es unCRM, AI Summarizationflujo de automatización del dominio deautomatización que contiene 19 nodos.Utiliza principalmente nodos como If, Code, Gmail, Slack, Filter. Respuesta de Correo Electrónico con IA Basada en Contexto de HubSpot y Aprobación en Slack
- •Cuenta de Google y credenciales de API de Gmail
- •Bot Token de Slack o URL de Webhook
- •Clave de API de HubSpot
- •Pueden requerirse credenciales de autenticación para la API de destino
- •Clave de API de Google Gemini
Nodos utilizados (19)
Categoría
{
"meta": {
"instanceId": "09423a3357ff64bdcc082268b9d577001317edbe377a3ccfb0b131ffb9554b30"
},
"nodes": [
{
"id": "6897a614-cd5a-4e76-99fb-7094b4692dd2",
"name": "Modelo de chat Google Gemini",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
2352,
464
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "1b011a66-c09a-4a6c-b666-89a5301f54cc",
"name": "Reply to a message",
"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": "Watch Gmail (New Inbound)",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
544,
256
],
"parameters": {
"filters": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"typeVersion": 1.3
},
{
"id": "4b25d17f-ce32-47d2-a27d-ca52c68a5e46",
"name": "Filtrar: Allowed Sender",
"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": "Draft Reply (Agente IA)",
"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": "Find Contact by Correo electrónico",
"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": "Establecer Record Types",
"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": "List Contact Associations",
"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": "Build Batch Read Requests",
"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": "Batch Read Objects",
"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": "Normalize CRM Context for LLM",
"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": "Esperar for Response - Approve Auto-Reply",
"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": "If Approved?",
"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": "Nota adhesiva",
"type": "n8n-nodes-base.stickyNote",
"position": [
976,
112
],
"parameters": {
"color": 7,
"width": 1280,
"height": 384,
"content": "## Get CRM information\nFetch contact info and associated deals, tickets and compaines."
},
"typeVersion": 1
},
{
"id": "bb2fb3e1-dbcc-468f-9d0b-65e379aad792",
"name": "Nota adhesiva1",
"type": "n8n-nodes-base.stickyNote",
"position": [
496,
112
],
"parameters": {
"color": 7,
"width": 464,
"height": 384,
"content": "## Get incoming email"
},
"typeVersion": 1
},
{
"id": "53796f4b-13c6-4af0-ad1b-199ac49ac096",
"name": "Nota adhesiva2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2272,
112
],
"parameters": {
"color": 7,
"width": 400,
"height": 528,
"content": "## Write draft response"
},
"typeVersion": 1
},
{
"id": "39b501df-7fc7-470f-8aa4-6dacc05eb255",
"name": "Nota adhesiva3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2688,
112
],
"parameters": {
"color": 7,
"width": 704,
"height": 384,
"content": "## Approve and reply"
},
"typeVersion": 1
},
{
"id": "86267d6e-aa6e-4521-a0fb-c823724b7e7d",
"name": "Workflow Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": 5,
"width": 468,
"height": 624,
"content": "## AI email reply with HubSpot context + Slack approval\n\n### How it works\n1. A new Gmail message arrives.\n2. Look up the sender in HubSpot and fetch related deals, companies, and tickets.\n3. Draft a reply with Gemini.\n4. Post the draft to Slack for approval.\n5. If approved, send a reply\n\n### Setup\n1. **Gmail:** Use the same account for the trigger and the send nodes.\n2. **HubSpot:** Connect all the HubSpot nodes.\n3. **Slack:** Connect Slack and choose where to send the draft for approval.\n4. **Gemini:** Add your [Google AI Studio](https://aistudio.google.com/) API key\n5. **Filter:** Tweak or remove the sender rule before going live.\n\n### Customize\n1. **Prompt:** Adjust tone, length, and how much CRM detail to include.\n2. **Fields:** Pick which deal/company/ticket properties to pull.\n3. **Approval:** Skip Slack to auto-send, or add extra reviewers if needed."
},
"typeVersion": 1
},
{
"id": "f7cb3860-8006-401b-9c4a-2fbb3ca0aa68",
"name": "Nota adhesiva6",
"type": "n8n-nodes-base.stickyNote",
"position": [
3184,
432
],
"parameters": {
"color": 7,
"width": 376,
"height": 232,
"content": "### 💡 Customizing this workflow\n\n* Be sure to update your name in the Agent prompt, so it get added to the email signature\n* Add a HubSpot form as the trigger and send customers a personalized followup email\n* Instead of replying to the message, create a draft in your Gmail inbox instead. That way, you'll be able to edit the message before sending."
},
"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": {
"464de728-acf5-4664-9469-f4f660d29ec6": {
"main": [
[
{
"node": "1b011a66-c09a-4a6c-b666-89a5301f54cc",
"type": "main",
"index": 0
}
]
]
},
"Set Record Types": {
"main": [
[
{
"node": "94281ea8-a451-42a9-9fb6-b90e4b5dc42a",
"type": "main",
"index": 0
}
]
]
},
"ee30292a-fce0-4e18-a422-3d7a58b82e4e": {
"main": [
[
{
"node": "e588dad3-5cdc-47f8-b180-d97d8e0bbb0a",
"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
}
]
]
},
"70d7c8e3-bbc5-4be2-bc65-c5c168bcba84": {
"main": [
[
{
"node": "ee30292a-fce0-4e18-a422-3d7a58b82e4e",
"type": "main",
"index": 0
}
]
]
},
"94281ea8-a451-42a9-9fb6-b90e4b5dc42a": {
"main": [
[
{
"node": "70d7c8e3-bbc5-4be2-bc65-c5c168bcba84",
"type": "main",
"index": 0
}
]
]
},
"287d6e80-9e28-4188-87a8-c46def152c1e": {
"main": [
[
{
"node": "Filter: Allowed Sender",
"type": "main",
"index": 0
}
]
]
},
"e588dad3-5cdc-47f8-b180-d97d8e0bbb0a": {
"main": [
[
{
"node": "Draft Reply (AI Agent)",
"type": "main",
"index": 0
}
]
]
},
"Wait for Response - Approve Auto-Reply": {
"main": [
[
{
"node": "464de728-acf5-4664-9469-f4f660d29ec6",
"type": "main",
"index": 0
}
]
]
}
}
}¿Cómo usar este flujo de trabajo?
Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.
¿En qué escenarios es adecuado este flujo de trabajo?
Avanzado - CRM, Resumen de IA
¿Es de pago?
Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.
Flujos de trabajo relacionados recomendados
Compartir este flujo de trabajo