{
  "name": "Service Order Fulfillment & SLA Escalation Engine",
  "nodes": [
    {
      "id": "sticky-overview", "name": "Overview", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, -500],
      "parameters": { "content": "## Service Order Fulfillment & SLA Escalation Engine\nVersion 1.0.0 — Operations\n\nProduction-grade order fulfillment orchestrator for service brokerages (skip hire, field services, logistics). Verifies Stripe payment, extracts structured order details with Claude AI, creates CRM records in Freshworks, sends transactional emails via Postmark, assigns a supplier, enforces a 4-hour SLA, auto-escalates missed SLAs to Slack and reassigns to the next available supplier, then logs every outcome to Google Sheets.\n\nFlow: Webhook => Validate Order => Verify Stripe Payment => Payment OK? => Extract with AI => Upsert Customer (Freshworks) => Create Deal => Send Confirmation (Postmark) => Find Supplier => Send Assignment => SLA Wait 4h => Check Deal Stage => Accepted? => Confirm/Escalate => SLA Retry 2h => Final Check => Log Outcome", "height": 260, "width": 720, "color": 7 }
    },
    {
      "id": "sticky-prereqs", "name": "Prerequisites", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, -220],
      "parameters": { "content": "## Prerequisites\n- Stripe account with secret key (for payment verification)\n- Freshworks CRM account — API key + domain (subdomain.freshsales.io)\n- Postmark account — Server API Token (transactional email stream)\n- Slack workspace — Bot Token + #ops-escalations channel\n- Google Sheets — OAuth2 credential + pre-created fulfillment log sheet\n- Anthropic API key (Claude for order extraction)\n\nFreshworks setup: Go to Admin Settings → API Settings → copy your API key. Suppliers must be CRM contacts tagged 'supplier-available'. Deals need stages: New, Supplier Assigned, Accepted, Escalated, Completed.", "height": 260, "width": 440, "color": 5 }
    },
    {
      "id": "sticky-setup", "name": "Setup Required", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [220, -220],
      "parameters": { "content": "## Setup Required\n1. Order Webhook — copy webhook URL into your website/checkout backend\n2. Verify Stripe Payment — add Stripe Secret Key to the HTTP Request header\n3. Extract Order Details — connect Anthropic API credential\n4. Upsert Customer Contact — set FRESHWORKS_DOMAIN + FRESHWORKS_API_KEY\n5. Create Service Deal — connect Freshworks CRM credential, update stageId for 'New'\n6. Send Order Confirmation — add Postmark Server Token, update From address\n7. Find Available Supplier — update tag filter to match your supplier tag in Freshworks\n8. Send Supplier Assignment — update From address and email template\n9. SLA Wait nodes — adjust 4h/2h windows to your SLA policy\n10. Alert Slack — connect Slack credential, confirm channel name #ops-escalations\n11. Log Outcome — connect Google Sheets, replace YOUR_FULFILLMENT_SHEET_ID", "height": 260, "width": 440, "color": 3 }
    },
    {
      "id": "sticky-howitworks", "name": "How It Works", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [-240, 60],
      "parameters": { "content": "## How It Works\n1. Webhook receives new order from checkout with payment_intent_id and customer details\n2. Validate Code checks required fields and normalises data (email lowercase, postcode uppercase)\n3. Stripe HTTP verifies payment_intent status = 'succeeded' before proceeding\n4. Claude AI extracts waste type, skip size, collection address, priority from order notes\n5. Customer contact upserted in Freshworks CRM via REST API\n6. Service deal created in Freshworks with order value and all custom fields\n7. Postmark sends branded order confirmation to customer\n8. Freshworks searched for available supplier contacts (tagged supplier-available)\n9. Postmark sends supplier assignment request with job details\n10. Wait node holds execution for 4 hours (SLA window)\n11. Deal stage checked — if Accepted, customer notified and success logged\n12. If not: Slack alert fired, next supplier found, reassigned, 2h retry SLA starts\n13. Retry check: if accepted → confirm; if not → Slack urgent alert + Escalated deal stage\n14. All outcomes (success / retry success / escalated) logged to Google Sheets", "height": 220, "width": 540, "color": 4 }
    },

    {
      "id": "order-webhook", "name": "New Order Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 100],
      "parameters": {
        "httpMethod": "POST",
        "path": "new-order",
        "responseMode": "responseNode",
        "options": { "rawBody": false }
      },
      "webhookId": "order-webhook-uuid-001"
    },

    {
      "id": "sni-validate", "name": "Validate note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [265, -35],
      "parameters": { "content": "Validates required fields, normalises email/postcode, assigns order reference", "height": 60, "width": 270 }
    },
    {
      "id": "validate-order", "name": "Validate & Parse Order", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [260, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const item = $input.first().json?.body || $input.first().json;\nconst required = ['payment_intent_id', 'customer_email', 'order_notes'];\nconst missing = required.filter(f => !item[f]);\nif (missing.length > 0) throw new Error(`Missing required fields: ${missing.join(', ')}`);\nconst email = (item.customer_email || '').trim().toLowerCase();\nif (!email.includes('@')) throw new Error('Invalid customer_email format');\nconst orderRef = item.order_ref || `ORD-${Date.now()}`;\nreturn [{ json: {\n  payment_intent_id:  item.payment_intent_id.trim(),\n  customer_email:     email,\n  customer_name:      item.customer_name || '',\n  customer_phone:     item.customer_phone || '',\n  order_notes:        item.order_notes || '',\n  service_type:       item.service_type || 'general',\n  collection_address: item.collection_address || '',\n  postcode:           (item.postcode || '').trim().toUpperCase(),\n  requested_date:     item.requested_date || '',\n  order_value_gbp:    parseFloat(item.order_value_gbp) || 0,\n  order_ref:          orderRef,\n  received_at:        new Date().toISOString()\n}}];"
      }
    },

    {
      "id": "sni-stripe", "name": "Stripe note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [525, -35],
      "parameters": { "content": "Verifies payment_intent status = succeeded via Stripe API before processing", "height": 60, "width": 265 }
    },
    {
      "id": "verify-payment", "name": "Verify Stripe Payment", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [520, 100],
      "parameters": {
        "method": "GET",
        "url": "=https://api.stripe.com/v1/payment_intents/{{ $json.payment_intent_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "Bearer YOUR_STRIPE_SECRET_KEY" },
            { "name": "Stripe-Version", "value": "2024-11-20.acacia" }
          ]
        },
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "consolidate-payment", "name": "Consolidate Payment Result", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [780, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst order = $('Validate & Parse Order').first().json;\nconst payment = rows[0]?.json || {};\nconst status = payment.status || 'error';\nconst amountPence = payment.amount || 0;\nreturn [{ json: {\n  ...order,\n  payment_status:      status,\n  payment_valid:       status === 'succeeded',\n  payment_amount_gbp:  amountPence / 100,\n  payment_currency:    payment.currency || 'gbp',\n  stripe_customer_id:  payment.customer || null,\n  payment_error:       payment.last_payment_error?.message || null\n}}];"
      }
    },

    {
      "id": "sni-payment-check", "name": "Payment check note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1045, -35],
      "parameters": { "content": "Routes to failure path if payment not succeeded — stops order processing immediately", "height": 60, "width": 270 }
    },
    {
      "id": "payment-valid", "name": "Payment Succeeded?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [1040, 100],
      "parameters": {
        "conditions": {
          "options": { "typeValidation": "loose" },
          "conditions": [{ "id": "cond-payment", "leftValue": "={{ $json.payment_valid }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equal" } }],
          "combinator": "and"
        }
      }
    },

    {
      "id": "send-failed-email", "name": "Send Payment Failed Notice", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1040, 320],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"orders@yourcompany.com\",\n  \"To\": \"{{ $json.customer_email }}\",\n  \"Subject\": \"Payment Issue with Order {{ $json.order_ref }}\",\n  \"HtmlBody\": \"<h2>Payment could not be verified</h2><p>Dear {{ $json.customer_name }},</p><p>We were unable to verify your payment for order <strong>{{ $json.order_ref }}</strong>. Error: {{ $json.payment_error || 'Payment not completed' }}.</p><p>Please contact us or retry your order.</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "log-failed", "name": "Log Failed Payment", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [1300, 320],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": { "__rl": true, "value": "YOUR_FULFILLMENT_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Order Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp":    "={{ DateTime.now().toISO() }}",
            "Order Ref":    "={{ $json.order_ref }}",
            "Customer":     "={{ $json.customer_email }}",
            "Status":       "payment_failed",
            "Reason":       "={{ $json.payment_error || 'payment not succeeded' }}",
            "Order Value":  "={{ $json.order_value_gbp }}"
          }
        }
      },
      "credentials": { "googleSheetsOAuth2Api": { "id": "gsheets-cred", "name": "Google Sheets OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-extract", "name": "Extract note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1305, -35],
      "parameters": { "content": "Claude AI parses free-text order notes into a validated structured schema", "height": 60, "width": 265 }
    },
    {
      "id": "extract-agent", "name": "Extract Order Details", "type": "@n8n/n8n-nodes-langchain.agent", "typeVersion": 1.8, "position": [1300, 100],
      "parameters": {
        "agentType": "toolsAgent",
        "promptType": "define",
        "text": "=Extract and validate the service order details from the following information.\n\nOrder Reference: {{ $json.order_ref }}\nService Type: {{ $json.service_type }}\nCollection Address: {{ $json.collection_address }}\nPostcode: {{ $json.postcode }}\nRequested Date: {{ $json.requested_date }}\nOrder Notes / Customer Instructions:\n{{ $json.order_notes }}\n\nEstimated Order Value: £{{ $json.order_value_gbp }}\n\nParse all details carefully. Extract waste type, skip size if applicable, any access restrictions, time preferences, and urgency level. Flag anything that seems unusual or incomplete in special_instructions.",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are an order processing assistant for a service brokerage. Extract structured data from customer order notes with high precision. For skip hire orders: identify skip size (2, 4, 6, 8, 10, 12 yard), waste type (general, construction, garden, commercial, mixed, hazardous), and any site access restrictions. Priority levels: standard = normal orders, urgent = same-day or next-day, emergency = immediate response required. Always output all fields even if some are null.",
          "returnIntermediateSteps": false
        }
      }
    },
    {
      "id": "extract-claude", "name": "Claude — Extract", "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic", "typeVersion": 1.3, "position": [1300, 300],
      "parameters": {
        "model": { "__rl": true, "value": "claude-3-5-sonnet-20241022", "mode": "list" },
        "options": { "temperature": 0 }
      },
      "credentials": { "anthropicApi": { "id": "anthropic-cred", "name": "Anthropic API" } }
    },
    {
      "id": "extract-parser", "name": "Order Details Schema", "type": "@n8n/n8n-nodes-langchain.outputParserStructured", "typeVersion": 1.2, "position": [1500, 300],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\"type\":\"object\",\"properties\":{\"service_type\":{\"type\":\"string\"},\"collection_address\":{\"type\":\"string\"},\"postcode\":{\"type\":\"string\"},\"waste_type\":{\"type\":\"string\",\"enum\":[\"general\",\"construction\",\"garden\",\"commercial\",\"mixed\",\"hazardous\",\"other\"]},\"skip_size\":{\"type\":\"string\"},\"requested_date\":{\"type\":\"string\"},\"customer_name\":{\"type\":\"string\"},\"customer_email\":{\"type\":\"string\"},\"customer_phone\":{\"type\":\"string\"},\"special_instructions\":{\"type\":\"string\"},\"access_restrictions\":{\"type\":\"string\"},\"estimated_value_gbp\":{\"type\":\"number\"},\"priority\":{\"type\":\"string\",\"enum\":[\"standard\",\"urgent\",\"emergency\"]}},\"required\":[\"service_type\",\"collection_address\",\"postcode\",\"waste_type\",\"customer_name\",\"customer_email\",\"customer_phone\",\"priority\"]}"
      }
    },

    {
      "id": "sni-upsert", "name": "Upsert note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [1565, -35],
      "parameters": { "content": "Creates or updates customer contact in Freshworks CRM via upsert REST endpoint", "height": 60, "width": 270 }
    },
    {
      "id": "upsert-contact", "name": "Upsert Customer Contact", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1560, 100],
      "parameters": {
        "method": "POST",
        "url": "=https://YOUR_DOMAIN.freshsales.io/api/upsert/contacts",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "Authorization", "value": "Token token=YOUR_FRESHWORKS_API_KEY" },
            { "name": "Content-Type", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"unique_identifier\": { \"emails\": \"{{ $json.output.customer_email || $json.customer_email }}\" },\n  \"contact\": {\n    \"email\": \"{{ $json.output.customer_email || $json.customer_email }}\",\n    \"first_name\": \"{{ ($json.output.customer_name || $json.customer_name).split(' ')[0] }}\",\n    \"last_name\": \"{{ ($json.output.customer_name || $json.customer_name).split(' ').slice(1).join(' ') || 'Unknown' }}\",\n    \"mobile_number\": \"{{ $json.output.customer_phone || $json.customer_phone }}\",\n    \"tag_list\": \"customer\",\n    \"custom_field\": {\n      \"cf_postcode\": \"{{ $json.output.postcode || $json.postcode }}\"\n    }\n  }\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-contact", "name": "Consolidate Contact", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [1820, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst order = $('Validate & Parse Order').first().json;\nconst payment = $('Consolidate Payment Result').first().json;\nconst aiOutput = $('Extract Order Details').first().json.output || {};\nconst contact = rows[0]?.json?.contact || rows[0]?.json || {};\nreturn [{ json: {\n  ...order,\n  payment_amount_gbp:   payment.payment_amount_gbp,\n  stripe_customer_id:   payment.stripe_customer_id,\n  extracted:            aiOutput,\n  contact_id:           String(contact.id || contact.contact?.id || ''),\n  customer_name:        aiOutput.customer_name || order.customer_name,\n  customer_email:       aiOutput.customer_email || order.customer_email,\n  customer_phone:       aiOutput.customer_phone || order.customer_phone,\n  collection_address:   aiOutput.collection_address || order.collection_address,\n  postcode:             aiOutput.postcode || order.postcode,\n  waste_type:           aiOutput.waste_type || 'general',\n  skip_size:            aiOutput.skip_size || '',\n  priority:             aiOutput.priority || 'standard',\n  special_instructions: aiOutput.special_instructions || '',\n  access_restrictions:  aiOutput.access_restrictions || ''\n}}];"
      }
    },

    {
      "id": "sni-deal", "name": "Deal note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2085, -35],
      "parameters": { "content": "Creates service deal in Freshworks CRM linked to the customer contact", "height": 60, "width": 265 }
    },
    {
      "id": "create-deal", "name": "Create Service Deal", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [2080, 100],
      "parameters": {
        "resource": "deal",
        "operation": "create",
        "name": "={{ $json.service_type | upper }} — {{ $json.postcode }} — {{ $json.order_ref }}",
        "additionalFields": {
          "amount": "={{ $json.payment_amount_gbp || $json.order_value_gbp }}",
          "closingDate": "={{ $json.extracted.requested_date || DateTime.now().plus({ days: 7 }).toISODate() }}",
          "probability": 100,
          "stageId": "YOUR_NEW_DEAL_STAGE_ID"
        }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-deal", "name": "Consolidate Deal", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [2340, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate Contact').first().json;\nconst deal = rows[0]?.json || {};\nconst dealId = String(deal.id || deal.deal?.id || '');\nreturn [{ json: {\n  ...ctx,\n  deal_id:   dealId,\n  deal_name: deal.name || deal.deal?.name || ctx.order_ref\n}}];"
      }
    },

    {
      "id": "sni-confirm", "name": "Confirmation note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2605, -35],
      "parameters": { "content": "Sends branded order confirmation email to customer via Postmark", "height": 60, "width": 260 }
    },
    {
      "id": "send-confirmation", "name": "Send Order Confirmation", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [2600, 100],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"orders@yourcompany.com\",\n  \"To\": \"{{ $json.customer_email }}\",\n  \"Subject\": \"Order Confirmed — {{ $json.order_ref }}\",\n  \"HtmlBody\": \"<h2>Thank you for your order, {{ $json.customer_name }}!</h2><p>Your order <strong>{{ $json.order_ref }}</strong> has been confirmed and payment of <strong>£{{ $json.payment_amount_gbp }}</strong> received.</p><table border='1' cellpadding='8' cellspacing='0' style='border-collapse:collapse'><tr><td><b>Service</b></td><td>{{ $json.service_type }}</td></tr><tr><td><b>Collection Address</b></td><td>{{ $json.collection_address }}, {{ $json.postcode }}</td></tr><tr><td><b>Skip Size</b></td><td>{{ $json.skip_size || 'TBC' }}</td></tr><tr><td><b>Waste Type</b></td><td>{{ $json.waste_type }}</td></tr><tr><td><b>Requested Date</b></td><td>{{ $json.extracted.requested_date || 'TBC' }}</td></tr><tr><td><b>Priority</b></td><td>{{ $json.priority }}</td></tr></table><p>We are now assigning a supplier partner to your order. You will receive a further confirmation shortly.</p><p>Reference: {{ $json.order_ref }}</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-supplier", "name": "Find Supplier note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [2865, -35],
      "parameters": { "content": "Queries Freshworks for contacts tagged 'supplier-available' — picks the first match", "height": 60, "width": 265 }
    },
    {
      "id": "find-supplier", "name": "Find Available Supplier", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [2860, 100],
      "parameters": {
        "resource": "contact",
        "operation": "getAll",
        "returnAll": false,
        "limit": 5,
        "filters": {
          "tag": "supplier-available"
        }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-supplier", "name": "Consolidate Supplier", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [3120, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate Deal').first().json;\nconst suppliers = rows.filter(r => !r.json.error && r.json.id);\nif (suppliers.length === 0) {\n  // No supplier found — flag for manual assignment\n  return [{ json: {\n    ...ctx,\n    supplier_id:    null,\n    supplier_email: null,\n    supplier_name:  'UNASSIGNED',\n    supplier_phone: null,\n    supplier_found: false,\n    all_supplier_ids: []\n  }}];\n}\nconst primary = suppliers[0].json;\nconst allIds = suppliers.map(s => String(s.json.id));\nreturn [{ json: {\n  ...ctx,\n  supplier_id:      String(primary.id),\n  supplier_email:   primary.email,\n  supplier_name:    `${primary.first_name || ''} ${primary.last_name || ''}`.trim(),\n  supplier_phone:   primary.mobile_number || primary.phone || '',\n  supplier_found:   true,\n  all_supplier_ids: allIds\n}}];"
      }
    },

    {
      "id": "sni-supplier-req", "name": "Supplier Request note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [3385, -35],
      "parameters": { "content": "Sends job assignment request to supplier with full order details and acceptance deadline", "height": 60, "width": 265 }
    },
    {
      "id": "send-supplier-request", "name": "Send Supplier Assignment", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [3380, 100],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"ops@yourcompany.com\",\n  \"To\": \"{{ $json.supplier_email || 'ops@yourcompany.com' }}\",\n  \"Subject\": \"New Job Assignment — {{ $json.order_ref }} ({{ $json.priority | upper }})\",\n  \"HtmlBody\": \"<h2>New Job Assignment</h2><p>Dear {{ $json.supplier_name }},</p><p>A new job has been assigned to you. Please confirm acceptance within <strong>4 hours</strong>.</p><table border='1' cellpadding='8' style='border-collapse:collapse'><tr><td><b>Order Ref</b></td><td>{{ $json.order_ref }}</td></tr><tr><td><b>Service</b></td><td>{{ $json.service_type }}</td></tr><tr><td><b>Collection Address</b></td><td>{{ $json.collection_address }}, {{ $json.postcode }}</td></tr><tr><td><b>Skip Size</b></td><td>{{ $json.skip_size || 'TBC' }}</td></tr><tr><td><b>Waste Type</b></td><td>{{ $json.waste_type }}</td></tr><tr><td><b>Date Required</b></td><td>{{ $json.extracted.requested_date || 'ASAP' }}</td></tr><tr><td><b>Priority</b></td><td>{{ $json.priority | upper }}</td></tr><tr><td><b>Access Notes</b></td><td>{{ $json.access_restrictions || 'None' }}</td></tr><tr><td><b>Special Instructions</b></td><td>{{ $json.special_instructions || 'None' }}</td></tr><tr><td><b>Value</b></td><td>£{{ $json.payment_amount_gbp }}</td></tr></table><p>To accept this job, update the deal stage to Accepted in the CRM within 4 hours, or reply to this email.</p><p>Deal ID for CRM update: {{ $json.deal_id }}</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-sla", "name": "SLA Wait note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [3645, -35],
      "parameters": { "content": "Holds execution for 4 hours — the primary SLA window for supplier acceptance", "height": 60, "width": 260 }
    },
    {
      "id": "sla-wait-4h", "name": "SLA Wait — 4 Hours", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [3640, 100],
      "parameters": {
        "resume": "timeInterval",
        "time": { "unit": "hours", "value": 4 }
      },
      "webhookId": "sla-wait-4h-uuid-002"
    },

    {
      "id": "sni-status-check", "name": "Status check note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [3905, -35],
      "parameters": { "content": "Polls Freshworks deal stage to determine if supplier has accepted the job", "height": 60, "width": 260 }
    },
    {
      "id": "check-deal-status", "name": "Check Deal Stage", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [3900, 100],
      "parameters": {
        "resource": "deal",
        "operation": "get",
        "dealId": "={{ $json.deal_id }}"
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-status", "name": "Consolidate SLA Status", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [4160, 100],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate Supplier').first().json;\nconst deal = rows[0]?.json || {};\nconst stageName = (deal.deal_stage?.name || deal.stage_name || '').toLowerCase();\nconst acceptedStages = ['accepted', 'confirmed', 'partner confirmed', 'supplier accepted', 'in progress'];\nconst isAccepted = acceptedStages.some(s => stageName.includes(s));\nreturn [{ json: {\n  ...ctx,\n  current_deal_stage:  stageName,\n  supplier_accepted:   isAccepted,\n  sla_check_at:        new Date().toISOString(),\n  sla_attempt:         1\n}}];"
      }
    },

    {
      "id": "sni-accepted", "name": "Accepted check note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [4425, -35],
      "parameters": { "content": "Routes TRUE if supplier accepted within SLA, FALSE triggers escalation path", "height": 60, "width": 265 }
    },
    {
      "id": "supplier-accepted", "name": "Supplier Accepted SLA?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [4420, 100],
      "parameters": {
        "conditions": {
          "options": { "typeValidation": "loose" },
          "conditions": [{ "id": "cond-accept", "leftValue": "={{ $json.supplier_accepted }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equal" } }],
          "combinator": "and"
        }
      }
    },

    {
      "id": "update-deal-confirmed", "name": "Update Deal — Confirmed", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [4680, -100],
      "parameters": {
        "resource": "deal",
        "operation": "update",
        "dealId": "={{ $json.deal_id }}",
        "updateFields": {
          "stageId": "YOUR_CONFIRMED_STAGE_ID"
        }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "send-customer-confirmed", "name": "Send Customer — Job Confirmed", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [4940, -100],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"orders@yourcompany.com\",\n  \"To\": \"{{ $('Consolidate SLA Status').first().json.customer_email }}\",\n  \"Subject\": \"Your Job is Confirmed — {{ $('Consolidate SLA Status').first().json.order_ref }}\",\n  \"HtmlBody\": \"<h2>Great news — your job is confirmed!</h2><p>Dear {{ $('Consolidate SLA Status').first().json.customer_name }},</p><p>A supplier partner has accepted your order <strong>{{ $('Consolidate SLA Status').first().json.order_ref }}</strong> and will be in contact to confirm the exact collection time.</p><p><b>Supplier:</b> {{ $('Consolidate SLA Status').first().json.supplier_name }}</p><p>If you have any questions please quote your reference number.</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "log-success", "name": "Log Success", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [5200, -100],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": { "__rl": true, "value": "YOUR_FULFILLMENT_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Order Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp":       "={{ DateTime.now().toISO() }}",
            "Order Ref":       "={{ $('Consolidate SLA Status').first().json.order_ref }}",
            "Customer":        "={{ $('Consolidate SLA Status').first().json.customer_email }}",
            "Postcode":        "={{ $('Consolidate SLA Status').first().json.postcode }}",
            "Service":         "={{ $('Consolidate SLA Status').first().json.service_type }}",
            "Waste Type":      "={{ $('Consolidate SLA Status').first().json.waste_type }}",
            "Skip Size":       "={{ $('Consolidate SLA Status').first().json.skip_size }}",
            "Order Value":     "={{ $('Consolidate SLA Status').first().json.payment_amount_gbp }}",
            "Supplier":        "={{ $('Consolidate SLA Status').first().json.supplier_name }}",
            "SLA Attempt":     "1",
            "Status":          "confirmed",
            "Deal ID":         "={{ $('Consolidate SLA Status').first().json.deal_id }}"
          }
        }
      },
      "credentials": { "googleSheetsOAuth2Api": { "id": "gsheets-cred", "name": "Google Sheets OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-slack-sla", "name": "Slack SLA note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [4685, 185],
      "parameters": { "content": "Fires Slack alert to ops team with full order context when SLA is missed", "height": 60, "width": 265 }
    },
    {
      "id": "alert-slack-sla", "name": "Alert Slack — SLA Missed", "type": "n8n-nodes-base.slack", "typeVersion": 2.2, "position": [4680, 320],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": { "__rl": true, "value": "#ops-escalations", "mode": "name" },
        "text": "=:warning: *SLA MISSED — Supplier did not accept within 4 hours*\n\n*Order:* {{ $json.order_ref }}\n*Customer:* {{ $json.customer_name }} ({{ $json.customer_email }})\n*Service:* {{ $json.service_type }} — {{ $json.skip_size || 'TBC' }}\n*Address:* {{ $json.collection_address }}, {{ $json.postcode }}\n*Priority:* {{ $json.priority | upper }}\n*Supplier Assigned:* {{ $json.supplier_name }} ({{ $json.supplier_email }})\n*Order Value:* £{{ $json.payment_amount_gbp }}\n*Deal ID:* {{ $json.deal_id }}\n\nSearching for next available supplier and initiating 2-hour retry SLA now.",
        "otherOptions": {}
      },
      "credentials": { "slackApi": { "id": "slack-cred", "name": "Slack OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "find-next-supplier", "name": "Find Next Supplier", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [4940, 320],
      "parameters": {
        "resource": "contact",
        "operation": "getAll",
        "returnAll": false,
        "limit": 10,
        "filters": {
          "tag": "supplier-available"
        }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-next-supplier", "name": "Consolidate Next Supplier", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [5200, 320],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate SLA Status').first().json;\nconst firstSupplierId = ctx.supplier_id;\n// Skip the first supplier who already failed to respond\nconst available = rows.filter(r => !r.json.error && r.json.id && String(r.json.id) !== firstSupplierId);\nif (available.length === 0) {\n  return [{ json: {\n    ...ctx,\n    next_supplier_id:    null,\n    next_supplier_email: null,\n    next_supplier_name:  'NONE AVAILABLE',\n    next_supplier_found: false\n  }}];\n}\nconst next = available[0].json;\nreturn [{ json: {\n  ...ctx,\n  next_supplier_id:    String(next.id),\n  next_supplier_email: next.email,\n  next_supplier_name:  `${next.first_name || ''} ${next.last_name || ''}`.trim(),\n  next_supplier_phone: next.mobile_number || next.phone || '',\n  next_supplier_found: true\n}}];"
      }
    },

    {
      "id": "sni-reassign", "name": "Reassign note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [5465, 185],
      "parameters": { "content": "Sends reassignment request to next available supplier with escalation context", "height": 60, "width": 265 }
    },
    {
      "id": "send-reassignment", "name": "Send Reassignment Request", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [5460, 320],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"ops@yourcompany.com\",\n  \"To\": \"{{ $json.next_supplier_email || 'ops@yourcompany.com' }}\",\n  \"Subject\": \"URGENT — Job Reassignment {{ $json.order_ref }} ({{ $json.priority | upper }})\",\n  \"HtmlBody\": \"<h2>Urgent Job Reassignment</h2><p>Dear {{ $json.next_supplier_name }},</p><p>A job has been escalated and reassigned to you. Original supplier did not respond within SLA. <strong>Please confirm acceptance within 2 hours.</strong></p><table border='1' cellpadding='8' style='border-collapse:collapse'><tr><td><b>Order Ref</b></td><td>{{ $json.order_ref }}</td></tr><tr><td><b>Service</b></td><td>{{ $json.service_type }}</td></tr><tr><td><b>Address</b></td><td>{{ $json.collection_address }}, {{ $json.postcode }}</td></tr><tr><td><b>Skip Size</b></td><td>{{ $json.skip_size || 'TBC' }}</td></tr><tr><td><b>Waste Type</b></td><td>{{ $json.waste_type }}</td></tr><tr><td><b>Date Required</b></td><td>{{ $json.extracted.requested_date || 'ASAP' }}</td></tr><tr><td><b>Priority</b></td><td>{{ $json.priority | upper }}</td></tr><tr><td><b>Access Notes</b></td><td>{{ $json.access_restrictions || 'None' }}</td></tr><tr><td><b>Value</b></td><td>£{{ $json.payment_amount_gbp }}</td></tr></table><p>Deal ID: {{ $json.deal_id }} — Update stage to Accepted in Freshworks CRM to confirm.</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-wait-retry", "name": "Retry wait note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [5725, 185],
      "parameters": { "content": "Holds execution for 2 hours — the retry SLA window after reassignment", "height": 60, "width": 255 }
    },
    {
      "id": "wait-retry-2h", "name": "Retry Wait — 2 Hours", "type": "n8n-nodes-base.wait", "typeVersion": 1.1, "position": [5720, 320],
      "parameters": {
        "resume": "timeInterval",
        "time": { "unit": "hours", "value": 2 }
      },
      "webhookId": "wait-retry-2h-uuid-003"
    },

    {
      "id": "check-retry-status", "name": "Check Retry Deal Stage", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [5980, 320],
      "parameters": {
        "resource": "deal",
        "operation": "get",
        "dealId": "={{ $json.deal_id }}"
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "consolidate-retry", "name": "Consolidate Retry Status", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [6240, 320],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const rows = $input.all();\nconst ctx = $('Consolidate Next Supplier').first().json;\nconst deal = rows[0]?.json || {};\nconst stageName = (deal.deal_stage?.name || deal.stage_name || '').toLowerCase();\nconst acceptedStages = ['accepted', 'confirmed', 'partner confirmed', 'supplier accepted', 'in progress'];\nconst isAccepted = acceptedStages.some(s => stageName.includes(s));\nreturn [{ json: {\n  ...ctx,\n  retry_deal_stage:  stageName,\n  retry_accepted:    isAccepted,\n  retry_check_at:    new Date().toISOString(),\n  sla_attempt:       2\n}}];"
      }
    },

    {
      "id": "sni-retry-check", "name": "Retry accepted note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [6505, 185],
      "parameters": { "content": "Final decision: retry accepted triggers confirmation; failure triggers manual intervention alert", "height": 60, "width": 265 }
    },
    {
      "id": "retry-accepted", "name": "Retry Accepted?", "type": "n8n-nodes-base.if", "typeVersion": 2.2, "position": [6500, 320],
      "parameters": {
        "conditions": {
          "options": { "typeValidation": "loose" },
          "conditions": [{ "id": "cond-retry", "leftValue": "={{ $json.retry_accepted }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equal" } }],
          "combinator": "and"
        }
      }
    },

    {
      "id": "update-deal-retry", "name": "Update Deal — Confirmed (Retry)", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [6760, 160],
      "parameters": {
        "resource": "deal",
        "operation": "update",
        "dealId": "={{ $json.deal_id }}",
        "updateFields": { "stageId": "YOUR_CONFIRMED_STAGE_ID" }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "send-confirmed-retry", "name": "Send Customer — Confirmed (Retry)", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [7020, 160],
      "parameters": {
        "method": "POST",
        "url": "https://api.postmarkapp.com/email",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            { "name": "X-Postmark-Server-Token", "value": "YOUR_POSTMARK_SERVER_TOKEN" },
            { "name": "Accept", "value": "application/json" }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "body": "={\n  \"From\": \"orders@yourcompany.com\",\n  \"To\": \"{{ $('Consolidate Retry Status').first().json.customer_email }}\",\n  \"Subject\": \"Your Job is Confirmed — {{ $('Consolidate Retry Status').first().json.order_ref }}\",\n  \"HtmlBody\": \"<h2>Your job is confirmed!</h2><p>Dear {{ $('Consolidate Retry Status').first().json.customer_name }},</p><p>A supplier partner has confirmed your order <strong>{{ $('Consolidate Retry Status').first().json.order_ref }}</strong> and will be in contact shortly. Supplier: {{ $('Consolidate Retry Status').first().json.next_supplier_name }}. Thank you for your patience.</p>\",\n  \"MessageStream\": \"outbound\"\n}",
        "options": { "response": { "response": { "neverError": true } } }
      },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "log-success-retry", "name": "Log Success (Retry)", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [7280, 160],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": { "__rl": true, "value": "YOUR_FULFILLMENT_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Order Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp":    "={{ DateTime.now().toISO() }}",
            "Order Ref":    "={{ $('Consolidate Retry Status').first().json.order_ref }}",
            "Customer":     "={{ $('Consolidate Retry Status').first().json.customer_email }}",
            "Postcode":     "={{ $('Consolidate Retry Status').first().json.postcode }}",
            "Service":      "={{ $('Consolidate Retry Status').first().json.service_type }}",
            "Waste Type":   "={{ $('Consolidate Retry Status').first().json.waste_type }}",
            "Skip Size":    "={{ $('Consolidate Retry Status').first().json.skip_size }}",
            "Order Value":  "={{ $('Consolidate Retry Status').first().json.payment_amount_gbp }}",
            "Supplier":     "={{ $('Consolidate Retry Status').first().json.next_supplier_name }}",
            "SLA Attempt":  "2",
            "Status":       "confirmed_retry",
            "Deal ID":      "={{ $('Consolidate Retry Status').first().json.deal_id }}"
          }
        }
      },
      "credentials": { "googleSheetsOAuth2Api": { "id": "gsheets-cred", "name": "Google Sheets OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },

    {
      "id": "sni-alert-manual", "name": "Manual alert note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [6765, 385],
      "parameters": { "content": "Both SLA attempts failed — urgent Slack alert for immediate manual intervention", "height": 60, "width": 265 }
    },
    {
      "id": "alert-manual", "name": "Alert Slack — Manual Intervention", "type": "n8n-nodes-base.slack", "typeVersion": 2.2, "position": [6760, 520],
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": { "__rl": true, "value": "#ops-escalations", "mode": "name" },
        "text": "=:rotating_light: *CRITICAL — MANUAL INTERVENTION REQUIRED*\n\nBoth 4-hour and 2-hour SLA windows have expired with no supplier acceptance. This order requires immediate manual handling to avoid SLA breach penalty.\n\n*Order:* {{ $json.order_ref }}\n*Customer:* {{ $json.customer_name }} — {{ $json.customer_email }} — {{ $json.customer_phone }}\n*Service:* {{ $json.service_type }} — {{ $json.skip_size || 'TBC' }}\n*Address:* {{ $json.collection_address }}, {{ $json.postcode }}\n*Priority:* {{ $json.priority | upper }}\n*Order Value:* £{{ $json.payment_amount_gbp }}\n*Requested Date:* {{ $json.extracted.requested_date || 'ASAP' }}\n*Special Notes:* {{ $json.special_instructions || 'None' }}\n*Supplier 1 (failed):* {{ $json.supplier_name }}\n*Supplier 2 (failed):* {{ $json.next_supplier_name }}\n*Freshworks Deal:* {{ $json.deal_id }}\n\nDeal has been marked Escalated. Assign manually in Freshworks immediately.",
        "otherOptions": {}
      },
      "credentials": { "slackApi": { "id": "slack-cred", "name": "Slack OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "update-escalated", "name": "Update Deal — Escalated", "type": "n8n-nodes-base.freshworksCrm", "typeVersion": 1, "position": [7020, 520],
      "parameters": {
        "resource": "deal",
        "operation": "update",
        "dealId": "={{ $json.deal_id }}",
        "updateFields": { "stageId": "YOUR_ESCALATED_STAGE_ID" }
      },
      "credentials": { "freshworksCrm": { "id": "fw-cred", "name": "Freshworks CRM" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    },
    {
      "id": "log-escalated", "name": "Log Escalated", "type": "n8n-nodes-base.googleSheets", "typeVersion": 4.5, "position": [7280, 520],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": { "__rl": true, "value": "YOUR_FULFILLMENT_SHEET_ID", "mode": "id" },
        "sheetName": { "__rl": true, "value": "Order Log", "mode": "name" },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Timestamp":    "={{ DateTime.now().toISO() }}",
            "Order Ref":    "={{ $json.order_ref }}",
            "Customer":     "={{ $json.customer_email }}",
            "Postcode":     "={{ $json.postcode }}",
            "Service":      "={{ $json.service_type }}",
            "Waste Type":   "={{ $json.waste_type }}",
            "Skip Size":    "={{ $json.skip_size }}",
            "Order Value":  "={{ $json.payment_amount_gbp }}",
            "Supplier":     "={{ $json.supplier_name }} / {{ $json.next_supplier_name }}",
            "SLA Attempt":  "2",
            "Status":       "escalated_manual",
            "Deal ID":      "={{ $json.deal_id }}"
          }
        }
      },
      "credentials": { "googleSheetsOAuth2Api": { "id": "gsheets-cred", "name": "Google Sheets OAuth2" } },
      "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 2000, "onError": "continueErrorOutput"
    }
  ],

  "connections": {
    "New Order Webhook":           { "main": [[{ "node": "Validate & Parse Order",     "type": "main", "index": 0 }]] },
    "Validate & Parse Order":      { "main": [[{ "node": "Verify Stripe Payment",       "type": "main", "index": 0 }]] },
    "Verify Stripe Payment":       { "main": [[{ "node": "Consolidate Payment Result",  "type": "main", "index": 0 }]] },
    "Consolidate Payment Result":  { "main": [[{ "node": "Payment Succeeded?",          "type": "main", "index": 0 }]] },
    "Payment Succeeded?": {
      "main": [
        [{ "node": "Extract Order Details",    "type": "main", "index": 0 }],
        [{ "node": "Send Payment Failed Notice","type": "main", "index": 0 }]
      ]
    },
    "Send Payment Failed Notice":  { "main": [[{ "node": "Log Failed Payment",         "type": "main", "index": 0 }]] },
    "Claude — Extract":            { "ai_languageModel": [[{ "node": "Extract Order Details", "type": "ai_languageModel", "index": 0 }]] },
    "Order Details Schema":        { "ai_outputParser":  [[{ "node": "Extract Order Details", "type": "ai_outputParser",  "index": 0 }]] },
    "Extract Order Details":       { "main": [[{ "node": "Upsert Customer Contact",    "type": "main", "index": 0 }]] },
    "Upsert Customer Contact":     { "main": [[{ "node": "Consolidate Contact",        "type": "main", "index": 0 }]] },
    "Consolidate Contact":         { "main": [[{ "node": "Create Service Deal",        "type": "main", "index": 0 }]] },
    "Create Service Deal":         { "main": [[{ "node": "Consolidate Deal",           "type": "main", "index": 0 }]] },
    "Consolidate Deal":            { "main": [[{ "node": "Send Order Confirmation",    "type": "main", "index": 0 }]] },
    "Send Order Confirmation":     { "main": [[{ "node": "Find Available Supplier",   "type": "main", "index": 0 }]] },
    "Find Available Supplier":     { "main": [[{ "node": "Consolidate Supplier",       "type": "main", "index": 0 }]] },
    "Consolidate Supplier":        { "main": [[{ "node": "Send Supplier Assignment",  "type": "main", "index": 0 }]] },
    "Send Supplier Assignment":    { "main": [[{ "node": "SLA Wait — 4 Hours",        "type": "main", "index": 0 }]] },
    "SLA Wait — 4 Hours":          { "main": [[{ "node": "Check Deal Stage",          "type": "main", "index": 0 }]] },
    "Check Deal Stage":            { "main": [[{ "node": "Consolidate SLA Status",    "type": "main", "index": 0 }]] },
    "Consolidate SLA Status":      { "main": [[{ "node": "Supplier Accepted SLA?",    "type": "main", "index": 0 }]] },
    "Supplier Accepted SLA?": {
      "main": [
        [{ "node": "Update Deal — Confirmed",      "type": "main", "index": 0 }],
        [{ "node": "Alert Slack — SLA Missed",     "type": "main", "index": 0 }]
      ]
    },
    "Update Deal — Confirmed":          { "main": [[{ "node": "Send Customer — Job Confirmed",   "type": "main", "index": 0 }]] },
    "Send Customer — Job Confirmed":    { "main": [[{ "node": "Log Success",                      "type": "main", "index": 0 }]] },
    "Alert Slack — SLA Missed":         { "main": [[{ "node": "Find Next Supplier",               "type": "main", "index": 0 }]] },
    "Find Next Supplier":               { "main": [[{ "node": "Consolidate Next Supplier",        "type": "main", "index": 0 }]] },
    "Consolidate Next Supplier":        { "main": [[{ "node": "Send Reassignment Request",        "type": "main", "index": 0 }]] },
    "Send Reassignment Request":        { "main": [[{ "node": "Retry Wait — 2 Hours",             "type": "main", "index": 0 }]] },
    "Retry Wait — 2 Hours":             { "main": [[{ "node": "Check Retry Deal Stage",           "type": "main", "index": 0 }]] },
    "Check Retry Deal Stage":           { "main": [[{ "node": "Consolidate Retry Status",         "type": "main", "index": 0 }]] },
    "Consolidate Retry Status":         { "main": [[{ "node": "Retry Accepted?",                  "type": "main", "index": 0 }]] },
    "Retry Accepted?": {
      "main": [
        [{ "node": "Update Deal — Confirmed (Retry)",    "type": "main", "index": 0 }],
        [{ "node": "Alert Slack — Manual Intervention",  "type": "main", "index": 0 }]
      ]
    },
    "Update Deal — Confirmed (Retry)":   { "main": [[{ "node": "Send Customer — Confirmed (Retry)", "type": "main", "index": 0 }]] },
    "Send Customer — Confirmed (Retry)": { "main": [[{ "node": "Log Success (Retry)",               "type": "main", "index": 0 }]] },
    "Alert Slack — Manual Intervention": { "main": [[{ "node": "Update Deal — Escalated",           "type": "main", "index": 0 }]] },
    "Update Deal — Escalated":           { "main": [[{ "node": "Log Escalated",                     "type": "main", "index": 0 }]] }
  },

  "pinData": {},
  "settings": { "executionOrder": "v1", "saveManualExecutions": true, "callerPolicy": "workflowsFromSameOwner" },
  "staticData": null,
  "tags": ["Operations", "Stripe", "Freshworks CRM", "Postmark", "Slack", "Google Sheets", "AI", "SLA", "Order Management", "Service Brokerage"],
  "triggerCount": 1,
  "active": false
}
