AutomationFlowsAI & RAG › AI Customer Service Automation - Portfolio

AI Customer Service Automation - Portfolio

AI Customer Service Automation - Portfolio. Uses googleSheets, httpRequest, agent, memoryBufferWindow. Webhook trigger; 91 nodes.

Webhook trigger★★★★★ complexityAI-powered91 nodesGoogle SheetsHTTP RequestAgentMemory Buffer WindowOutput Parser StructuredHTTP Request ToolGoogle Sheets ToolSupabase
AI & RAG Trigger: Webhook Nodes: 91 Complexity: ★★★★★ AI nodes: yes Added:

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 →

Download .json
{
  "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.

Pro

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 →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

VEP WAPP. Uses openAi, lmChatOpenAi, toolCalculator, agent. Webhook trigger; 100 nodes.

OpenAI, OpenAI Chat, Tool Calculator +7
AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

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

Chat Trigger, Output Parser Structured, Memory Buffer Window +6
AI & RAG

leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.

Supabase, Gmail, Form Trigger +13
AI & RAG

🧠 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

Tool Workflow, Memory Buffer Window, Agent +10