{
  "id": "1kRvSB08K-gM_0-hSXcHQ",
  "name": "Analyze customer support ticket sentiment and route to Slack channels",
  "tags": [
    {
      "id": "5160a02758f54f13",
      "name": "ai",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "274a391fee674268",
      "name": "gpt-4",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "QWzGPDfiad0OMdvN",
      "name": "openai",
      "createdAt": "2026-04-22T22:17:12.459Z",
      "updatedAt": "2026-04-22T22:17:12.459Z"
    },
    {
      "id": "78e26cf7df1f4e52",
      "name": "slack",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    },
    {
      "id": "loDvXh5Qq0snh5pT",
      "name": "customer-support",
      "createdAt": "2026-04-22T22:17:12.496Z",
      "updatedAt": "2026-04-22T22:17:12.496Z"
    },
    {
      "id": "FGrQ8vVq5xDBcJH4",
      "name": "sentiment-analysis",
      "createdAt": "2026-04-22T22:17:12.487Z",
      "updatedAt": "2026-04-22T22:17:12.487Z"
    },
    {
      "id": "3f8091ee9b744783",
      "name": "automation",
      "createdAt": "2026-04-30T18:38:11.353194Z",
      "updatedAt": "2026-04-30T18:38:11.353194Z"
    }
  ],
  "nodes": [
    {
      "id": "634fcb35-e36f-47e8-b4ca-53acc96066d7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1360,
        -64
      ],
      "parameters": {
        "width": 480,
        "height": 624,
        "content": "## Analyze customer support ticket sentiment and route to Slack channels\n\n## How it works\n\n1. The workflow receives a support ticket via a webhook.\n2. It normalizes the ticket payload for processing.\n3. Sentiment is analyzed using the GPT-4o AI model.\n4. The AI response is parsed to determine routing.\n5. The message is routed to a specific Slack channel based on sentiment, and a routing result is returned.\n\n## Setup steps\n\n- [ ] Configure webhook URL to receive support tickets.\n- [ ] Set up OpenAI API credentials for sentiment analysis.\n- [ ] Connect Slack credentials to enable message posting to channels.\n\n## Customization\n\nThe routing logic can be adjusted based on different sentiment scores or additional criteria."
      },
      "typeVersion": 1
    },
    {
      "id": "7cf71e92-5749-4091-a2a8-eac742b72e99",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 304,
        "content": "## Receive and normalize\n\nHandles incoming support tickets and prepares them for analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "0f3b2ea2-1912-4658-aa42-bf407543f77b",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 320,
        "content": "## Analyze sentiment\n\nUses AI to analyze customer sentiment from the support ticket."
      },
      "typeVersion": 1
    },
    {
      "id": "1367d3d5-2a75-4b8b-b798-110dda9629d4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 336,
        "content": "## Parse and route\n\nParses the AI response and routes based on sentiment analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "8b69d01e-4962-4c7f-af4b-530f03a9bf61",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 624,
        "content": "## Post to Slack channels\n\nPosts the ticket to the appropriate Slack channel based on the anger score."
      },
      "typeVersion": 1
    },
    {
      "id": "1665fb1a-e094-4de6-b9ab-1a963bfcc0fb",
      "name": "When Ticket Received",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -752,
        224
      ],
      "parameters": {
        "path": "support-ticket",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "02d910ef-92e6-4f39-8f7c-7b73c65c89a5",
      "name": "Normalize Ticket Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -544,
        224
      ],
      "parameters": {
        "jsCode": "// Normalize and validate incoming ticket payload\nconst body = $input.first().json.body || $input.first().json;\n\nconst ticketId = body.ticket_id || `TICKET-${Date.now()}`;\nconst customerName = body.customer_name || 'Unknown Customer';\nconst email = body.email || 'user@example.com';\nconst subject = body.subject || 'No Subject';\nconst message = body.message || body.text || body.content || '';\nconst priority = body.priority || 'normal';\nconst createdAt = body.created_at || new Date().toISOString();\n\nif (!message || message.trim().length === 0) {\n  throw new Error('Ticket message body is empty or missing.');\n}\n\nreturn [{\n  json: {\n    ticket_id: ticketId,\n    customer_name: customerName,\n    email: email,\n    subject: subject,\n    message: message.trim(),\n    priority: priority,\n    created_at: createdAt,\n    processed_at: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "6672218e-cd79-4ae9-97a9-b0cef65e0f20",
      "name": "OpenAI Sentiment Analysis",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -160,
        224
      ],
      "parameters": {
        "resource": "chat"
      },
      "typeVersion": 1.8
    },
    {
      "id": "278e210e-03cf-4eba-8d72-79cb8eeb27ce",
      "name": "Parse Sentiment Output",
      "type": "n8n-nodes-base.code",
      "position": [
        112,
        224
      ],
      "parameters": {
        "jsCode": "// Parse AI JSON response and append routing decision\nconst ticketData = $('Normalize Ticket Data').first().json;\nconst aiContent = $input.first().json.message?.content\n  || $input.first().json.choices?.[0]?.message?.content\n  || '';\n\nlet analysis;\ntry {\n  const clean = aiContent.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  analysis = JSON.parse(clean);\n} catch (e) {\n  analysis = {\n    anger_score: 5,\n    sentiment_category: 'NEUTRAL',\n    emotion_summary: 'AI response could not be parsed. Manual review required.',\n    key_issues: ['Parse error'],\n    urgency_level: 'MEDIUM',\n    recommended_action: 'Review ticket manually'\n  };\n}\n\nconst angerScore = Math.min(10, Math.max(0, parseInt(analysis.anger_score, 10) || 5));\n\nlet routing_channel, routing_reason;\nif (angerScore >= 8) {\n  routing_channel = '#escalation';\n  routing_reason = 'Score \u22658 \u2014 immediate escalation required';\n} else if (angerScore >= 4) {\n  routing_channel = '#support';\n  routing_reason = 'Score 4\u20137 \u2014 standard support handling';\n} else {\n  routing_channel = '#feedback';\n  routing_reason = 'Score \u22643 \u2014 feedback or general inquiry';\n}\n\nreturn [{\n  json: {\n    ...ticketData,\n    anger_score: angerScore,\n    sentiment_category: analysis.sentiment_category || 'NEUTRAL',\n    emotion_summary: analysis.emotion_summary || '',\n    key_issues: analysis.key_issues || [],\n    urgency_level: analysis.urgency_level || 'MEDIUM',\n    recommended_action: analysis.recommended_action || '',\n    routing_channel,\n    routing_reason,\n    analyzed_at: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5d665945-363d-4305-9b66-098ce47aa9ad",
      "name": "Route by Sentiment Score",
      "type": "n8n-nodes-base.switch",
      "position": [
        336,
        224
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "Escalation",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    },
                    "leftValue": "={{ $json.anger_score }}",
                    "rightValue": 8
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Support",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    },
                    "leftValue": "={{ $json.anger_score }}",
                    "rightValue": 4
                  },
                  {
                    "operator": {
                      "type": "number",
                      "operation": "lte"
                    },
                    "leftValue": "={{ $json.anger_score }}",
                    "rightValue": 7
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Feedback",
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "lte"
                    },
                    "leftValue": "={{ $json.anger_score }}",
                    "rightValue": 3
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "bdac3d15-1858-4e12-909b-6b4324b1c4e8",
      "name": "Send to Slack #escalation",
      "type": "n8n-nodes-base.slack",
      "position": [
        560,
        64
      ],
      "parameters": {
        "text": "=\ud83d\udea8 *URGENT ESCALATION REQUIRED*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udd34 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "escalation"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "889e9ab6-c8c1-4d4d-baa2-cc7fd794fde0",
      "name": "Send to Slack #support",
      "type": "n8n-nodes-base.slack",
      "position": [
        560,
        224
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f *New Support Ticket*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udfe1 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "support"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "652668fd-06e5-4737-b84e-3a33f3642b5d",
      "name": "Send to Slack #feedback",
      "type": "n8n-nodes-base.slack",
      "position": [
        560,
        384
      ],
      "parameters": {
        "text": "=\ud83d\udcac *New Feedback / Inquiry*\n\n*Ticket:* `{{ $json.ticket_id }}` | *Score:* {{ $json.anger_score }}/10 \ud83d\udfe2 | *Urgency:* {{ $json.urgency_level }}\n*Customer:* {{ $json.customer_name }} \u2014 {{ $json.email }}\n*Subject:* {{ $json.subject }}\n\n*Message:*\n> {{ $json.message }}\n\n*Sentiment:* {{ $json.sentiment_category }}\n*Summary:* {{ $json.emotion_summary }}\n*Issues:* {{ $json.key_issues.join(' / ') }}\n*Action:* {{ $json.recommended_action }}\n\n_Received: {{ $json.created_at }} | Analyzed: {{ $json.analyzed_at }}_",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "feedback"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "0b88f173-9a26-40db-a73f-790edb454757",
      "name": "Respond with Routing Outcome",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        784,
        224
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, ticket_id: $('Parse Sentiment Output').first().json.ticket_id, anger_score: $('Parse Sentiment Output').first().json.anger_score, sentiment_category: $('Parse Sentiment Output').first().json.sentiment_category, routing_channel: $('Parse Sentiment Output').first().json.routing_channel }) }}"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "f8ab54dd-d40c-40e2-aa3d-a8b849f703a4",
  "connections": {
    "When Ticket Received": {
      "main": [
        [
          {
            "node": "Normalize Ticket Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Ticket Data": {
      "main": [
        [
          {
            "node": "OpenAI Sentiment Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Sentiment Output": {
      "main": [
        [
          {
            "node": "Route by Sentiment Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Slack #support": {
      "main": [
        [
          {
            "node": "Respond with Routing Outcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Slack #feedback": {
      "main": [
        [
          {
            "node": "Respond with Routing Outcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Sentiment Score": {
      "main": [
        [
          {
            "node": "Send to Slack #escalation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send to Slack #support",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send to Slack #feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Sentiment Analysis": {
      "main": [
        [
          {
            "node": "Parse Sentiment Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to Slack #escalation": {
      "main": [
        [
          {
            "node": "Respond with Routing Outcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}