This workflow corresponds to n8n.io template #15248 — we link there as the canonical source.
This workflow follows the HTTP Request → Informationextractor 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "dde8db34-25a8-4f2b-a68d-0bc6d18e7a44",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-4192,
1280
],
"parameters": {
"width": 480,
"height": 896,
"content": "## \ud83d\udce7 StuccoOS Email Triage \u2014 Smart Labels + SLA Routing\n\n### How it works\n\n1. The webhook triggers when an email arrives.\n2. Sent emails are filtered out to prevent duplicates.\n3. Emails are processed one by one, reading the content for classification.\n4. AI classifies emails, filtering spam and creating labels.\n5. Emails are routed according to SLA status and tasks are logged or alerts raised.\n\n### Setup steps\n\n- [ ] Setup webhook to trigger on email receipt.\n- [ ] Configure AI model for email classification.\n- [ ] Connect to CRM via API key or OAuth.\n- [ ] Set up rate limiting as needed.\n- [ ] Map SLA statuses to CRM list IDs.\n\n### Customization\n\nEmail classification and label creation can be customized based on specific needs."
},
"typeVersion": 1
},
{
"id": "61ea0b8e-0e40-4916-a17c-c7799ae2eb54",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3632,
1792
],
"parameters": {
"color": 7,
"width": 416,
"height": 304,
"content": "## Trigger and initial filter\n\nStarts with a webhook for incoming emails and ignores sent emails to continue processing. YOU can replace this with ANY email trigger. "
},
"typeVersion": 1
},
{
"id": "1325f8fc-7bc7-4fb4-a401-b525bcf1472f",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3184,
1744
],
"parameters": {
"color": 7,
"height": 336,
"content": "## Batch processing\n\nProcesses emails one by one to prepare for content reading and AI classification."
},
"typeVersion": 1
},
{
"id": "26e25f88-0bb9-487c-811e-f79d25a30340",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2912,
1616
],
"parameters": {
"color": 7,
"width": 768,
"height": 512,
"content": "## Content reading and AI classification\n\nReads email content and uses AI to classify it, filtering out spam before creating labels."
},
"typeVersion": 1
},
{
"id": "fec4cda1-f613-48d3-bea6-47505cf72829",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2112,
1568
],
"parameters": {
"color": 7,
"width": 416,
"height": 368,
"content": "## Label creation and routing\n\nCreates labels based on AI classification and routes emails by SLA color for further action."
},
"typeVersion": 1
},
{
"id": "b55c90ad-7d4d-47ad-903c-4b4f59be6084",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1664,
1280
],
"parameters": {
"color": 7,
"height": 912,
"content": "## SLA-based task creation\n\nCreates or updates tasks based on SLA color, directing to specific CRM actions."
},
"typeVersion": 1
},
{
"id": "c0af5f00-aea7-479f-ab80-c3e1a93120ea",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1392,
1488
],
"parameters": {
"color": 7,
"width": 1088,
"height": 704,
"content": "## CRM list and contact handling\n\nHandles CRM contact checking, updating, or creating as appropriate for routing SLA tasks."
},
"typeVersion": 1
},
{
"id": "7c16506a-9130-4301-9329-db226daa77bf",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-272,
1568
],
"parameters": {
"color": 7,
"width": 640,
"height": 528,
"content": "## Final CRM update\n\nAdds the contact to the SLA list and confirms updates, ensuring rate limits are respected."
},
"typeVersion": 1
},
{
"id": "b53f6eed-e514-4d98-9e27-e5fbe8561406",
"name": "When Email Arrives",
"type": "n8n-nodes-base.webhook",
"position": [
-3584,
1920
],
"parameters": {
"path": "agentmail-webhook",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "a127ebf0-2ee7-4aad-bfcd-e2c7886facae",
"name": "Filter Sent Emails",
"type": "n8n-nodes-base.filter",
"position": [
-3360,
1920
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "filter-received",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.event_type || $json.type }}",
"rightValue": "message.received"
}
]
}
},
"typeVersion": 2
},
{
"id": "24da19d7-8dad-4b41-84cf-13e904a075a2",
"name": "Loop Over Emails",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-3136,
1920
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "5d85f960-5ffd-42ed-a8c5-83448ddd428d",
"name": "Extract Email Content",
"type": "n8n-nodes-base.code",
"position": [
-2864,
1744
],
"parameters": {
"jsCode": "const payload = $input.first().json;\nconst message = payload.message || {};\nconst thread = payload.thread || {};\n\nlet fromField = message.from_ || message.from || '';\nlet senderEmail = fromField;\nif (fromField.includes('<') && fromField.includes('>')) {\n senderEmail = fromField.split('<')[1].split('>')[0].trim();\n} else {\n senderEmail = fromField.trim();\n}\n\nconst body = message.extracted_text || message.text || message.extracted_html || message.html || message.preview || '';\n\nif (!message.message_id || !message.inbox_id) {\n throw new Error('Missing message_id or inbox_id');\n}\nif (!body || body.trim().length === 0) {\n throw new Error('Empty email body');\n}\n\nreturn [{\n json: {\n event_id: payload.event_id || '',\n sender_email: senderEmail,\n raw_from: fromField,\n to: message.to || [],\n cc: message.cc || [],\n subject: message.subject || '(no subject)',\n body: body,\n message_id: message.message_id,\n inbox_id: message.inbox_id,\n thread_id: message.thread_id || thread.thread_id || '',\n existing_labels: message.labels || [],\n thread_labels: thread.labels || [],\n thread_message_count: thread.message_count || 1,\n has_attachments: (message.attachments || []).length > 0,\n attachment_count: (message.attachments || []).length,\n received_at: message.timestamp || message.created_at || new Date().toISOString(),\n processed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "e471e168-e1d7-470b-be76-3fac1651c405",
"name": "AI Email Classifier",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
-2640,
1744
],
"parameters": {
"text": "=Analyze this incoming email for a plastering/stucco contractor business.\n\nFrom: {{ $json.sender_email }}\nTo: {{ $json.to }}\nSubject: {{ $json.subject }}\nContent: {{ $json.body }}\n\nThread context: This is message {{ $json.thread_message_count }} in this thread. Has attachments: {{ $json.has_attachments }}.",
"options": {
"batching": {},
"systemPromptTemplate": "You are the 'Headless Brain' of StuccoOS \u2014 an intelligent triage system for plastering contractors.\n\nMission: Prevent LEAD LEAKAGE and SLEEPING REVENUE loss.\n\nRules:\n1. When in doubt, classify MORE urgent.\n2. New quote requests = minimum priority 'high' + 'NEW_LEAD'.\n3. Complaints about completed work = 'AFTERCARE' + minimum 'high'.\n4. Unknown specs = fill 'unknown', never leave empty.\n5. Words like 'urgent','ASAP','damage','leak','spoed','lekkage','schade','direct' = ALWAYS 'urgent'.\n6. helen_action 'true' for everything except pure admin/spam.\n7. SLEEPING_REVENUE = most valuable, always minimum 'high'.\n8. Spam/auto-replies = is_noise 'true'.\n9. sla_status determines entire downstream workflow. Be precise.\n10. Dual-intent messages = classify as MORE urgent.\n11. Works in Dutch and English.\n12. Only return values from allowed options."
},
"attributes": {
"attributes": [
{
"name": "sentiment",
"required": true,
"description": "The emotional tone of the incoming message. Must be exactly one of: positive | neutral | negative. A message expressing frustration, anger, or disappointment is 'negative'. A message expressing gratitude, excitement, or satisfaction is 'positive'. Everything else is 'neutral'."
},
{
"name": "category",
"required": true,
"description": "The communication type. Must be exactly one of: quote-request | follow-up | complaint | scheduling | invoice-question | praise | general-question | spam."
},
{
"name": "stucco_priority",
"required": true,
"description": "SLA classification for the 48h Guard system. Must be exactly one of: urgent | high | normal | low. 'urgent' = immediate action needed: damage, leaks, complaints, legal threats, words like ASAP/spoed/lekkage/schade. 'high' = new lead, quote acceptance, time-sensitive. 'normal' = general admin, planning, invoices. 'low' = praise, spam, newsletters."
},
{
"name": "inquiry_category",
"required": true,
"description": "StuccoOS pipeline stage. Must be exactly one of: NEW_LEAD | SLEEPING_REVENUE | ADMIN_FLOW | AFTERCARE. 'NEW_LEAD' = first-time inquiry. 'SLEEPING_REVENUE' = response to existing quote or follow-up (most revenue-critical). 'ADMIN_FLOW' = operational: invoices, scheduling. 'AFTERCARE' = post-completion: warranty, complaints, reviews."
},
{
"name": "department",
"required": true,
"description": "Where to route. Must be exactly one of: sales | planning | billing | aftercare."
},
{
"name": "technical_specs",
"required": true,
"description": "Extract technical details. Format: 'surface:[concrete|plasterboard|masonry|wood|unknown], finish:[stucco|plaster|decorative|spachtelputz|venetian|beton-cire|unknown], sqm:[number or unknown], rooms:[number or unknown]'. If nothing found: 'surface:unknown, finish:unknown, sqm:unknown, rooms:unknown'."
},
{
"name": "property_type",
"required": true,
"description": "Must be exactly one of: existing_build | new_build | commercial | unknown."
},
{
"name": "helen_action",
"required": true,
"description": "Should AI draft a response? Must be exactly one of: true | false. 'true' if emotional, new lead, sleeping revenue, or negative. 'false' only for pure admin or spam."
},
{
"name": "sla_status",
"required": true,
"description": "Escalation color. Must be exactly one of: RED | ORANGE | YELLOW | GREEN. RED = negative+urgent, complaint+urgent, damage, legal. ORANGE = negative+high, urgent+non-negative, time-critical sleeping revenue. YELLOW = high+neutral, normal+negative, any new lead. GREEN = positive/neutral + normal/low."
},
{
"name": "owner_alert",
"required": true,
"description": "Must be exactly one of: true | false. 'true' ONLY if sla_status RED, legal action, physical damage, or project >200sqm."
},
{
"name": "response_deadline",
"required": true,
"description": "Must be exactly one of: 1h | 4h | 24h | 48h. RED=1h, ORANGE=4h, YELLOW=24h, GREEN=48h."
},
{
"name": "is_noise",
"required": true,
"description": "Channel Contamination filter. Must be exactly one of: true | false. 'true' for auto-replies, spam, newsletters, system notifications. 'false' for all real client communication."
}
]
}
},
"typeVersion": 1.2
},
{
"id": "d20f4d4b-ffcc-4d23-9a93-51dacc3cbe3b",
"name": "Filter Spam Emails",
"type": "n8n-nodes-base.filter",
"position": [
-2288,
1744
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "noise-filter",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.output.is_noise }}",
"rightValue": "true"
}
]
}
},
"typeVersion": 2
},
{
"id": "e7bce726-4b4a-431f-859c-ef99d0dc3d5a",
"name": "Build Email Labels",
"type": "n8n-nodes-base.code",
"position": [
-2064,
1744
],
"parameters": {
"jsCode": "// Build 4 lean labels + merge all data for downstream\n\nconst raw = $input.first().json;\nconst triage = raw.output || raw;\nconst labels = [];\n\n// === 4 LABELS FOR AGENTMAIL ===\nif (triage.sla_status) labels.push(`sla:${triage.sla_status}`);\nif (triage.inquiry_category) labels.push(`pipeline:${triage.inquiry_category}`);\nif (triage.stucco_priority) labels.push(`priority:${triage.stucco_priority}`);\nif (triage.sentiment) labels.push(`sentiment:${triage.sentiment}`);\n\n// === GET EMAIL DATA (loop-safe) ===\nlet emailData;\ntry {\n emailData = $('Extract Email Content').item.json;\n} catch (e) {\n emailData = $('Extract Email Content').first().json;\n}\n\nreturn [{\n json: {\n labels: labels,\n message_id: emailData.message_id,\n inbox_id: emailData.inbox_id,\n thread_id: emailData.thread_id,\n event_id: emailData.event_id,\n sla_status: triage.sla_status,\n owner_alert: triage.owner_alert,\n helen_action: triage.helen_action,\n response_deadline: triage.response_deadline,\n stucco_priority: triage.stucco_priority,\n inquiry_category: triage.inquiry_category,\n sentiment: triage.sentiment,\n category: triage.category,\n department: triage.department,\n property_type: triage.property_type,\n technical_specs: triage.technical_specs,\n sender_email: emailData.sender_email,\n subject: emailData.subject,\n body: emailData.body,\n received_at: emailData.received_at,\n processed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f18f70fd-577e-4ddf-a6c8-70954a2e023e",
"name": "Route by SLA Category",
"type": "n8n-nodes-base.switch",
"position": [
-1840,
1696
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "3156237d-5f05-4197-bbc0-2469c4e862c6",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.sla_status }}",
"rightValue": "RED"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d355986d-cdd7-441c-b3c2-71a3f2951920",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.sla_status }}",
"rightValue": "ORANGE"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d62b26f2-048b-459c-a0cb-f80170947dfc",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.sla_status }}",
"rightValue": "YELLOW"
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1ac76077-2caf-4a41-9b2c-c6875be95ea4",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.sla_status }}",
"rightValue": "GREEN"
}
]
}
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3.2
},
{
"id": "9a4b28db-5274-46ce-b5fc-3144d055483c",
"name": "Notify Owner Immediately",
"type": "n8n-nodes-base.code",
"position": [
-1616,
1456
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\nconst alertMessage = [\n '\ud83d\udd34 RED ALERT \u2014 StuccoOS',\n '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501',\n '',\n `\ud83d\udce7 From: ${data.sender_email}`,\n `\ud83d\udccb Subject: ${data.subject}`,\n `\u23f0 Respond within: ${data.response_deadline}`,\n `\ud83c\udfd7\ufe0f Pipeline: ${data.inquiry_category}`,\n `\ud83d\udea8 Priority: ${data.stucco_priority}`,\n `\ud83d\udcac Sentiment: ${data.sentiment}`,\n `\ud83d\udcc1 Category: ${data.category}`,\n '',\n `\ud83d\udcdd ${data.body.substring(0, 200)}...`,\n '',\n '\u26a0\ufe0f IMMEDIATE owner attention required.',\n `\ud83d\udd17 Thread: ${data.thread_id}`,\n].join('\\n');\n\nreturn [{\n json: {\n alert_message: alertMessage,\n alert_type: 'RED',\n channel: 'owner-direct',\n ...data\n }\n}];"
},
"typeVersion": 2
},
{
"id": "06df9406-530a-4b66-95d3-24a5d3308463",
"name": "Create Urgent Task",
"type": "n8n-nodes-base.code",
"position": [
-1616,
1648
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\nreturn [{\n json: {\n task_title: `\ud83d\udfe0 [${data.inquiry_category}] ${data.subject}`,\n task_description: `From: ${data.sender_email}\\nDeadline: ${data.response_deadline}\\nSentiment: ${data.sentiment}\\nCategory: ${data.category}\\nHelen Draft: ${data.helen_action}\\n\\n${data.body.substring(0, 300)}`,\n task_priority: 'high',\n task_due: new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString(),\n ...data\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a8196e70-a1ef-4418-8b49-1a78e2e02dea",
"name": "Create Standard Task",
"type": "n8n-nodes-base.code",
"position": [
-1616,
1840
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\nreturn [{\n json: {\n task_title: `\ud83d\udfe1 [${data.inquiry_category}] ${data.subject}`,\n task_description: `From: ${data.sender_email}\\nDeadline: ${data.response_deadline}\\nSentiment: ${data.sentiment}\\nCategory: ${data.category}\\nHelen Draft: ${data.helen_action}\\n\\n${data.body.substring(0, 300)}`,\n task_priority: 'normal',\n task_due: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),\n ...data\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f1b83d9a-143a-47e0-8cff-1875ab6d8fd4",
"name": "Log Email and Wait 48h",
"type": "n8n-nodes-base.code",
"position": [
-1616,
2032
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\nreturn [{\n json: {\n status: 'logged',\n note: `No action needed. ${data.labels.length} labels applied. 48h timer started.`,\n next_check: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),\n ...data\n }\n}];"
},
"typeVersion": 2
},
{
"id": "fbd36509-2347-448c-b81f-bb6e9fd276c8",
"name": "Chat Model Integration",
"type": "@n8n/n8n-nodes-langchain.lmChatGroq",
"position": [
-2576,
1968
],
"parameters": {
"model": "openai/gpt-oss-120b",
"options": {}
},
"credentials": {
"groqApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "49c50c76-c418-4593-b323-963cd6195b8c",
"name": "Wait 30 Seconds",
"type": "n8n-nodes-base.wait",
"position": [
224,
1920
],
"parameters": {
"amount": 30
},
"typeVersion": 1.1
},
{
"id": "040e474a-3317-4340-b930-db5294ff762b",
"name": "Search CRM for Contact",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1120,
1696
],
"parameters": {
"url": "=https://www.ninjapipe.app/api/contacts?search={{ encodeURIComponent($json.search_email) }}\n",
"options": {
"timeout": 10000,
"response": {
"response": {
"neverError": true
}
}
},
"sendHeaders": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "f29e4f50-29f9-4359-b975-be8f5ea739fb",
"name": "Check Contact Existence",
"type": "n8n-nodes-base.if",
"position": [
-896,
1696
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "contact-found",
"operator": {
"type": "string",
"operation": "exists",
"singleValue": true
},
"leftValue": "={{ $json.data[0].id }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "b487f3fc-2bd2-42a0-8cfb-653a6c38edaf",
"name": "Utilize Existing Contact",
"type": "n8n-nodes-base.code",
"position": [
-448,
1600
],
"parameters": {
"jsCode": "// Contact FOUND \u2014 grab contact_id from data[0]\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const apiResponse = item.json;\n const contact = apiResponse.data[0];\n\n let triageData;\n try {\n triageData = $('Select CRM List for SLA').item.json;\n } catch (e) {\n triageData = $('Select CRM List for SLA').first().json;\n }\n\n results.push({\n json: {\n ...triageData,\n contact_id: contact.id,\n contact_status: 'existing',\n contact_crm_name: `${contact.first_name || ''} ${contact.last_name || ''}`.trim(),\n contact_crm_email: contact.email,\n contact_crm_status: contact.status\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "95974ebe-5bf4-48da-bd6c-ddae44de0b2d",
"name": "Add New CRM Contact",
"type": "n8n-nodes-base.httpRequest",
"position": [
-672,
2032
],
"parameters": {
"url": "https://www.ninjapipe.app/api/contacts",
"method": "POST",
"options": {
"timeout": 10000
},
"jsonBody": "={{ JSON.stringify({\n \"first_name\": $('Select CRM List for SLA').item?.json?.contact_first_name || $('Select CRM List for SLA').first().json.contact_first_name,\n \"last_name\": $('Select CRM List for SLA').item?.json?.contact_last_name || $('Select CRM List for SLA').first().json.contact_last_name,\n \"email\": $('Select CRM List for SLA').item?.json?.search_email || $('Select CRM List for SLA').first().json.search_email,\n \"status\": \"Prospect\",\n \"fields\": {\n \"leadSource\": \"StuccoOS Email Triage\",\n \"category\": $('Select CRM List for SLA').item?.json?.inquiry_category || $('Select CRM List for SLA').first().json.inquiry_category,\n \"industry\": \"Stucwerk\"\n }\n}) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "dc86af69-9dd5-4940-9e40-1696a09020c8",
"name": "Retrieve New Contact ID",
"type": "n8n-nodes-base.code",
"position": [
-448,
2032
],
"parameters": {
"jsCode": "// Grab new contact_id from create response\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const apiResponse = item.json;\n\n const contactId = apiResponse.id\n || apiResponse.data?.id\n || apiResponse.data?.[0]?.id\n || '';\n\n let triageData;\n try {\n triageData = $('Select CRM List for SLA').item.json;\n } catch (e) {\n triageData = $('Select CRM List for SLA').first().json;\n }\n\n results.push({\n json: {\n ...triageData,\n contact_id: contactId,\n contact_status: 'new'\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "859c9975-2e94-4c80-8669-76b253c2c768",
"name": "Add Contact to SLA List",
"type": "n8n-nodes-base.httpRequest",
"position": [
-224,
1696
],
"parameters": {
"url": "=https://www.ninjapipe.app/api/lists/{{ $json.target_list_id }}/members",
"method": "POST",
"options": {
"timeout": 10000,
"response": {
"response": {
"neverError": true
}
}
},
"jsonBody": "={{ JSON.stringify({ \"contact_id\": $json.contact_id }) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpBearerAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "b628ac57-415a-46c2-a0bc-5081e1f763c8",
"name": "Confirm CRM Update",
"type": "n8n-nodes-base.code",
"position": [
0,
1696
],
"parameters": {
"jsCode": "// Final confirmation\n\nconst items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n const data = item.json;\n\n results.push({\n json: {\n status: 'complete',\n summary: `${data.contact_status === 'new' ? '\u2795 Created' : '\u2705 Found'} contact \u2192 Added to ${data.sla_status} list`,\n contact_id: data.contact_id,\n contact_status: data.contact_status,\n sla_status: data.sla_status,\n sender_email: data.sender_email,\n subject: data.subject\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "542bf278-b9e5-46eb-aaa6-da1ce22306af",
"name": "Select CRM List for SLA",
"type": "n8n-nodes-base.code",
"position": [
-1344,
1696
],
"parameters": {
"jsCode": "// Map SLA status to NinjaPipe List ID\n\nconst items = $input.all();\nconst results = [];\n\nconst listMap = {\n 'RED': 'REPLACE_WITH_RED_LIST_UUID',\n 'ORANGE': 'REPLACE_WITH_ORANGE_LIST_UUID',\n 'YELLOW': 'REPLACE_WITH_YELLOW_LIST_UUID',\n 'GREEN': 'REPLACE_WITH_GREEN_LIST_UUID'\n};\n\nfor (const item of items) {\n const data = item.json;\n\n let contactName = data.sender_email || 'Unknown';\n const rawFrom = data.raw_from || data.sender_email || '';\n if (rawFrom.includes('<')) {\n contactName = rawFrom.split('<')[0].trim() || contactName;\n }\n\n // Split name into first/last\n const nameParts = contactName.split(' ');\n const firstName = nameParts[0] || 'Unknown';\n const lastName = nameParts.slice(1).join(' ') || '';\n\n results.push({\n json: {\n ...data,\n target_list_id: listMap[data.sla_status] || listMap['GREEN'],\n search_email: data.sender_email,\n contact_name: contactName,\n contact_first_name: firstName,\n contact_last_name: lastName\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
}
],
"connections": {
"Wait 30 Seconds": {
"main": [
[
{
"node": "Loop Over Emails",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Emails": {
"main": [
[],
[
{
"node": "Extract Email Content",
"type": "main",
"index": 0
}
]
]
},
"Build Email Labels": {
"main": [
[
{
"node": "Route by SLA Category",
"type": "main",
"index": 0
}
]
]
},
"Confirm CRM Update": {
"main": [
[
{
"node": "Wait 30 Seconds",
"type": "main",
"index": 0
}
]
]
},
"Create Urgent Task": {
"main": [
[
{
"node": "Select CRM List for SLA",
"type": "main",
"index": 0
}
]
]
},
"Filter Sent Emails": {
"main": [
[
{
"node": "Loop Over Emails",
"type": "main",
"index": 0
}
]
]
},
"Filter Spam Emails": {
"main": [
[
{
"node": "Build Email Labels",
"type": "main",
"index": 0
}
]
]
},
"When Email Arrives": {
"main": [
[
{
"node": "Filter Sent Emails",
"type": "main",
"index": 0
}
]
]
},
"AI Email Classifier": {
"main": [
[
{
"node": "Filter Spam Emails",
"type": "main",
"index": 0
}
]
]
},
"Add New CRM Contact": {
"main": [
[
{
"node": "Retrieve New Contact ID",
"type": "main",
"index": 0
}
]
]
},
"Create Standard Task": {
"main": [
[
{
"node": "Select CRM List for SLA",
"type": "main",
"index": 0
}
]
]
},
"Extract Email Content": {
"main": [
[
{
"node": "AI Email Classifier",
"type": "main",
"index": 0
}
]
]
},
"Route by SLA Category": {
"main": [
[
{
"node": "Notify Owner Immediately",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Urgent Task",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Standard Task",
"type": "main",
"index": 0
}
],
[
{
"node": "Log Email and Wait 48h",
"type": "main",
"index": 0
}
]
]
},
"Chat Model Integration": {
"ai_languageModel": [
[
{
"node": "AI Email Classifier",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Log Email and Wait 48h": {
"main": [
[
{
"node": "Select CRM List for SLA",
"type": "main",
"index": 0
}
]
]
},
"Search CRM for Contact": {
"main": [
[
{
"node": "Check Contact Existence",
"type": "main",
"index": 0
}
]
]
},
"Add Contact to SLA List": {
"main": [
[
{
"node": "Confirm CRM Update",
"type": "main",
"index": 0
}
]
]
},
"Check Contact Existence": {
"main": [
[
{
"node": "Utilize Existing Contact",
"type": "main",
"index": 0
}
],
[
{
"node": "Add New CRM Contact",
"type": "main",
"index": 0
}
]
]
},
"Retrieve New Contact ID": {
"main": [
[
{
"node": "Add Contact to SLA List",
"type": "main",
"index": 0
}
]
]
},
"Select CRM List for SLA": {
"main": [
[
{
"node": "Search CRM for Contact",
"type": "main",
"index": 0
}
]
]
},
"Notify Owner Immediately": {
"main": [
[
{
"node": "Select CRM List for SLA",
"type": "main",
"index": 0
}
]
]
},
"Utilize Existing Contact": {
"main": [
[
{
"node": "Add Contact to SLA List",
"type": "main",
"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.
groqApihttpBearerAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This is built for the plastering and stucco owners who are out on the tools while the partner (the 'Patricia' of the business) tries to keep the admin from exploding. It's for anyone losing money because high-value quotes or urgent fires are buried under newsletters and spam.
Source: https://n8n.io/workflows/15248/ — 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.
Automatically qualify, score, and route inbound B2B leads using GPT-4o-mini — no manual review needed.
This workflow uses KlickTipp community nodes, available for self-hosted n8n instances only.
You know the businesses that need your services, but finding them is the hard part. They have 150+ five-star reviews, customers raving about specific services, and zero way to book online. They exist,
Automatically identify and qualify ideal customer prospects from LinkedIn post reactions using AI-powered profile analysis and intelligent data enrichment.
Stop manually digging through endless Google Ads search term reports! 📊 This workflow puts your brand campaign analysis on autopilot, acting as an AI-powered performance marketer that works for you 24