{
  "name": "Retail Company: AI Support Ticket Triage",
  "nodes": [
    {
      "parameters": {
        "pollTimes": { "item": [{ "mode": "everyMinute" }] },
        "mailbox": "support@example.com",
        "postProcessAction": "read",
        "options": {}
      },
      "id": "2b3c4d5e-0001-4b2b-9c2b-000000000001",
      "name": "New Support Email",
      "type": "n8n-nodes-base.emailReadImap",
      "typeVersion": 2,
      "position": [-40, 260],
      "notes": "Polls the shared support inbox every minute. Most email providers don't offer a true webhook trigger, so polling is the realistic option here."
    },
    {
      "parameters": {
        "modelId": { "__rl": true, "value": "claude-sonnet-4-6", "mode": "list" },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a support-ticket classifier for an outdoor apparel retailer. Read the email and return strict JSON only: {\"category\": one of [\"Order Status\",\"Return/Exchange\",\"Product Question\",\"Complaint\",\"Other\"], \"urgency\": one of [\"Low\",\"Normal\",\"High\"], \"draft_reply\": a short, warm first-pass reply under 80 words}. Do not invent order details you were not given."
            },
            {
              "role": "user",
              "content": "=Subject: {{ $json.subject }}\n\nFrom: {{ $json.from }}\n\nBody:\n{{ $json.textPlain }}"
            }
          ]
        },
        "options": { "temperature": 0.2 }
      },
      "id": "2b3c4d5e-0002-4b2b-9c2b-000000000002",
      "name": "Classify & Draft Reply (Claude)",
      "type": "n8n-nodes-base.anthropic",
      "typeVersion": 1,
      "position": [200, 260],
      "notes": "The AI-enhanced step: classifies topic + urgency and drafts a first-pass reply for a human to review, rather than auto-sending anything."
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json.content || $input.first().json.text;\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch (e) {\n  parsed = { category: 'Other', urgency: 'Normal', draft_reply: '' };\n}\nreturn [{ json: { ...$input.first().json, ...parsed } }];"
      },
      "id": "2b3c4d5e-0003-4b2b-9c2b-000000000003",
      "name": "Parse Classification JSON",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [440, 260],
      "notes": "Safely parses the model's JSON output; falls back to a safe default instead of breaking the workflow if the model ever returns malformed JSON."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "urgent-1",
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "High",
              "operator": { "type": "string", "operation": "equals" }
            },
            {
              "id": "complaint-1",
              "leftValue": "={{ $json.category }}",
              "rightValue": "Complaint",
              "operator": { "type": "string", "operation": "equals" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "2b3c4d5e-0004-4b2b-9c2b-000000000004",
      "name": "IF High Urgency or Complaint",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [680, 260],
      "notes": "Complaints and high-urgency tickets never get auto-routed to the standard queue: they always page a human directly."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#support-urgent", "mode": "name" },
        "text": "=🚨 {{ $json.category }} ({{ $json.urgency }}) from {{ $json.from }}\nSubject: {{ $json.subject }}\n\nDraft reply for review:\n{{ $json.draft_reply }}",
        "otherOptions": {}
      },
      "id": "2b3c4d5e-0005-4b2b-9c2b-000000000005",
      "name": "Slack: Urgent Review Queue",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [940, 140],
      "notes": "A human reviews and edits the AI draft before anything is sent: this workflow never auto-sends a reply to a customer."
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": { "__rl": true, "value": "1Yk8pR_COASTAL_TICKET_LOG_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Ticket Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Date": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}",
            "From": "={{ $json.from }}",
            "Subject": "={{ $json.subject }}",
            "Category": "={{ $json.category }}",
            "Urgency": "={{ $json.urgency }}",
            "Draft Reply": "={{ $json.draft_reply }}",
            "Status": "Pending Review"
          }
        }
      },
      "id": "2b3c4d5e-0006-4b2b-9c2b-000000000006",
      "name": "Log to Standard Queue (Sheets)",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [940, 380],
      "notes": "Normal-priority tickets skip the Slack page and go straight into a reviewable queue the support rep checks a few times a day."
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "loose" },
          "conditions": [
            {
              "id": "empty-body",
              "leftValue": "={{ $json.textPlain }}",
              "rightValue": "",
              "operator": { "type": "string", "operation": "empty" }
            }
          ],
          "combinator": "or"
        }
      },
      "id": "2b3c4d5e-0007-4b2b-9c2b-000000000007",
      "name": "Empty/Unparseable Email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [-40, 460],
      "notes": "Error-handling branch for emails with no readable body (e.g. image-only or malformed encoding)."
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": { "__rl": true, "value": "#automation-alerts", "mode": "name" },
        "text": "=⚠️ Received a support email that couldn't be auto-classified (empty body). From: {{ $json.from }}, Subject: {{ $json.subject }}. Please check manually.",
        "otherOptions": {}
      },
      "id": "2b3c4d5e-0008-4b2b-9c2b-000000000008",
      "name": "Notify: Manual Review Needed",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [200, 460],
      "notes": "Fallback so an unparseable email is surfaced to a human rather than silently dropped."
    }
  ],
  "connections": {
    "New Support Email": {
      "main": [
        [
          { "node": "Classify & Draft Reply (Claude)", "type": "main", "index": 0 },
          { "node": "Empty/Unparseable Email?", "type": "main", "index": 0 }
        ]
      ]
    },
    "Classify & Draft Reply (Claude)": {
      "main": [[{ "node": "Parse Classification JSON", "type": "main", "index": 0 }]]
    },
    "Parse Classification JSON": {
      "main": [[{ "node": "IF High Urgency or Complaint", "type": "main", "index": 0 }]]
    },
    "IF High Urgency or Complaint": {
      "main": [
        [{ "node": "Slack: Urgent Review Queue", "type": "main", "index": 0 }],
        [{ "node": "Log to Standard Queue (Sheets)", "type": "main", "index": 0 }]
      ]
    },
    "Empty/Unparseable Email?": {
      "main": [
        [{ "node": "Notify: Manual Review Needed", "type": "main", "index": 0 }],
        []
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "portfolioSample": true,
    "instructions": "Sample workflow for portfolio purposes. Replace the mailbox, Google Sheet ID, Slack channel IDs, and AI credentials with the client's own before deploying live. This workflow only ever drafts replies for human review: it never auto-sends to a customer."
  }
}
