This workflow corresponds to n8n.io template #16235 — we link there as the canonical source.
This workflow follows the Agent → Emailsend recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "JMLklGiqNKJswtXO",
"name": "AI support assistant via EmailConnect \u2014 match Notion KB, draft reply or escalate",
"tags": [],
"nodes": [
{
"id": "f02d973f-9ccf-40bb-adff-a8eaec7a5de9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
128
],
"parameters": {
"width": 720,
"height": 518,
"content": "## AI support assistant via EmailConnect \u2014 match Notion KB, draft reply or escalate\n\n### How it works\n\nInbound support emails arrive through an EmailConnect webhook. Obvious spam is dropped by score. For everything else, the workflow loads your Q&A knowledge base from Notion, asks an LLM to pick the single closest matching entry (or return NO_MATCH below a confidence threshold), and then branches:\n\n- **Match found** \u2192 an LLM drafts a tailored reply using the KB answer and sends it to the customer.\n- **No confident match** \u2192 the question is escalated to your team by email, and the customer gets a friendly \"we're on it\" acknowledgement.\n\nBoth drafting agents keep short per-sender memory so follow-ups read naturally.\n\n### Setup steps\n\n- Point your EmailConnect alias webhook at the **When Email Received** trigger and verify it.\n- Connect Notion and set the database in **Get All KB Questions**. The Notion DB needs columns that surface as `question`, `answer`, and `help_url`.\n- Connect your LLM credential on the three chat-model nodes, and an SMTP credential on the three send-email nodes.\n- Set your own from-address, escalation inbox, and BCC in the email nodes.\n\n### Customization\n\nTune the spam score threshold, raise/lower the confidence bar in the **Find Best Match** prompt, or swap the escalation step for a ticket/Slack action."
},
"typeVersion": 1
},
{
"id": "e6f4b1a8-77b9-4a0c-82b8-30c3f97bd7c3",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1008,
128
],
"parameters": {
"color": 7,
"width": 424,
"height": 438,
"content": "## Receive & spam-gate\n\nStarts on the EmailConnect webhook and drops messages above the spam-score threshold before any AI runs."
},
"typeVersion": 1
},
{
"id": "12f82423-a461-40f0-a825-43d28a3bb8b3",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
128
],
"parameters": {
"color": 7,
"width": 520,
"height": 560,
"content": "## Match against Notion KB\n\nLoads all Q&A rows from Notion, formats them, and asks the LLM to return the ID of the closest entry (or NO_MATCH below ~85% confidence). The result decides whether to answer or escalate."
},
"typeVersion": 1
},
{
"id": "ed58cfda-b664-4ca2-9be4-0cdb2b3b339c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-16,
128
],
"parameters": {
"color": 7,
"width": 620,
"height": 556,
"content": "## Find KB match for received e-mail\n\nHave an LLM evaluate the original question versus the Notion articles found and calculate a confidence for each. Then have it indicate whether NO_MATCH was found, it came up with a hallucinated index or found a genuine answer."
},
"typeVersion": 1
},
{
"id": "fae0e83d-77ca-4492-9568-8ca725817255",
"name": "When Email Received",
"type": "n8n-nodes-base.webhook",
"position": [
-960,
384
],
"parameters": {
"path": "emailconnect-inbound",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "d93c2bc8-ad71-4665-93da-4cfcc28c1962",
"name": "Check if spam",
"type": "n8n-nodes-base.if",
"position": [
-752,
384
],
"parameters": {
"options": {
"ignoreCase": false
},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "32329838-35bf-4de1-ae3d-58816e992f43",
"operator": {
"type": "boolean",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.body.spam.score }}",
"rightValue": ""
},
{
"id": "3551762c-fad0-4305-838f-6e1c54a59d98",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.body.spam.score }}",
"rightValue": 10
}
]
}
},
"typeVersion": 2.2
},
{
"id": "87b14197-a8fd-4522-b67c-bbb0114926b5",
"name": "Stop processing",
"type": "n8n-nodes-base.noOp",
"position": [
-464,
288
],
"parameters": {},
"typeVersion": 1
},
{
"id": "5c0576a7-9fec-44ca-a413-5d6cd80bb86a",
"name": "Get All KB Questions",
"type": "n8n-nodes-base.notion",
"position": [
-464,
496
],
"parameters": {
"options": {},
"resource": "databasePage",
"operation": "getAll",
"returnAll": true,
"databaseId": {
"__rl": true,
"mode": "id",
"value": "YOUR_NOTION_DATABASE_ID",
"__regex": "^([0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12})"
}
},
"credentials": {
"notionApi": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "7adf39c5-d34c-4d6c-b3fa-6a73a61c030f",
"name": "Format KB data",
"type": "n8n-nodes-base.code",
"position": [
-272,
496
],
"parameters": {
"jsCode": "// Extract all Q&A pairs from Notion KB\nconst kbData = $('Get All KB Questions').all().map((item, index) => {\n return {\n id: index,\n question: item.json.property_question || 'No question',\n answer: item.json.property_answer || 'No answer',\n help_article: item.json.property_help_url || ''\n };\n});\n\n// Format for similarity comparison - include both question and context\nconst questionsForComparison = kbData.map(item =>\n `ID:${item.id} | Q: ${item.question} | Context: ${item.answer.substring(0, 120)}`\n).join('\\n');\n\n// Robust email data extraction - handles both wrapped and unwrapped payloads\nconst emailData = $('When Email Received').first().json;\nconst message = emailData.body?.message || emailData.message;\n\nreturn [{\n json: {\n kbData: kbData,\n questionsText: questionsForComparison,\n userName: message.sender.name,\n userEmail: message.sender.email,\n userQuestion: message.content.text,\n totalQuestions: kbData.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "e8f29f1b-3b2b-44e1-b30d-c4922c536d1e",
"name": "Find Best Match",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
112,
288
],
"parameters": {
"text": "=You are an expert at finding semantic similarity between questions. \n\n# Your task\nYou will be given a user's question and a list of questions from our knowledge base. Your job is to find the question that is most semantically similar to what the user is asking.\n\nUSER QUESTION: {{ $json.userQuestion }}\n\nAVAILABLE KB QUESTIONS:\n{{ $json.questionsText }}\n\nAnalyze the user's question and find the most relevant KB entry. Consider:\n- Exact keyword matches\n- Semantic similarity \n- Intent similarity\n- Context clues\n\nRespond with ONLY the ID number of the best matching question. If no good match exists (confidence < 85%), respond with \"NO_MATCH\".\n\nBest match ID:",
"options": {},
"promptType": "define"
},
"typeVersion": 2
},
{
"id": "c4b438a9-7925-4cef-bb7f-0b74ed2ddcf8",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
64,
512
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "8900f109-28a1-455f-8978-600f8f844473",
"name": "If",
"type": "n8n-nodes-base.if",
"position": [
704,
288
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3551762c-fad0-4305-838f-6e1c54a59d98",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.escalate }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "6ef3a250-8a47-40b6-bb31-3a0e1ddcbb46",
"name": "Draft escalation",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
944,
480
],
"parameters": {
"text": "=We've received a support request email. You are a supportive, friendly customer success manager, tasked with responding to their question.\n\n# Details\n## Sender\nName: {{ $('Format KB data').item.json.userName }}\nEmail: {{ $('Format KB data').item.json.userEmail }}\n\n## Original e-mail message\n{{ $('Format KB data').item.json.userQuestion }}\n\n## Result\nAs we don't have a direct answer for sender, we've escalated the question for further follow up.\n\n# Task\nDraft an e-mail to the sender that informs them that their questions has been escalated. Explain that for any legitimate e-mail, they can expect a response within a business day. **IF** however, their message is commercial or promotional, explain that that response might not come.\n\n# Guidelines\nReview the user's original e-mail message and make sure you:\n- Check for recent conversations from the same sender ({{ $('Format KB data').item.json.userEmail }}) in the attached memory tool to refer to if relevant.\n- Meet their tone, e.g. formal vs informal \n- Meet their depth, e.g. to the point vs detail\n- Be professional and concise, yet warm",
"options": {
"systemMessage": "- Write in normal English sentence case. Do not capitalize every noun. Use standard capitalization rules: sentences begin with a capital, and proper nouns are capitalized.\n- Close your message by picking any gender neutral first name for yourself, and identifying yourself as a customer success agent."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "cd7935bb-f12f-454a-9543-a0ad63a8d5b3",
"name": "OpenRouter Chat Model2",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
880,
704
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "086f66f8-8ff3-47b5-9d03-86546b713019",
"name": "Simple Memory1",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
1040,
704
],
"parameters": {
"sessionKey": "={{ $('Format KB data').item.json.userEmail }}",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "88134f40-fa39-43e9-916b-c02540e8545e",
"name": "Structured Output Parser1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1200,
704
],
"parameters": {
"jsonSchemaExample": "{\n\t\"subject\": \"Email subject\",\n\t\"message\": \"Hi there, thanks for reaching out!\"\n}"
},
"typeVersion": 1.3
},
{
"id": "50b197c4-6afe-4046-ae8b-580815feebcf",
"name": "Draft answer",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
928,
944
],
"parameters": {
"text": "=We've received a support request email. You are a supportive, friendly customer success manager, tasked with responding to their question.\n\n# Details\n## Sender\nName: {{ $('Format KB data').item.json.userName }}\nEmail: {{ $('Format KB data').item.json.userEmail }}\n\n## Original e-mail message\n{{ $json.userQuestion }}\n\n## Matched answer in our KB\n{{ $('Get answer').item.json.matchedAnswer }}\n\n## Further reading (include in reply only if relevant and present)\n{{ $('Get answer').item.json.matchedHelpUrl }}\n\n# Task\nDraft an e-mail to the sender that incorporates the answer found in the KB to address their question. \n\n# Guidelines\nReview the user's original e-mail message for their question and make sure you:\n- Tailor the KBs answer to their specific question and remove fluf. Be to the point. \n- Check recent conversations from the same sender ({{ $('Format KB data').item.json.userEmail }}) in the attached memory tool to refer to if relevant.\n- Meet their tone, e.g. formal vs informal \n- Meet their depth, e.g. to the point vs detail\n- Be professional and concise, yet warm",
"options": {
"systemMessage": "- Write in normal English sentence case. Do not capitalize every noun. Use standard capitalization rules: sentences begin with a capital, and proper nouns are capitalized.\n- Close your message by picking any gender neutral first name for yourself, and identifying yourself as a customer success agent."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "04aeb3ad-3b42-4aad-89af-5f8a6401555e",
"name": "OpenRouter Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
864,
1184
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "f754fdea-ace4-4813-827a-ca14281c6e87",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
1024,
1184
],
"parameters": {
"sessionKey": "={{ $('Format KB data').item.json.userEmail }}",
"sessionIdType": "customKey",
"contextWindowLength": 8
},
"typeVersion": 1.3
},
{
"id": "0167af9c-f4ad-4518-afda-ff99afe1fb5b",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1184,
1184
],
"parameters": {
"jsonSchemaExample": "{\n\t\"subject\": \"Email subject\",\n\t\"message\": \"Hi there, thanks for reaching out!\"\n}"
},
"typeVersion": 1.3
},
{
"id": "bd3be683-89c0-4e03-8410-c93dba8057bf",
"name": "Simple Memory3",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
256,
512
],
"parameters": {
"sessionKey": "={{ $('Format KB data').item.json.userEmail }}",
"sessionIdType": "customKey",
"contextWindowLength": 8
},
"typeVersion": 1.3
},
{
"id": "1a68258f-e729-4bb3-8507-97d5e49a3ef9",
"name": "Get answer",
"type": "n8n-nodes-base.code",
"position": [
448,
288
],
"parameters": {
"jsCode": "const raw = $input.first().json.output?.toString().trim() ?? '';\nconst matchId = /^NO_MATCH$/i.test(raw) ? 'NO_MATCH' : (raw.match(/\\d+/) || ['NO_MATCH'])[0];\nconst kbData = $('Format KB data').first().json.kbData\nconst userEmail = $('Format KB data').first().json.userEmail\nconst userQuestion = $('Format KB data').first().json.userQuestion\n\n// If LLM concluded there was no answer in the KB.\nif (matchId === \"NO_MATCH\") {\n return [{\n json: {\n found: false,\n escalate: true,\n message: \"ESCALATE_TO_HUMAN\"\n }\n }];\n}\n\n/**\n * A defensive escalation against a bad/hallucinated index. This \n * makes sure the index exists.\n */ \nconst matchedEntry = kbData[parseInt(matchId)];\nif (!matchedEntry) {\n return [{\n json: {\n found: false,\n escalate: true, \n message: \"ESCALATE_TO_HUMAN\"\n }\n }];\n}\n\n// If the LLM found an answer with high confidence.\nreturn [{\n json: {\n found: true,\n escalate: false,\n matchedQuestion: matchedEntry.question,\n matchedAnswer: matchedEntry.answer,\n matchedHelpUrl: matchedEntry.help_article,\n userEmail: userEmail,\n userQuestion: userQuestion,\n confidence: \"high\"\n }\n}];"
},
"typeVersion": 2
},
{
"id": "48b63650-6f4b-4ba2-8666-cc5a6d09af39",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
128
],
"parameters": {
"color": 7,
"width": 940,
"height": 1228,
"content": "## Draft reply or escalate\n\n**Top branch \u2014 escalate:** notify your team and send the customer an acknowledgement.\n\n**Bottom branch \u2014 answer:** draft a reply grounded in the matched KB answer and send it.\n\nBoth drafting agents use per-sender memory keyed on the customer's email."
},
"typeVersion": 1
},
{
"id": "86cb94f8-1e1a-4598-a8d7-f2c295f05c16",
"name": "Send escalation to human",
"type": "n8n-nodes-base.emailSend",
"position": [
1328,
272
],
"parameters": {
"text": "=A question was raised, currently not in the KB. Please review, and answer the user directly.\n\nDetails as per below.\n\nFrom: {{ $('Format KB data').item.json.userName }}\nEmail: {{ $('Format KB data').item.json.userEmail }}\nMessage: {{ $('Format KB data').item.json.userQuestion }}",
"options": {
"appendAttribution": false
},
"subject": "[!] Escalated question",
"toEmail": "user@example.com",
"fromEmail": "Support <support@example.com>",
"emailFormat": "text"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "56ad2b0e-8b8f-4bb3-9438-5c6d48d92b41",
"name": "Send answer email to sender",
"type": "n8n-nodes-base.emailSend",
"position": [
1328,
944
],
"parameters": {
"text": "={{ $json.output.message }}",
"options": {
"bccEmail": "user@example.com",
"appendAttribution": false
},
"subject": "={{ $json.output.subject }}",
"toEmail": "={{ $('Format KB data').item.json.userName }} <{{ $('Format KB data').item.json.userEmail }}>",
"fromEmail": "Support <support@example.com>",
"emailFormat": "text"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "3d4599aa-8296-46e2-8fe4-ce57637a43b1",
"name": "Inform need for escalation to sender",
"type": "n8n-nodes-base.emailSend",
"position": [
1328,
480
],
"parameters": {
"text": "={{ $json.output.message }}",
"options": {
"bccEmail": "user@example.com",
"appendAttribution": false
},
"subject": "={{ $json.output.subject }}",
"toEmail": "={{ $('Format KB data').item.json.userName }} <{{ $('Format KB data').item.json.userEmail }}>",
"fromEmail": "Support <support@example.com>",
"emailFormat": "text"
},
"credentials": {
"smtp": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "be48d42a-46f0-4a87-8f58-f47393cb98dc",
"connections": {
"If": {
"main": [
[
{
"node": "Send escalation to human",
"type": "main",
"index": 0
},
{
"node": "Draft escalation",
"type": "main",
"index": 0
}
],
[
{
"node": "Draft answer",
"type": "main",
"index": 0
}
]
]
},
"Get answer": {
"main": [
[
{
"node": "If",
"type": "main",
"index": 0
}
]
]
},
"Draft answer": {
"main": [
[
{
"node": "Send answer email to sender",
"type": "main",
"index": 0
}
]
]
},
"Check if spam": {
"main": [
[
{
"node": "Stop processing",
"type": "main",
"index": 0
}
],
[
{
"node": "Get All KB Questions",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory": {
"ai_memory": [
[
{
"node": "Draft answer",
"type": "ai_memory",
"index": 0
}
]
]
},
"Format KB data": {
"main": [
[
{
"node": "Find Best Match",
"type": "main",
"index": 0
}
]
]
},
"Simple Memory1": {
"ai_memory": [
[
{
"node": "Draft escalation",
"type": "ai_memory",
"index": 0
}
]
]
},
"Simple Memory3": {
"ai_memory": [
[
{
"node": "Find Best Match",
"type": "ai_memory",
"index": 0
}
]
]
},
"Find Best Match": {
"main": [
[
{
"node": "Get answer",
"type": "main",
"index": 0
}
]
]
},
"Draft escalation": {
"main": [
[
{
"node": "Inform need for escalation to sender",
"type": "main",
"index": 0
}
]
]
},
"When Email Received": {
"main": [
[
{
"node": "Check if spam",
"type": "main",
"index": 0
}
]
]
},
"Get All KB Questions": {
"main": [
[
{
"node": "Format KB data",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "Find Best Match",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter Chat Model1": {
"ai_languageModel": [
[
{
"node": "Draft answer",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OpenRouter Chat Model2": {
"ai_languageModel": [
[
{
"node": "Draft escalation",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Draft answer",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Structured Output Parser1": {
"ai_outputParser": [
[
{
"node": "Draft escalation",
"type": "ai_outputParser",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
notionApismtp
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow receives inbound support emails via an EmailConnect webhook, filters likely spam, matches the message against a Notion Q&A knowledge base using an OpenRouter chat model, and then either drafts and sends a grounded reply or escalates the request to a human by email.…
Source: https://n8n.io/workflows/16235/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow acts as an AI-powered research assistant that takes a topic from the user, performs multi-step intelligent research, and stores the final report in Notion. It uses advanced search, conte
Are you drowning in daily operational chaos, desperately trying to juggle sales, projects, content, and client communication? Imagine an AI brain that handles it all, freeing you to lead your business
Who is this for? Agencies, consultants, and service providers who conduct discovery calls and need to quickly turn conversations into professional proposals.
Public-facing professionals (developer advocates, founders, marketers, content creators) who get bombarded with LinkedIn messages that aren't actually for them - support requests when you're in market
Sourcing_Agent_LinkedIn_validator_production. Uses executeWorkflowTrigger, chainLlm, mySql, httpRequest. Event-driven trigger; 51 nodes.