{
  "name": "Thalura sample: stockout to restock (Shopify, Gmail, Slack, Klaviyo)",
  "nodes": [
    {"parameters": {"httpMethod": "POST", "path": "shopify-inventory", "responseMode": "onReceived", "options": {}}, "id": "a1", "name": "Shopify inventory webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [0, 300], "notes": "Shopify Admin > Notifications > Webhooks > Inventory level update, pointed at this URL. Verify the HMAC header in production."},
    {"parameters": {"jsCode": "// Normalize the Shopify payload and attach SKU settings.\n// REORDER holds the reorder point and supplier for each inventory item.\nconst REORDER = {\n  // 'inventory_item_id': { sku: 'BK-BEEF-5', reorder_point: 40, order_qty: 240, supplier_email: 'orders@supplier.example' }\n};\nconst p = $json.body || $json;\nconst cfg = REORDER[String(p.inventory_item_id)] || { sku: String(p.inventory_item_id), reorder_point: 30, order_qty: 200, supplier_email: 'orders@supplier.example' };\nconst prev = $getWorkflowStaticData('global')[p.inventory_item_id] ?? null;\n$getWorkflowStaticData('global')[p.inventory_item_id] = p.available;\nreturn [{ json: { ...cfg, inventory_item_id: p.inventory_item_id, available: p.available, previous: prev, restocked: prev !== null && prev <= 0 && p.available > 0, low: p.available <= cfg.reorder_point && (prev === null || prev > cfg.reorder_point) } }];"}, "id": "a2", "name": "Normalize + settings", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [240, 300]},
    {"parameters": {"conditions": {"options": {"caseSensitive": true, "typeValidation": "strict"}, "conditions": [{"leftValue": "={{ $json.low }}", "rightValue": true, "operator": {"type": "boolean", "operation": "true", "singleValue": true}}], "combinator": "and"}, "options": {}}, "id": "a3", "name": "Crossed reorder point?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [480, 180]},
    {"parameters": {"conditions": {"options": {"caseSensitive": true, "typeValidation": "strict"}, "conditions": [{"leftValue": "={{ $json.restocked }}", "rightValue": true, "operator": {"type": "boolean", "operation": "true", "singleValue": true}}], "combinator": "and"}, "options": {}}, "id": "a4", "name": "Back in stock?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [480, 420]},
    {"parameters": {"resource": "draft", "subject": "=PO request: {{ $json.sku }} x {{ $json.order_qty }}", "emailType": "text", "message": "=Hi,\n\nWe'd like to order {{ $json.order_qty }} units of {{ $json.sku }}. Current stock is {{ $json.available }}.\n\nPlease confirm price and ship date.\n\nThanks", "options": {"sendTo": "={{ $json.supplier_email }}"}}, "id": "a5", "name": "Draft supplier PO (Gmail)", "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [720, 120], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 5000},
    {"parameters": {"select": "channel", "channelId": {"__rl": true, "value": "#ops", "mode": "name"}, "text": "=:package: {{ $json.sku }} is at {{ $json.available }} (reorder point {{ $json.reorder_point }}). A PO draft for {{ $json.order_qty }} is waiting in Gmail. Approve and send.", "otherOptions": {}}, "id": "a6", "name": "Alert ops (Slack)", "type": "n8n-nodes-base.slack", "typeVersion": 2.2, "position": [960, 120], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 5000},
    {"parameters": {"method": "POST", "url": "https://a.klaviyo.com/api/events", "authentication": "genericCredentialType", "genericAuthType": "httpHeaderAuth", "sendHeaders": true, "headerParameters": {"parameters": [{"name": "revision", "value": "2024-10-15"}, {"name": "accept", "value": "application/vnd.api+json"}]}, "sendBody": true, "specifyBody": "json", "jsonBody": "={ \"data\": { \"type\": \"event\", \"attributes\": { \"properties\": { \"sku\": \"{{ $json.sku }}\", \"available\": {{ $json.available }} }, \"metric\": { \"data\": { \"type\": \"metric\", \"attributes\": { \"name\": \"Product Restocked\" } } }, \"profile\": { \"data\": { \"type\": \"profile\", \"attributes\": { \"email\": \"ops-trigger@yourstore.example\" } } } } } }", "options": {}}, "id": "a7", "name": "Klaviyo: Product Restocked event", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [720, 480], "retryOnFail": true, "maxTries": 3, "waitBetweenTries": 5000, "notes": "Klaviyo's built-in back-in-stock flow covers waitlist emails. This event lets a custom flow or segment react too."}
  ],
  "connections": {
    "Shopify inventory webhook": {"main": [[{"node": "Normalize + settings", "type": "main", "index": 0}]]},
    "Normalize + settings": {"main": [[{"node": "Crossed reorder point?", "type": "main", "index": 0}, {"node": "Back in stock?", "type": "main", "index": 0}]]},
    "Crossed reorder point?": {"main": [[{"node": "Draft supplier PO (Gmail)", "type": "main", "index": 0}], []]},
    "Draft supplier PO (Gmail)": {"main": [[{"node": "Alert ops (Slack)", "type": "main", "index": 0}]]},
    "Back in stock?": {"main": [[{"node": "Klaviyo: Product Restocked event", "type": "main", "index": 0}], []]}
  },
  "settings": {"executionOrder": "v1", "saveDataErrorExecution": "all", "errorWorkflow": "SET-YOUR-ERROR-ALERT-WORKFLOW-ID"},
  "meta": {"templateCredsSetupCompleted": false, "notes": "Sample by Thalura (thalura.com). Add your Gmail, Slack and Klaviyo credentials, set REORDER in the Code node, and point an error workflow at your Slack to get a human alert after 3 failed retries."}
}
