Réponse par IA aux e-mails basée sur le contexte HubSpot avec approbation Slack
Avancé
Ceci est unCRM, AI Summarizationworkflow d'automatisation du domainecontenant 19 nœuds.Utilise principalement des nœuds comme If, Code, Gmail, Slack, Filter. Réponse par IA aux e-mails basée sur le contexte HubSpot avec approbation Slack
Prérequis
- •Compte Google et informations d'identification Gmail API
- •Token Bot Slack ou URL Webhook
- •Clé API HubSpot
- •Peut nécessiter les informations d'identification d'authentification de l'API cible
- •Clé API Google Gemini
Nœuds utilisés (19)
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
"meta": {
"instanceId": "09423a3357ff64bdcc082268b9d577001317edbe377a3ccfb0b131ffb9554b30"
},
"nodes": [
{
"id": "6897a614-cd5a-4e76-99fb-7094b4692dd2",
"name": "Modèle 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": "Filtrer: 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 (Agent 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 E-mail",
"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": "Définir 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": "Attendre 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": "Note adhésive",
"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": "Note adhésive1",
"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": "Note adhésive2",
"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": "Note adhésive3",
"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": "Note adhésive6",
"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
}
]
]
}
}
}Foire aux questions
Comment utiliser ce workflow ?
Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.
Dans quelles scénarios ce workflow est-il adapté ?
Avancé - CRM, Résumé IA
Est-ce payant ?
Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.
Workflows recommandés
Amélioration des contacts HubSpot avec l'IA
Amélioration des contacts HubSpot avec l'IA
Filter
Hubspot
Agent
+
Filter
Hubspot
Agent
12 NœudsMiha
CRM
Explorer les nœuds n8n dans la bibliothèque de références visuelles
Explorer les nœuds n8n dans la base de références visuelles
If
Ftp
Set
+
If
Ftp
Set
113 NœudsI versus AI
Autres
Résumé d'appel par IA + Tâche de suivi HubSpot
Résumé d'appel par IA + Tâche de suivi HubSpot
Hubspot
Form Trigger
Hubspot Tool
+
Hubspot
Form Trigger
Hubspot Tool
12 NœudsMiha
CRM
e-mail转WhatsApp - AI多账户桥接
AI驱动dee-mail转发至WhatsApp,intégrationGmail、OutlooketGoogle Gemini
Code
Gmail
Http Request
+
Code
Gmail
Http Request
22 NœudsiTzJok3r
Productivité personnelle
基于AIde会议研究与每日议程(Google日历、Attio CRMetSlack)
基于AIde会议研究与每日议程:utilisationGoogle日历、Attio CRMetSlack
If
Set
Code
+
If
Set
Code
30 NœudsHarry Siggins
Résumé IA
Analyseur d'écart de contenu concurrentiel : mappage automatisé des thèmes de sites web
Analyser les écarts de contenu concurrentiel avec Gemini AI, Apify et Google Sheets
If
Set
Code
+
If
Set
Code
30 NœudsMychel Garzon
Divers
Informations sur le workflow
Niveau de difficulté
Avancé
Nombre de nœuds19
Catégorie2
Types de nœuds11
Description de la difficulté
Auteur
Liens externes
Voir sur n8n.io →
Partager ce workflow