This workflow follows the Agent → Chainllm 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 →
{
"name": "AI Customer Service Automation - Portfolio",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "Freshchat-agent",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-768,
768
],
"id": "cf563fc0-d544-47e8-a9ed-625f5a0ad779",
"name": "Webhook"
},
{
"parameters": {
"jsCode": "const items = $input.all();\n\n// --------------------------------------------------\n// KNOWN AUTOMATION / BOT ACTOR IDs\n// --------------------------------------------------\n// \u0647\u0630\u0647 IDs \u0644\u0628\u0648\u062a\u0627\u062a / \u0631\u0633\u0627\u0626\u0644 \u0622\u0644\u064a\u0629 \u0645\u0639\u0631\u0648\u0641\u0629 \u0639\u0646\u062f\u0646\u0627.\n// \u0645\u0645\u0646\u0648\u0639 \u0623\u064a \u0648\u0627\u062d\u062f \u0645\u0646\u0647\u0645 \u064a\u0648\u0642\u0641 \u0627\u0644\u0640 AI.\nconst AUTOMATION_ACTOR_IDS = [\n 'AUTOMATION_ACTOR_ID_1',\n 'AUTOMATION_ACTOR_ID_2',\n];\n\nreturn items.map((item) => {\n const body = item.json.body ?? item.json;\n\n const action = body.action ?? '';\n const actorType = body.actor?.actor_type ?? '';\n const actorId = body.actor?.actor_id ?? '';\n\n let eventType = 'ignore';\n let conversation = null;\n let message = null;\n let user = null;\n\n // --------------------------------------------------\n // 1) MESSAGE CREATED\n // --------------------------------------------------\n if (action === 'message_create') {\n message = body.data?.message ?? {};\n\n const messageActorId =\n message?.actor_id ??\n actorId ??\n '';\n\n const orgActorId =\n message?.org_actor_id ??\n null;\n\n // -----------------------------\n // Customer message\n // -----------------------------\n if (actorType === 'user') {\n eventType = 'customer_message';\n\n // -----------------------------\n // Agent / Bot / AI message\n // -----------------------------\n } else if (actorType === 'agent') {\n\n const isKnownAutomation =\n AUTOMATION_ACTOR_IDS.includes(messageActorId);\n\n // \u0627\u0644\u0645\u0648\u0638\u0641 \u0627\u0644\u0628\u0634\u0631\u064a \u0627\u0644\u062d\u0642\u064a\u0642\u064a \u064a\u0638\u0647\u0631 \u0645\u0639\u0647 org_actor_id\n const isHumanAgent =\n Boolean(orgActorId) &&\n !isKnownAutomation;\n\n if (isKnownAutomation) {\n // \u0628\u0648\u062a Freshchat / Freshdesk \u0623\u0648 AI/n8n\n eventType = 'automated_agent_message';\n\n } else if (isHumanAgent) {\n // \u0645\u0648\u0638\u0641 \u0628\u0634\u0631\u064a \u062d\u0642\u064a\u0642\u064a\n eventType = 'human_agent_message';\n\n } else {\n // Agent \u063a\u064a\u0631 \u0645\u0639\u0631\u0648\u0641 \u0648\u0645\u0627 \u0639\u0646\u062f\u0647 org_actor_id.\n // \u0646\u0639\u0627\u0645\u0644\u0647 \u0643\u0631\u0633\u0627\u0644\u0629 \u0622\u0644\u064a\u0629 \u062d\u062a\u0649 \u0644\u0627 \u0646\u0648\u0642\u0641 \u0627\u0644\u0640 AI \u0628\u0627\u0644\u063a\u0644\u0637\n // \u0625\u0630\u0627 \u0638\u0647\u0631 \u0628\u0648\u062a \u062c\u062f\u064a\u062f \u0645\u0633\u062a\u0642\u0628\u0644\u064b\u0627.\n eventType = 'automated_agent_message';\n }\n\n } else {\n eventType = 'unknown_message';\n }\n\n // --------------------------------------------------\n // 2) ASSIGN TO ME / CONVERSATION ASSIGNMENT\n // --------------------------------------------------\n } else if (action === 'conversation_assignment') {\n conversation =\n body.data?.assignment?.conversation ??\n {};\n\n const assignorId =\n body.data?.assignment?.assignor_id ??\n actorId ??\n '';\n\n // \u0625\u0630\u0627 assignment \u062c\u0627\u0621 \u0645\u0646 automation \u0645\u0639\u0631\u0648\u0641 \u0644\u0627 \u0646\u0648\u0642\u0641 AI.\n // \u063a\u064a\u0631 \u0630\u0644\u0643 \u0646\u0639\u062a\u0628\u0631\u0647 \u062a\u062f\u062e\u0644 \u0645\u0648\u0638\u0641 \u0628\u0634\u0631\u064a.\n if (AUTOMATION_ACTOR_IDS.includes(assignorId)) {\n eventType = 'automated_assignment';\n } else {\n eventType = 'agent_assignment';\n }\n\n // --------------------------------------------------\n // 3) RESOLVE\n // --------------------------------------------------\n } else if (action === 'conversation_resolution') {\n conversation =\n body.data?.resolve?.conversation ??\n {};\n\n user =\n body.data?.resolve?.user ??\n {};\n\n eventType = 'conversation_resolved';\n\n // --------------------------------------------------\n // 4) REOPEN\n // --------------------------------------------------\n } else if (action === 'conversation_reopen') {\n conversation =\n body.data?.reopen?.conversation ??\n {};\n\n const reopenerId =\n body.data?.reopen?.reopener_id ??\n actorId ??\n '';\n\n if (actorType === 'user') {\n eventType = 'customer_reopen';\n\n } else if (actorType === 'agent') {\n\n if (AUTOMATION_ACTOR_IDS.includes(reopenerId)) {\n eventType = 'automated_reopen';\n } else {\n eventType = 'agent_reopen';\n }\n\n } else {\n eventType = 'unknown_reopen';\n }\n }\n\n // --------------------------------------------------\n // Conversation ID\n // --------------------------------------------------\n const conversationId =\n message?.conversation_id ??\n conversation?.conversation_id ??\n null;\n\n // --------------------------------------------------\n // Message Parts\n // --------------------------------------------------\n const messageParts =\n message?.message_parts ??\n [];\n\n const messageText =\n messageParts\n .map((part) => part.text?.content)\n .filter(Boolean)\n .join('\\n') || null;\n\n // --------------------------------------------------\n // Images\n // --------------------------------------------------\n const imageUrls =\n messageParts\n .map((part) => part.image?.url)\n .filter(Boolean);\n\n // --------------------------------------------------\n // AI ACTION\n // --------------------------------------------------\n let aiAction = 'ignore';\n\n // \u0631\u0633\u0627\u0644\u0629 \u0627\u0644\u0639\u0645\u064a\u0644:\n // \u0627\u0641\u062d\u0635 \u0647\u0644 AI \u0634\u063a\u0627\u0644 \u0623\u0648 \u0645\u0648\u0642\u0648\u0641\n if (eventType === 'customer_message') {\n aiAction = 'check_if_ai_active';\n }\n\n // \u0641\u0642\u0637 \u062a\u062f\u062e\u0644 \u0628\u0634\u0631\u064a \u062d\u0642\u064a\u0642\u064a \u064a\u0648\u0642\u0641 AI\n if (\n eventType === 'human_agent_message' ||\n eventType === 'agent_assignment' ||\n eventType === 'agent_reopen'\n ) {\n aiAction = 'pause_ai';\n }\n\n // Resolve \u064a\u0631\u062c\u0639 AI \u0644\u0644\u0639\u0645\u0644\n if (\n eventType === 'conversation_resolved' ||\n eventType === 'customer_reopen'\n ) {\n aiAction = 'activate_ai';\n }\n\n // \u0647\u0630\u0647 \u0627\u0644\u062d\u0627\u0644\u0627\u062a \u0643\u0644\u0647\u0627 \u062a\u0628\u0642\u0649 ignore:\n //\n // automated_agent_message\n // automated_assignment\n // automated_reopen\n //\n // \u064a\u0639\u0646\u064a \u0627\u0644\u0628\u0648\u062a\u0627\u062a \u0648\u0631\u062f\u0648\u062f AI/n8n\n // \u0645\u0645\u0646\u0648\u0639 \u062a\u063a\u064a\u0631 \u062d\u0627\u0644\u0629 \u0627\u0644\u0640 AI.\n\n // --------------------------------------------------\n // OUTPUT\n // --------------------------------------------------\n return {\n json: {\n request_id: body.request_id ?? null,\n action,\n action_time: body.action_time ?? null,\n\n event_type: eventType,\n ai_action: aiAction,\n\n actor_type: actorType,\n actor_id: actorId,\n\n // \u0645\u0647\u0645 \u0644\u0644\u062a\u0634\u062e\u064a\u0635 \u0648\u0645\u0639\u0631\u0641\u0629 \u0627\u0644\u0628\u0634\u0631\u064a \u0645\u0646 \u0627\u0644\u0622\u0644\u064a\n org_actor_id:\n message?.org_actor_id ??\n conversation?.assigned_org_agent_id ??\n null,\n\n conversation_id: conversationId,\n\n conversation_status:\n conversation?.status ??\n null,\n\n conversation_internal_id:\n conversation?.conversation_internal_id ??\n message?.freshchat_conversation_id ??\n null,\n\n user_id:\n message?.user_id ??\n conversation?.user_id ??\n user?.id ??\n null,\n\n message_id:\n message?.id ??\n null,\n\n message_type:\n message?.message_type ??\n null,\n\n message_text: messageText,\n message_parts: messageParts,\n\n image_urls: imageUrls,\n image_count: imageUrls.length,\n has_images: imageUrls.length > 0,\n\n channel_id:\n message?.channel_id ??\n conversation?.channel_id ??\n null,\n\n assigned_agent_id:\n conversation?.assigned_agent_id ??\n body.data?.assignment?.to_agent_id ??\n null,\n\n customer_phone:\n user?.phone ??\n null,\n\n customer_name:\n user?.first_name ??\n null,\n\n original_event: body,\n },\n };\n});"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-528,
768
],
"id": "45effbd4-f2c2-42bf-af7e-ed734b023857",
"name": "Normalize Freshchat Event"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "f786292c-5b88-45bf-a56e-ad057bf244d0",
"leftValue": "={{ $json.ai_action }}",
"rightValue": "pause_ai",
"operator": {
"type": "string",
"operation": "regex"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Pause AI"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "83b59d66-c455-4619-9d51-d269d22ebde3",
"leftValue": "={{ $json.ai_action }}",
"rightValue": "activate_ai",
"operator": {
"type": "string",
"operation": "regex"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Activate AI"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.ai_action }}",
"rightValue": "check_if_ai_active",
"operator": {
"type": "string",
"operation": "regex"
},
"id": "66c61d1d-44fb-4cf9-8065-99f42496af12"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Customer Message"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
-720,
992
],
"id": "a19651e3-d284-4ccd-80fe-c6d7fdb87c77",
"name": "Route Freshchat Event"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Customer Service AI System",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Conversation States",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"conversation_id": "={{ $json.conversation_id }}",
"conversation_internal_id": "={{ String($json.conversation_internal_id) }}",
"user_id": "={{ $json.user_id }}",
"ai_status": "paused",
"last_event": "={{ $json.event_type }}",
"updated_at": "={{ DateTime.fromISO($json.action_time).setZone('Asia/Riyadh').toFormat('yyyy-MM-dd HH:mm:ss') }}"
},
"matchingColumns": [
"conversation_id"
],
"schema": [
{
"id": "conversation_id",
"displayName": "conversation_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "conversation_internal_id",
"displayName": "conversation_internal_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "user_id",
"displayName": "user_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ai_status",
"displayName": "ai_status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "last_event",
"displayName": "last_event",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "updated_at",
"displayName": "updated_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
672,
992
],
"id": "2cae8d75-4594-4a02-9f88-768adb49d9ea",
"name": "Save Paused State",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Customer Service AI System",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Conversation States",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"conversation_id": "={{ $json.conversation_id }}",
"conversation_internal_id": "={{ String($json.conversation_internal_id) }}",
"user_id": "={{ $json.user_id }}",
"ai_status": "active",
"last_event": "={{ $json.event_type }}",
"updated_at": "={{ DateTime.fromISO($json.action_time).setZone('Asia/Riyadh').toFormat('yyyy-MM-dd HH:mm:ss') }}"
},
"matchingColumns": [
"conversation_id"
],
"schema": [
{
"id": "conversation_id",
"displayName": "conversation_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "conversation_internal_id",
"displayName": "conversation_internal_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "user_id",
"displayName": "user_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ai_status",
"displayName": "ai_status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "last_event",
"displayName": "last_event",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "updated_at",
"displayName": "updated_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {
"cellFormat": "RAW"
}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
960,
992
],
"id": "fefa7ebc-970e-4a68-a013-849e6f9617a3",
"name": "Save Active State",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Customer Service AI System",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Conversation States",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID"
},
"filtersUI": {
"values": [
{
"lookupColumn": "conversation_id",
"lookupValue": "={{ $json.conversation_id }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-448,
1040
],
"id": "c477de46-b18a-4d32-bb24-1d2f2339c38c",
"name": "Get Conversation State",
"alwaysOutputData": true,
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "9f2604bd-7463-4d7b-a97a-f51626dabf0b",
"leftValue": "={{ $json.ai_status ?? 'active' }}",
"rightValue": "active",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-224,
1040
],
"id": "a3f51ff9-4ec6-4fec-8001-6a51028f1dd5",
"name": "Is AI Active?"
},
{
"parameters": {
"url": "=https://YOUR_FRESHCHAT_DOMAIN.freshchat.com/v2/users/{{ $('Normalize Freshchat Event').item.json.user_id }}",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
-16,
1024
],
"id": "dcbd77b6-11ce-41b7-9e56-5f8c6034c4fa",
"name": "Get Freshchat Customer",
"credentials": {
"httpBasicAuth": {
"name": "<your credential>"
},
"httpBearerAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "62dabe01-07b4-49ba-ab38-ddc12fbd36d4",
"name": "customer_phone",
"value": "={{ $json.phone }}",
"type": "string"
},
{
"id": "be61252e-dec9-4ae5-b0af-d3b8515e16b8",
"name": "customer_message",
"value": "={{ $('Normalize Freshchat Event').item.json.message_text }}",
"type": "string"
},
{
"id": "95bfc432-4333-4a60-9853-01e5196a7a73",
"name": "conversation_id",
"value": "={{ $('Normalize Freshchat Event').item.json.conversation_id }}",
"type": "string"
},
{
"id": "611e5382-ef32-49ae-a334-d320eb594a1a",
"name": "conversation_internal_id",
"value": "={{ String($('Normalize Freshchat Event').item.json.conversation_internal_id) }}",
"type": "string"
},
{
"id": "91b2e54d-080a-4306-a7b3-40e98cce32c8",
"name": "user_id",
"value": "={{ $('Normalize Freshchat Event').item.json.user_id }}",
"type": "string"
},
{
"id": "93e1de4b-9810-4827-aa24-b4d7616582f2",
"name": "image_urls",
"value": "={{ $('Normalize Freshchat Event').item.json.image_urls ?? [] }}",
"type": "array"
},
{
"id": "c7561f8c-9b43-44d0-ba49-06ab1270abf6",
"name": "message_id",
"value": "={{ $('Normalize Freshchat Event').item.json.message_id }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
208,
1024
],
"id": "8238386a-73ca-48b8-9658-7f0656edf107",
"name": "Build Customer Context"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "0156eebf-91dc-4026-8213-7d21280b6f47",
"leftValue": "={{ Array.isArray($json.image_urls) && $json.image_urls.length > 0 }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-720,
1264
],
"id": "0d43318e-42f9-468b-9774-0f32d459ac62",
"name": "Has Images?"
},
{
"parameters": {
"amount": 6
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
-224,
1248
],
"id": "ca4c4810-64ba-4547-afe3-2990c933d24f",
"name": "Wait for More Images"
},
{
"parameters": {
"jsCode": "const rows = $input.all().map(item => item.json);\n\nif (rows.length === 0) {\n return [];\n}\n\n// \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0627\u0644\u062a\u064a \u0634\u063a\u0651\u0644\u062a \u0647\u0630\u0627 \u0627\u0644\u0640 Execution\nconst currentMessageId = String(\n $('Build Customer Context').first().json.message_id ?? ''\n);\n\n// --------------------------------------------------\n// \u062a\u062d\u0648\u064a\u0644 \u0648\u0642\u062a Supabase \u0625\u0644\u0649 timestamp\n// received_at \u0645\u062b\u0627\u0644:\n// 2026-08-09T11:02:45.830+00:00\n// --------------------------------------------------\nfunction parseTime(value) {\n if (!value) return null;\n\n const time = Date.parse(String(value));\n return Number.isFinite(time) ? time : null;\n}\n\n// --------------------------------------------------\n// \u062a\u062d\u062f\u064a\u062f \u0623\u062d\u062f\u062b \u0635\u0648\u0631\u0629 \u062d\u0633\u0628 received_at\n// \u0648\u0625\u0630\u0627 \u062a\u0633\u0627\u0648\u0649 \u0627\u0644\u0648\u0642\u062a \u0646\u0633\u062a\u062e\u062f\u0645 id\n// --------------------------------------------------\nconst latestRow = rows.reduce((latest, row) => {\n const latestTime = parseTime(latest.received_at);\n const rowTime = parseTime(row.received_at);\n\n if (latestTime === null && rowTime !== null) {\n return row;\n }\n\n if (latestTime !== null && rowTime === null) {\n return latest;\n }\n\n if (latestTime === null && rowTime === null) {\n return Number(row.id) > Number(latest.id)\n ? row\n : latest;\n }\n\n if (rowTime > latestTime) {\n return row;\n }\n\n if (\n rowTime === latestTime &&\n Number(row.id) > Number(latest.id)\n ) {\n return row;\n }\n\n return latest;\n});\n\n// \u0641\u0642\u0637 Execution \u0627\u0644\u062e\u0627\u0635 \u0628\u0623\u062d\u062f\u062b \u0635\u0648\u0631\u0629 \u064a\u0643\u0645\u0644\nif (String(latestRow.message_id) !== currentMessageId) {\n return [];\n}\n\n// --------------------------------------------------\n// \u0646\u0623\u062e\u0630 \u0641\u0642\u0637 \u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0642\u0631\u064a\u0628\u0629 \u0645\u0646 \u0623\u062d\u062f\u062b \u0635\u0648\u0631\u0629\n// \u062d\u062a\u0649 \u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0642\u062f\u064a\u0645\u0629 \u063a\u064a\u0631 \u0627\u0644\u0645\u0639\u0627\u0644\u062c\u0629 \u0644\u0627 \u062a\u062f\u062e\u0644 \u0641\u064a Batch \u062c\u062f\u064a\u062f\n// --------------------------------------------------\nconst latestTime = parseTime(latestRow.received_at);\n\nconst BATCH_WINDOW_MS = 60 * 1000;\n\nconst batchRows = rows.filter(row => {\n const rowTime = parseTime(row.received_at);\n\n if (latestTime === null || rowTime === null) {\n return String(row.message_id) === String(latestRow.message_id);\n }\n\n return (\n latestTime - rowTime >= 0 &&\n latestTime - rowTime <= BATCH_WINDOW_MS\n );\n});\n\n// --------------------------------------------------\n// \u062a\u0631\u062a\u064a\u0628 \u0635\u0641\u0648\u0641 \u0627\u0644\u062f\u0641\u0639\u0629 \u0645\u0646 \u0627\u0644\u0623\u0642\u062f\u0645 \u0625\u0644\u0649 \u0627\u0644\u0623\u062d\u062f\u062b\n// \u062d\u062a\u0649 \u0646\u062c\u0645\u0639 \u0627\u0644\u0646\u0635\u0648\u0635 \u0628\u0627\u0644\u062a\u0631\u062a\u064a\u0628 \u0627\u0644\u0635\u062d\u064a\u062d\n// --------------------------------------------------\nconst sortedBatchRows = [...batchRows].sort((a, b) => {\n const timeA = parseTime(a.received_at);\n const timeB = parseTime(b.received_at);\n\n if (timeA === null && timeB === null) {\n return Number(a.id) - Number(b.id);\n }\n\n if (timeA === null) return -1;\n if (timeB === null) return 1;\n\n if (timeA !== timeB) {\n return timeA - timeB;\n }\n\n return Number(a.id) - Number(b.id);\n});\n\n// --------------------------------------------------\n// \u062a\u062c\u0645\u064a\u0639 \u0631\u0648\u0627\u0628\u0637 \u0627\u0644\u0635\u0648\u0631\n// --------------------------------------------------\nconst imageUrls = [];\n\nfor (const row of sortedBatchRows) {\n let urls = row.image_urls;\n\n if (typeof urls === 'string') {\n try {\n urls = JSON.parse(urls);\n } catch {\n urls = urls ? [urls] : [];\n }\n }\n\n if (Array.isArray(urls)) {\n imageUrls.push(...urls);\n }\n}\n\n// --------------------------------------------------\n// \u062a\u062c\u0645\u064a\u0639 \u0646\u0635\u0648\u0635 \u0646\u0641\u0633 \u0627\u0644\u0640 Batch\n// --------------------------------------------------\nconst textParts = sortedBatchRows\n .map(row => String(row.message_text ?? '').trim())\n .filter(text => text.length > 0);\n\nconst batchMessageText = textParts.join('\\n');\n\nreturn [\n {\n json: {\n conversation_id: latestRow.conversation_id,\n conversation_internal_id: latestRow.conversation_internal_id,\n user_id: latestRow.user_id,\n customer_phone: latestRow.customer_phone,\n\n image_urls: [...new Set(imageUrls)],\n\n batch_message_text: batchMessageText,\n\n buffered_message_ids: sortedBatchRows.map(row => row.message_id),\n\n // \u0628\u062f\u0644 row_number \u062a\u0628\u0639 Google Sheets\n buffer_ids: sortedBatchRows.map(row => row.id)\n }\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
224,
1248
],
"id": "20bded24-0a46-4432-b62b-3fb49ebb3e26",
"name": "Keep Latest Image Batch"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Customer Service AI System",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": 128263397,
"mode": "list",
"cachedResultName": "Issue Drafts",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID"
},
"filtersUI": {
"values": [
{
"lookupColumn": "conversation_id",
"lookupValue": "={{ $json.conversation_id }}"
},
{
"lookupColumn": "status",
"lookupValue": "collecting"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
448,
1248
],
"id": "af7d7a24-e8c0-483b-a33b-1c9011b17dcc",
"name": "Get Collecting Issue",
"alwaysOutputData": true,
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "fe7eac9b-59dc-4967-98a3-341d759a25fc",
"leftValue": "={{ $json.row_number !== undefined }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-720,
1472
],
"id": "173ed7da-71a6-4fd8-a642-e58c1291ce08",
"name": "Collecting Issue Exists?"
},
{
"parameters": {
"jsCode": "const draft = $input.first().json;\nconst newBatch = $('Keep Latest Image Batch').first().json;\n\nfunction toArray(value) {\n if (Array.isArray(value)) return value;\n if (!value) return [];\n\n if (typeof value === 'string') {\n try {\n const parsed = JSON.parse(value);\n return Array.isArray(parsed) ? parsed : [parsed];\n } catch {\n return [value];\n }\n }\n\n return [value];\n}\n\nconst oldImages = toArray(draft.image_urls);\nconst newImages = toArray(newBatch.image_urls);\n\nconst mergedImages = [...new Set([\n ...oldImages,\n ...newImages\n])];\n\nreturn [{\n json: {\n row_number: draft.row_number,\n issue_id: draft.issue_id,\n image_urls: JSON.stringify(mergedImages)\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-448,
1456
],
"id": "08be4247-3467-4fc2-aeb9-5338b0028a6e",
"name": "Merge New Images Into Draft"
},
{
"parameters": {
"operation": "update",
"documentId": {
"__rl": true,
"value": "YOUR_GOOGLE_SHEET_ID",
"mode": "list",
"cachedResultName": "Customer Service AI System",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
},
"sheetName": {
"__rl": true,
"value": 128263397,
"mode": "list",
"cachedResultName": "Issue Drafts",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=YOUR_SHEET_GID"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"image_urls": "={{ $json.image_urls }}",
"updated_at": "={{ $now.setZone('Asia/Riyadh').toFormat('yyyy-MM-dd HH:mm:ss') }}",
"issue_id": "={{ $json.issue_id }}"
},
"matchingColumns": [
"issue_id"
],
"schema": [
{
"id": "issue_id",
"displayName": "issue_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "conversation_id",
"displayName": "conversation_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "conversation_internal_id",
"displayName": "conversation_internal_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "user_id",
"displayName": "user_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "customer_phone",
"displayName": "customer_phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "order_number",
"displayName": "order_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "order_phone",
"displayName": "order_phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "issue_description",
"displayName": "issue_description",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "image_urls",
"displayName": "image_urls",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "status",
"displayName": "status",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "is_active",
"displayName": "is_active",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "ticket_id",
"displayName": "ticket_id",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "created_at",
"displayName": "created_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "updated_at",
"displayName": "updated_at",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "ticket_type\n",
"displayName": "ticket_type\n",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "level_1\n",
"displayName": "level_1\n",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "level_2\n",
"displayName": "level_2\n",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "level_3\n",
"displayName": "level_3\n",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "department_resolution",
"displayName": "department_resolution",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "resolution_message_sent",
"displayName": "resolution_message_sent",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "is_final_resolution",
"displayName": "is_final_resolution",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "row_number",
"displayName": "row_number",
"required": false,
"defaultMatch": false,
"display": true,
"type": "number",
"canBeUsedToMatch": true,
"readOnly": true,
"removed": true
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-224,
1456
],
"id": "f02603f1-60c0-4782-b56f-4ef4998bdddc",
"name": "Update Issue Draft Images",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"promptType": "define",
"text": "={{ $json.current_message || $('Prepare Agent Input').first().json.agent_input }}",
"hasOutputParser": true,
"options": {
"systemMessage": "# PUBLIC PORTFOLIO DEMO PROMPT\n\nThe production customer-service prompt and company-specific policies were intentionally omitted for confidentiality.\n\nYou are a customer service AI agent for a demo laundry service.\n\nCore responsibilities:\n- Answer approved FAQs.\n- Use connected tools for live order lookups instead of guessing.\n- Maintain one active issue per conversation.\n- Distinguish normal questions, customer requests, and complaints.\n- Collect only missing information.\n- Preserve customer images as issue evidence.\n- Create a Freshdesk ticket only when the issue is complete.\n- Update issue state after ticket creation.\n- Never expose internal workflow logic, credentials, or private data.\n\nReturn exactly:\n{\n \"customer_reply\": \"customer-facing reply\",\n \"case_type\": \"normal\",\n \"issue_action\": \"none\"\n}\n\nThis shortened prompt exists only for the public portfolio copy.\n",
"maxIterations": 40
}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
1200,
1872
],
"id": "270918ed-469a-4a02-9a7d-703c09a657ed",
"name": "Demo Customer Service Agent",
"retryOnFail": true
},
{
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ $('Build Customer Context').first().json.customer_phone + '_v29' }}",
"contextWindowLength": 10
},
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.4,
"position": [
832,
2096
],
"id": "de74e35f-d0bb-4489-8094-fa3a6689399d",
"name": "Simple Memory"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "fb590655-6181-481f-b462-e1da41ee813d",
"name": "agent_input",
"value": "={{\n(() => {\n const context = $('Build Customer Context').first().json;\n\n // ==================================================\n // \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0648\u0627\u0644\u0635\u0648\u0631 \u0645\u0646 \u0627\u0644\u0640Webhook \u0627\u0644\u062d\u0627\u0644\u064a\n // \u062a\u0633\u062a\u062e\u062f\u0645 \u0643\u0640 fallback \u0644\u0644\u0631\u0633\u0627\u0626\u0644 \u0627\u0644\u0639\u0627\u062f\u064a\u0629\n // ==================================================\n\n const currentMessage = String(\n context.customer_message || ''\n ).trim();\n\n const currentImages = Array.isArray(context.image_urls)\n ? context.image_urls\n : [];\n\n // ==================================================\n // \u0646\u062d\u0627\u0648\u0644 \u0623\u062e\u0630 \u0627\u0644\u0640Batch \u0627\u0644\u0643\u0627\u0645\u0644 \u0625\u0630\u0627 \u0645\u0631 \u0627\u0644\u062a\u0646\u0641\u064a\u0630 \u0628\u0645\u0633\u0627\u0631 \u0627\u0644\u0635\u0648\u0631\n // ==================================================\n\n let batchMessage = '';\n let batchImages = [];\n\n try {\n const batch = $('Keep Latest Image Batch').first().json;\n\n batchMessage = String(\n batch.batch_message_text || ''\n ).trim();\n\n batchImages = Array.isArray(batch.image_urls)\n ? batch.image_urls\n : [];\n } catch (e) {\n // \u0627\u0644\u0631\u0633\u0627\u0644\u0629 \u0644\u0645 \u062a\u0645\u0631 \u0628\u0645\u0633\u0627\u0631 \u0627\u0644\u0635\u0648\u0631\n }\n\n // \u0625\u0630\u0627 \u0643\u0627\u0646 \u0639\u0646\u062f\u0646\u0627 Batch \u0635\u0648\u0631 \u0646\u0633\u062a\u062e\u062f\u0645 \u0628\u064a\u0627\u0646\u0627\u062a\u0647.\n // \u0648\u0625\u0630\u0627 \u0643\u0627\u0646\u062a \u0631\u0633\u0627\u0644\u0629 \u0639\u0627\u062f\u064a\u0629 \u0646\u0633\u062a\u062e\u062f\u0645 Build Customer Context.\n const message = batchMessage || currentMessage;\n\n const images = batchImages.length > 0\n ? batchImages\n : currentImages;\n\n // ==================================================\n // \u0647\u0644 \u064a\u0648\u062c\u062f Issue Draft \u062d\u0627\u0644\u064a \u0628\u062d\u0627\u0644\u0629 collecting\u061f\n // ==================================================\n\n let hasCollectingIssue = false;\n\n try {\n const draft = $('Get Collecting Issue').first().json;\n\n hasCollectingIssue =\n draft &&\n draft.row_number !== undefined;\n } catch (e) {\n hasCollectingIssue = false;\n }\n\n // ==================================================\n // \u064a\u0648\u062c\u062f Issue Draft \u062d\u0642\u064a\u0642\u064a \u0645\u0641\u062a\u0648\u062d\n // ==================================================\n\n if (hasCollectingIssue) {\n\n if (message && images.length) {\n return (\n message +\n '\\n\\n[\u0627\u0644\u0639\u0645\u064a\u0644 \u0623\u0631\u0633\u0644 \u0635\u0648\u0631\u0629 \u0623\u0648 \u0623\u0643\u062b\u0631\u060c \u0648\u062a\u0645\u062a \u0625\u0636\u0627\u0641\u0629 \u0627\u0644\u0635\u0648\u0631 \u0625\u0644\u0649 \u0645\u0633\u0648\u062f\u0629 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629. \u0644\u0627 \u062a\u0637\u0644\u0628 \u0645\u0646\u0647 \u0625\u0639\u0627\u062f\u0629 \u0625\u0631\u0633\u0627\u0644\u0647\u0627\u060c \u0648\u062a\u0627\u0628\u0639 \u062c\u0645\u0639 \u0627\u0644\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0627\u0644\u0646\u0627\u0642\u0635\u0629 \u0644\u0644\u0645\u0634\u0643\u0644\u0629.]'\n );\n }\n\n if (message) {\n return message;\n }\n\n if (images.length) {\n return '[\u0627\u0644\u0639\u0645\u064a\u0644 \u0623\u0631\u0633\u0644 \u0635\u0648\u0631\u0629 \u0623\u0648 \u0623\u0643\u062b\u0631 \u0648\u062a\u0645\u062a \u0625\u0636\u0627\u0641\u062a\u0647\u0627 \u0625\u0644\u0649 \u0645\u0633\u0648\u062f\u0629 \u0627\u0644\u0645\u0634\u0643\u0644\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629. \u0644\u0627 \u062a\u0637\u0644\u0628 \u0645\u0646\u0647 \u0625\u0639\u0627\u062f\u0629 \u0625\u0631\u0633\u0627\u0644\u0647\u0627\u060c \u0648\u062a\u0627\u0628\u0639 \u062c\u0645\u0639 \u0627\u0644\u0645\u0639\u0644\u0648\u0645\u0627\u062a \u0627\u0644\u0646\u0627\u0642\u0635\u0629 \u0644\u0644\u0645\u0634\u0643\u0644\u0629.]';\n }\n }\n\n // ==================================================\n // \u0644\u0627 \u064a\u0648\u062c\u062f Issue Draft\n // ==================================================\n\n if (message && images.length) {\n return (\n message +\n '\\n\\n[\u0627\u0644\u0639\u0645\u064a\u0644 \u0623\u0631\u0633\u0644 \u0635\u0648\u0631\u0629 \u0623\u0648 \u0623\u0643\u062b\u0631 \u0645\u0639 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629. \u0644\u0627 \u062a\u0641\u062a\u0631\u0636 \u0623\u0646 \u0627\u0644\u0635\u0648\u0631\u0629 \u0634\u0643\u0648\u0649 \u0623\u0648 \u0645\u0634\u0643\u0644\u0629 \u0645\u0646 \u062a\u0644\u0642\u0627\u0621 \u0646\u0641\u0633\u0647\u0627. \u0627\u0641\u0647\u0645 \u0627\u0644\u063a\u0631\u0636 \u0645\u0646 \u0643\u0644\u0627\u0645 \u0627\u0644\u0639\u0645\u064a\u0644 \u0648\u0633\u064a\u0627\u0642 \u0627\u0644\u0645\u062d\u0627\u062f\u062b\u0629. \u0644\u0627 \u062a\u0637\u0644\u0628 \u0645\u0646\u0647 \u0625\u0639\u0627\u062f\u0629 \u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0635\u0648\u0631\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629.]'\n );\n }\n\n if (message) {\n return message;\n }\n\n if (images.length) {\n return '[\u0627\u0644\u0639\u0645\u064a\u0644 \u0623\u0631\u0633\u0644 \u0635\u0648\u0631\u0629 \u0623\u0648 \u0623\u0643\u062b\u0631 \u0628\u062f\u0648\u0646 \u0646\u0635. \u0644\u0627 \u062a\u0641\u062a\u0631\u0636 \u0623\u0646\u0647\u0627 \u0634\u0643\u0648\u0649 \u0623\u0648 \u0645\u0634\u0643\u0644\u0629. \u0625\u0630\u0627 \u0644\u0645 \u064a\u0643\u0646 \u0627\u0644\u063a\u0631\u0636 \u0648\u0627\u0636\u062d\u064b\u0627\u060c \u0627\u0633\u0623\u0644 \u0627\u0644\u0639\u0645\u064a\u0644 \u0628\u0627\u062e\u062a\u0635\u0627\u0631 \u0645\u0627\u0630\u0627 \u064a\u0631\u064a\u062f \u0628\u062e\u0635\u0648\u0635 \u0627\u0644\u0635\u0648\u0631\u0629\u060c \u0648\u0644\u0627 \u062a\u0637\u0644\u0628 \u0645\u0646\u0647 \u0625\u0639\u0627\u062f\u0629 \u0625\u0631\u0633\u0627\u0644 \u0627\u0644\u0635\u0648\u0631\u0629 \u0627\u0644\u062d\u0627\u0644\u064a\u0629.]';\n }\n\n return '\u0648\u0635\u0644\u062a \u0631\u0633\u0627\u0644\u0629 \u0645\u0646 \u0627\u0644\u0639\u0645\u064a\u0644 \u062f\u0648\u0646 \u0646\u0635 \u0623\u0648 \u0635\u0648\u0631 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0642\u0631\u0627\u0621\u0629.';\n})()\n}}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-448,
1648
],
"id": "f8cc9318-99a0-4755-9d2e-0354a0e901a3",
"name": "Prepare Agent Input"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "2c4841d6-8225-4b30-bf87-c94198feaa02",
"leftValue": "={{ $json.body.data.message.user_id }}",
"rightValue": "YOUR_TEST_USER_ID",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-576,
528
],
"id": "2c134549-36fb-4e17-91a9-39d242204c29",
"name": "If"
},
{
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"customer_reply\": {\n \"type\": \"string\"\n },\n \"case_type\": {\n \"type\": \"string\",\n \"enum\": [\n \"normal\",\n \"bag_info\",\n \"bag_image\",\n \"sack_image\",\n \"how_to_order\",\n \"how_to_hang_bag\"\n ]\n },\n \"issue_action\": {\n \"type\": \"string\",\n \"enum\": [\n \"none\",\n \"start_issue\",\n \"await_context\"\n ]\n }\n },\n \"required\": [\n \"customer_reply\",\n \"case_type\",\n \"issue_action\"\n ],\n \"additionalProperties\": false\n}",
"autoFix": true
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [
1856,
2096
],
"id": "2c912924-ae46-49d4-9b13-d80ea314d3d2",
"name": "Structured Output Parser",
"executeOnce": false,
"retryOnFail": true
},
{
"parameters": {
"method": "POST",
"url": "=https://YOUR_FRESHCHAT_DOMAIN.freshchat.com/v2/conversations/{{ $('Normalize Freshchat Event').first().json.conversation_id }}/messages",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_API_TOKEN"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Accept",
"value": "application/json"
},
{
"name": "ASSUME-IDENTITY",
"value": "false"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"message_parts\": [\n {\n \"text\": {\n \"content\": {{ JSON.stringify($json.output.customer_reply) }}\n }\n }\n ],\n \"message_type\": \"normal\",\n \"actor_type\": \"bot\"\n}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
1680,
1872
],
"id": "2b67c8bf-20e6-46a2-aa3c-daeb4fa2ab3a",
"name": "Send Customer Reply"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "bag_info",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "d0bae682-a664-4b18-850d-6e01638af93b"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Bag Info"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "ebe29642-52d7-453a-9de8-d2fab3edcec3",
"leftValue": "bag_image",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Bag Image"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d6f387b6-1335-4ae1-8d00-94d02a862099",
"leftValue": "sack_image",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Sack Image"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "2f1c6f55-ee4e-4c8a-a808-7c4feff71edd",
"leftValue": "how_to_order",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "How To Order"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c2431f23-97ee-4afa-9150-17534f26a1e5",
"leftValue": "how_to_hang_bag",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "How To Hang Bag"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "20bb0368-a6c8-48cb-a28a-fb5c3b0f5b5b",
"leftValue": "normal",
"rightValue": "={{ $('Demo Customer Service Agent').item.json.output.case_type }}",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "Normal"
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.
freshdeskApigoogleSheetsOAuth2ApihttpBasicAuthhttpBearerAuthopenAiApisupabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
AI Customer Service Automation - Portfolio. Uses googleSheets, httpRequest, agent, memoryBufferWindow. Webhook trigger; 91 nodes.
Source: https://github.com/yahya0093/ai-customer-service-automation/blob/main/workflows/customer-service-main.json — 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.
VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.
⏺ 🚀 How it works
Enhance your support, onboarding, and internal knowledge workflows with an intelligent RAG-powered chatbot that responds using live data stored in Google Sheets. 🤖📚 Built for teams that rely on struct
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
🧠 Gwen – The AI Voice Marketing Agent Gwen is your intelligent voice-powered marketing assistant built in n8n. She combines the power of OpenAI, ElevenLabs, and automation workflows to handle content