{
  "id": "MScSPanoLkQwiPVD",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Shopify No Orders in X Minutes Alert",
  "tags": [],
  "nodes": [
    {
      "id": "ba543263-fd96-4d1f-a74a-caed71219a50",
      "name": "\ud83d\udcd6 Complete Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13440,
        6256
      ],
      "parameters": {
        "width": 724,
        "height": 1130,
        "content": "## \ud83d\uded2 Shopify \"No Orders in X Minutes\" Alert\n\n### Who It's For\nEcommerce teams, store owners, and DevOps who need to know immediately when orders stop flowing.\n\n### What It Does\nMonitors your Shopify store for new orders at regular intervals (default: 30 min). Tracks downtime duration and sends rich alerts (Slack & HTML Email). Automatically resets when orders resume or if data is stale (>24h).\n\n### How It Works\n1. Schedule Trigger runs at set interval\n2. Config Node validates settings\n3. Fetch Orders pulls orders from Shopify (dynamic lookback)\n4. Orders Found? branches: YES \u2192 reset counters | NO \u2192 send alert\n5. Build Alert creates formatted message with downtime details\n6. Route sends to Slack/Email based on configuration\n7. Error Handler catches API failures and alerts team\n\n### How to Set Up\n1. Connect Shopify OAuth2 to Fetch Orders node\n2. Connect Slack & Gmail accounts to alert nodes\n3. Open the Slack node and manually select your target channel from the dropdown.\n4. Open Configuration node and set:\n   \u2022 enableSlackAlert: true/false\n   \u2022 enableEmailAlert: true/false\n   \u2022 enableRecoveryNotification: true/false\n   \u2022 sendEmailTo: your.email@example.com\n5. Toggle workflow to ACTIVE\n\n### Requirements\n- n8n version 2.11.3+\n- Shopify OAuth2 (orders:read scope)\n- Slack OAuth (chat:write scope) - optional\n- Gmail OAuth (gmail.send scope) - optional\n- Workflow static data support ($getWorkflowStaticData)\n\n### Version Info\n**Version:** 1.2\n**Status:** \u2705 Production Ready\n**Features:** \u2022 Error handling \u2022 Config validation \u2022 Multi-channel alerts \u2022 Recovery Notifications"
      },
      "typeVersion": 1
    },
    {
      "id": "006a7784-02bb-416e-9c1d-638efd2481ce",
      "name": "Group: Validation & Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16128,
        6432
      ],
      "parameters": {
        "color": 4,
        "width": 640,
        "height": 584,
        "content": "### 4\ufe0f\u20e3 VALIDATION & ROUTING\nRoutes structured alerts to enabled channels using Slack Block Kit and HTML Email templates."
      },
      "typeVersion": 1
    },
    {
      "id": "f31872c1-d168-42d4-846b-6c147e553967",
      "name": "Handle API Error",
      "type": "n8n-nodes-base.code",
      "position": [
        15648,
        6880
      ],
      "parameters": {
        "jsCode": "// Handle Shopify API errors gracefully\nconst globalData = $getWorkflowStaticData('global');\nconst intervalMinutes = $('Schedule Trigger').params.rule.interval[0].minutesInterval || 30;\nconst currentTime = $now.toFormat('yyyy-MM-dd HH:mm:ss');\n\n// Increment specific API error counter\nglobalData.apiErrorCount = globalData.apiErrorCount ? globalData.apiErrorCount + 1 : 1;\n\nif (!globalData.firstFailingTime) {\n    globalData.firstFailingTime = currentTime;\n}\n\nconst totalDowntimeMinutes = globalData.apiErrorCount * intervalMinutes;\nconst errorMessage = $input.first().json.error?.message || 'Unknown API error';\n\nconst alertMessage = `\u26a0\ufe0f *SHOPIFY API ERROR* (Consecutive API Error #${globalData.apiErrorCount})\\n\\n\u23f0 Time: ${currentTime}\\n\ud83d\udd52 Failing Since: ${globalData.firstFailingTime} (~${totalDowntimeMinutes} mins)\\n\u274c Error: ${errorMessage}\\n\\n\u26a1 Action Required:\\n  \u2022 Check Shopify status page\\n  \u2022 Verify API credentials\\n  \u2022 Check n8n logs for details\\n  \u2022 Your store may be unreachable`;\n\nreturn {json: {\n  alertMessage: alertMessage,\n  apiErrorCount: globalData.apiErrorCount,\n  firstFailingTime: globalData.firstFailingTime,\n  totalDowntimeMinutes: totalDowntimeMinutes,\n  isApiError: true,\n  isTestMode: false,\n  headerIcon: \"\u26a0\ufe0f\",\n  headerTitle: \"SHOPIFY API ERROR\",\n  themeColor: \"#dd6b20\"\n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "d51776cd-c5b3-43e8-87ab-0449d1f9e745",
      "name": "Group: Setup1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        14208,
        6432
      ],
      "parameters": {
        "color": 6,
        "width": 780,
        "height": 588,
        "content": "### 1\ufe0f\u20e3 SETUP & CONFIG\nConfigure alert channels. Config validation prevents silent failures and unrouted alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "3ff8013f-2ff7-4fd5-a35d-8fefa6f115e3",
      "name": "Group: Monitoring1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        15040,
        6432
      ],
      "parameters": {
        "color": 5,
        "width": 460,
        "height": 584,
        "content": "### 2\ufe0f\u20e3 LIVE MONITORING\nFetches orders from Shopify."
      },
      "typeVersion": 1
    },
    {
      "id": "e8854a73-9f1b-4aa4-83be-94fc059d3be0",
      "name": "Group: State1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        15536,
        6432
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 588,
        "content": "### 3\ufe0f\u20e3 STATE TRACKING\nUses static data to measure downtime. Triggers recovery alerts when orders resume."
      },
      "typeVersion": 1
    },
    {
      "id": "454f7935-996e-4bc7-a172-8a125558934a",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        14240,
        6608
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 30
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "2e2d0b81-f1d4-452f-a9e7-e71d862921cd",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        14416,
        6608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
              "name": "enableSlackAlert",
              "type": "boolean",
              "value": false
            },
            {
              "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
              "name": "enableEmailAlert",
              "type": "boolean",
              "value": false
            },
            {
              "id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
              "name": "sendEmailTo",
              "type": "string",
              "value": "PASTE_RECIPIENT_EMAIL_HERE"
            },
            {
              "id": "df8fdb10-51c6-4c55-ba2e-6a6c86269d36",
              "name": "enableRecoveryNotification",
              "type": "boolean",
              "value": false
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d3c2b1b5-a89e-477e-af10-8bedfd45265f",
      "name": "Validate Email Config?",
      "type": "n8n-nodes-base.if",
      "position": [
        14784,
        6608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1v2b3n4-m5q6-w7e8-r9t0-y1u2i3o4p5",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableEmailAlert }}",
              "rightValue": true
            },
            {
              "id": "a1s2d3f4-g5h6-j7k8-l9z0-x1c2v3b4n5",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Configuration').item.json.sendEmailTo }}",
              "rightValue": "PASTE_RECIPIENT_EMAIL_HERE"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "3ce3b545-c5e7-4bf4-a4a0-902291de1ecb",
      "name": "All Channels Disabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        14576,
        6608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "x1y2z3a4",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableSlackAlert }}",
              "rightValue": false
            },
            {
              "id": "b5c6d7e8",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableEmailAlert }}",
              "rightValue": false
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "a787bc8a-221a-4627-800c-18a692f5202a",
      "name": "Config Error",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        14784,
        6816
      ],
      "parameters": {
        "errorMessage": "Validation Failed: You must enable at least one primary alert channel (Slack or Email), and provide a valid email address if Email Alerts are enabled. Enabling only Recovery notifications is not allowed."
      },
      "typeVersion": 1
    },
    {
      "id": "dee26b50-a858-4d94-8101-dfb661df5873",
      "name": "Build Alert Message",
      "type": "n8n-nodes-base.code",
      "position": [
        15648,
        6688
      ],
      "parameters": {
        "jsCode": "const globalData = $getWorkflowStaticData('global');\nconst intervalMinutes = $('Schedule Trigger').params.rule.interval[0].minutesInterval || 30;\nconst currentTime = $now.toFormat('yyyy-MM-dd HH:mm:ss');\n\n// Handle Stale Data Check\nif (globalData.firstFailingTime) {\n  const hoursSinceFirst = $now.diff(DateTime.fromISO(globalData.firstFailingTime), 'hours').hours;\n  if (hoursSinceFirst > 24) {\n    delete globalData.firstFailingTime;\n    delete globalData.failedCount;\n  }\n}\n\nglobalData.failedCount = globalData.failedCount ? globalData.failedCount + 1 : 1;\n\nif (!globalData.firstFailingTime) {\n    globalData.firstFailingTime = currentTime;\n}\n\nconst totalDowntimeMinutes = globalData.failedCount * intervalMinutes;\n\n// Determine context mode\nconst headerIcon = \"\ud83d\udea8\";\nconst headerTitle = \"NO ORDERS ALERT\";\n\nconst alertMessage = `${headerIcon} *${headerTitle}* (Consecutive Alert #${globalData.failedCount})\\n\\n\u23f0 Time Checked: ${currentTime}\\n\ud83d\udd52 Failing Since: ${globalData.firstFailingTime} (~${totalDowntimeMinutes} mins without new orders)\\n\ud83d\udcca Status: No orders found in the last ${intervalMinutes} minutes.\\n\u26a0\ufe0f Action Required: Please check your Shopify store for:\\n  \u2022 Payment gateway issues\\n  \u2022 Checkout errors\\n  \u2022 Traffic anomalies\\n  \u2022 API connectivity`;\n\nreturn {json: {\n  alertMessage: alertMessage,\n  failedCount: globalData.failedCount,\n  firstFailingTime: globalData.firstFailingTime,\n  totalDowntimeMinutes: totalDowntimeMinutes,\n  headerIcon: headerIcon,\n  headerTitle: headerTitle,\n  themeColor: \"#e53e3e\"\n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "a1390a0f-24d7-453b-8be3-55658e8fec3c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16480,
        6496
      ],
      "parameters": {
        "color": 6,
        "width": 250,
        "height": 90,
        "content": "\u26a0\ufe0f **ACTION REQUIRED**\nOpen this node and select your Slack channel from the dropdown before activating the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "30cc8795-d656-49e0-9c6c-7e6c69ca6965",
      "name": "Alert Team via Slack",
      "type": "n8n-nodes-base.slack",
      "onError": "continueRegularOutput",
      "position": [
        16496,
        6608
      ],
      "parameters": {
        "text": "={{ $json.alertMessage }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "7db49dfb-ddbb-4db6-85c2-baa08669681f",
      "name": "Alert Team via Email",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueRegularOutput",
      "position": [
        16496,
        6816
      ],
      "parameters": {
        "sendTo": "={{ $('Configuration').item.json.sendEmailTo }}",
        "message": "=<div style=\"font-family:sans-serif;border-left:4px solid {{ $json.themeColor || '#e53e3e' }};padding:16px;\">\n  <h2 style=\"color:{{ $json.themeColor || '#e53e3e' }};\">{{ $json.headerIcon }} {{ $json.headerTitle }}</h2>\n  <p><strong>Status:</strong> {{ $json.isRecovery ? 'Orders have resumed.' : 'Downtime ongoing.' }}</p>\n  <p><strong>Approx Downtime:</strong> {{ $json.totalDowntimeMinutes }} minutes</p>\n  <p><strong>Failing Since:</strong> {{ $json.firstFailingTime }}</p>\n  <br>\n  <p><strong>{{ $json.isRecovery ? '\u2705 Recovery Actions:' : '\u26a0\ufe0f Action Required:' }}</strong></p>\n  <ul style=\"color:#4a5568;\">\n    <li>{{ $json.isRecovery ? 'Verify order data looks correct in admin' : 'Check Shopify status page' }}</li>\n    <li>{{ $json.isRecovery ? 'Monitor for any lingering sync issues' : 'Verify payment gateway connectivity' }}</li>\n    <li>{{ $json.isRecovery ? 'Check n8n logs for API error details (if any)' : 'Investigate checkout errors / Rate Limits' }}</li>\n  </ul>\n</div>",
        "options": {},
        "subject": "={{ $json.headerIcon }} {{ $json.headerTitle }} - Notification"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "4c3c1729-b28a-4599-8e69-209e053fb355",
      "name": "Orders Resumed \u2014 Reset Counters",
      "type": "n8n-nodes-base.code",
      "position": [
        15648,
        6496
      ],
      "parameters": {
        "jsCode": "const globalData = $getWorkflowStaticData('global');\nconst intervalMinutes = $('Schedule Trigger').params.rule.interval[0].minutesInterval || 30;\n\nlet wasFailing = globalData.failedCount > 0 || globalData.apiErrorCount > 0;\nlet downtimeMinutes = ((globalData.failedCount || 0) + (globalData.apiErrorCount || 0)) * intervalMinutes;\nlet failingSince = globalData.firstFailingTime || 'Unknown';\n\n// Clear all tracking variables\ndelete globalData.failedCount;\ndelete globalData.apiErrorCount;\ndelete globalData.firstFailingTime;\n\n// If we were previously failing, generate a recovery payload\nif (wasFailing) {\n  const headerIcon = \"\u2705\";\n  const headerTitle = \"SYSTEM RECOVERED\";\n  const alertMessage = `${headerIcon} *${headerTitle}*\\n\\n\ud83c\udf89 Orders are successfully flowing again!\\n\ud83d\udd52 Outage Started: ${failingSince}\\n\u23f1\ufe0f Approx Downtime: ${downtimeMinutes} mins`;\n\n  return [{ json: {\n    alertMessage: alertMessage,\n    totalDowntimeMinutes: downtimeMinutes,\n    firstFailingTime: failingSince,\n    isRecovery: true,\n    headerIcon: headerIcon,\n    headerTitle: headerTitle,\n    themeColor: \"#38a169\"\n  }}];\n}\n\n// Always return an item so the downstream IF node can cleanly stop execution\nreturn [{ json: { \n  isRecovery: false, \n  message: \"No active outage to recover from. Counters cleared.\"\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "5f0b43df-efe3-4f09-abee-d0be9065fa90",
      "name": "Slack Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        16208,
        6624
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3b16f6ce-394e-4065-8a69-fcba5e78c140",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableSlackAlert }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d949d8d3-65a8-4c1b-a26f-2afff53d6a76",
      "name": "Email Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        16208,
        6832
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f3f262f5-09a3-4b18-b6d1-ae35ec776faa",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableEmailAlert }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "397a97d8-4fd4-45ae-a967-5be1260a2cd0",
      "name": "Orders Found?",
      "type": "n8n-nodes-base.if",
      "position": [
        15296,
        6544
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "14183057-8344-46ed-b5b7-00d7b1575929",
              "operator": {
                "type": "object",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $input.item.json }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b597108d-422f-482b-a86a-6748fd934bb8",
      "name": "Fetch Orders",
      "type": "n8n-nodes-base.shopify",
      "onError": "continueErrorOutput",
      "position": [
        15136,
        6720
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "createdAtMin": "={{ $now.minus({ minutes: $(\"Schedule Trigger\").params.rule.interval?.[0]?.minutesInterval || 30 }).toISO() }}"
        },
        "operation": "getAll",
        "authentication": "oAuth2"
      },
      "credentials": {
        "shopifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "f6913565-d598-44b8-b106-b7ee70c047a5",
      "name": "Recovery Enabled?",
      "type": "n8n-nodes-base.if",
      "position": [
        15888,
        6496
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1d2e3f4-g5h6-i7j8-k9l0-m1n2o3p4q5",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isRecovery }}",
              "rightValue": true
            },
            {
              "id": "e5485374-cd9e-43af-98f6-8a2f981c0f3e",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $('Configuration').item.json.enableRecoveryNotification }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "timeSavedMode": "fixed",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "c9b9a508-434f-46f6-adf0-e3d4ca51c466",
  "connections": {
    "Fetch Orders": {
      "main": [
        [
          {
            "node": "Orders Found?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Handle API Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "All Channels Disabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Orders Found?": {
      "main": [
        [
          {
            "node": "Orders Resumed \u2014 Reset Counters",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Alert Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Enabled?": {
      "main": [
        [
          {
            "node": "Alert Team via Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Enabled?": {
      "main": [
        [
          {
            "node": "Alert Team via Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Handle API Error": {
      "main": [
        [
          {
            "node": "Slack Enabled?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Recovery Enabled?": {
      "main": [
        [
          {
            "node": "Slack Enabled?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Alert Message": {
      "main": [
        [
          {
            "node": "Slack Enabled?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "All Channels Disabled?": {
      "main": [
        [
          {
            "node": "Config Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Validate Email Config?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Email Config?": {
      "main": [
        [
          {
            "node": "Config Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Orders Resumed \u2014 Reset Counters": {
      "main": [
        [
          {
            "node": "Recovery Enabled?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}