AutomationFlowsEmail & Gmail › Monitor Shopify Dispatch Sla Breaches with Google Sheets, Slack and Gmail

Monitor Shopify Dispatch Sla Breaches with Google Sheets, Slack and Gmail

ByTricore Infotech Pvt Ltd @jinitp on n8n.io

This workflow is built to monitor your Shopify store every hour and automatically detect open, unfulfilled orders that have gone past your dispatch SLA window. The moment a breach is detected, alerts go out to Slack and Email instantly, helping your team respond faster, reduce…

Event trigger★★★★☆ complexity18 nodesShopifyGoogle SheetsSlackGmailError Trigger
Email & Gmail Trigger: Event Nodes: 18 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15941 — we link there as the canonical source.

This workflow follows the Error Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "DrWPJJfAGhghjLvK",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Shopify Dispatch SLA Breach Monitor",
  "tags": [],
  "nodes": [
    {
      "id": "de9539d9-f8f3-4d14-abfe-45b9cf9d458e",
      "name": "Zone 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4832,
        -320
      ],
      "parameters": {
        "color": 2,
        "width": 848,
        "height": 876,
        "content": "## 3. \ud83d\udce4 Logging & Multi-Channel Alerts\n\n**Log Breach to Sheets**\nAppends one new row per breach run with:\n- `runTimestamp` \u2014 UTC datetime of this check\n- `unfulfilledOrderIds` \u2014 comma-separated new breached order IDs (powers future deduplication)\n- `vipOrderIds` \u2014 comma-separated VIP order IDs (or `None`)\n- `totalOrders` \u2014 count of net-new breaches this run\n- `alertStatus` \u2014 always `Yes` (only reached when breaches exist)\n\n**Slack - Post SLA Breach Alert**\nPosts a plain-text mrkdwn breach summary to the configured escalation channel.\n\n**Generate Email HTML**\nDedicated node that builds the full HTML email body from processed order data.\n\n**Email SLA Breach Alert**\nSends the HTML email to `recipientMail`. Subject line includes breach count and SLA hours for quick inbox scanning.\n\n**Global Error Trigger \u2192 Slack - Send Workflow Error Alert**\nCatches any node failure across the workflow and posts the failed node name and error message to Slack for immediate debugging."
      },
      "typeVersion": 1
    },
    {
      "id": "7b9fbb1e-0a53-4809-af20-959c2868bfeb",
      "name": "Set SLA Config",
      "type": "n8n-nodes-base.set",
      "position": [
        3744,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "sla",
              "name": "slaHours",
              "type": "number",
              "value": 48
            },
            {
              "id": "vip",
              "name": "vipThreshold",
              "type": "number",
              "value": 3000
            },
            {
              "id": "mail",
              "name": "recipientMail",
              "type": "string",
              "value": "PASTE_RECIPIENT_MAIL_HERE"
            },
            {
              "id": "sheet",
              "name": "googleSheetUrl",
              "type": "string",
              "value": "PASTE_GOOGLE_SHEET_URL_HERE"
            },
            {
              "id": "slack",
              "name": "slackEscalationChannel",
              "type": "string",
              "value": "PASTE_SLACK_CHANNEL_ID_HERE"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7f8d2c3b-0de2-423e-a67e-6cc167b09725",
      "name": "Fetch Unfulfilled Orders",
      "type": "n8n-nodes-base.shopify",
      "position": [
        4016,
        160
      ],
      "parameters": {
        "limit": 250,
        "options": {
          "status": "open",
          "createdAtMax": "={{ $now.minus({ hours: $json.slaHours }).toISO() }}",
          "fulfillmentStatus": "unfulfilled"
        },
        "operation": "getAll",
        "authentication": "oAuth2"
      },
      "credentials": {
        "shopifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": false
    },
    {
      "id": "64215b3d-b846-47c7-8e56-920711dd45f3",
      "name": "Read Previous Sheet Logs",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        4240,
        160
      ],
      "parameters": {
        "options": {},
        "returnAll": true,
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $('Set SLA Config').item.json.googleSheetUrl }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "974fe0cb-9009-458a-b67d-1d90227d0caf",
      "name": "Process & Deduplicate",
      "type": "n8n-nodes-base.code",
      "position": [
        4464,
        160
      ],
      "parameters": {
        "jsCode": "const shopifyOrders = $('Fetch Unfulfilled Orders').all().map(i => i.json);\nconst sheetLogs = $('Read Previous Sheet Logs').all().map(i => i.json);\nconst config = $('Set SLA Config').first().json;\n\nconst slaHours = config.slaHours || 48;\nconst vipThreshold = config.vipThreshold || 3000;\nconst now = new Date();\n\nif (shopifyOrders.length === 0 || Object.keys(shopifyOrders[0]).length === 0) {\n  return [];\n}\n\nconst alertedSet = new Set();\nsheetLogs.forEach(row => {\n  const val = row.unfulfilledOrderIds || '';\n  String(val).split(',').forEach(id => {\n    const cleanId = id.trim();\n    if (cleanId) alertedSet.add(cleanId);\n  });\n});\n\nconst unfulfilledOrders = [];\nconst vipOrders = [];\n\nshopifyOrders.forEach(order => {\n  const createdAt = new Date(order.created_at);\n  const ageHours = (now.getTime() - createdAt.getTime()) / 3600000;\n  // Double-check age in case pinned data is used during testing\n  if (ageHours <= slaHours) return [];\n  const orderId = String(order.id);\n  if (alertedSet.has(orderId)) return [];\n  const value = parseFloat(order.current_total_price || order.total_price || '0');\n  const currency = order.currency || 'INR';\n  unfulfilledOrders.push({ orderId, value, currency });\n  if (value >= vipThreshold) vipOrders.push(orderId);\n});\n\nif (unfulfilledOrders.length === 0) return [];\n\nconst highestOrder = unfulfilledOrders.reduce(\n  (max, o) => o.value > max.value ? o : max,\n  { value: 0, currency: 'INR' }\n);\n\nconst currencySymbolMap = { INR: '\u20b9', USD: '$', EUR: '\u20ac', GBP: '\u00a3', AED: 'AED ' };\nconst currencySymbol = currencySymbolMap[highestOrder.currency] || `${highestOrder.currency} `;\nconst highestValueFormatted = highestOrder.value.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n\nconst pad = n => String(n).padStart(2, '0');\nconst runTimestamp = `${now.getUTCFullYear()}-${pad(now.getUTCMonth() + 1)}-${pad(now.getUTCDate())} ${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}`;\n\nconst orderCount = unfulfilledOrders.length;\n\nconst vipLine = vipOrders.length > 0\n  ? `\\n>\ud83c\udf1f *VIP Orders (\u2265${currencySymbol}${vipThreshold.toLocaleString('en-IN')}):* ${vipOrders.join(', ')}`\n  : '';\n\nconst slackSummary = [\n  `\ud83d\udea8 *SLA Breach Alert \u2014 ${orderCount} Unfulfilled Order${orderCount > 1 ? 's' : ''}*`,\n  ``,\n  `*${orderCount}* order${orderCount > 1 ? 's have' : ' has'} exceeded the *${slaHours}-hour SLA* and require immediate attention.`,\n  ``,\n  `\ud83d\udce6 *Breached Orders:* ${orderCount}`,\n  `\ud83d\udcb0 *Highest Order Value:* ${currencySymbol}${highestValueFormatted}`,\n  `\u23f1 *SLA Threshold:* ${slaHours} hours`,\n  `\ud83c\udf1f *VIP Orders:* ${vipOrders.length > 0 ? vipOrders.length : 'None'}`,\n  ``,\n  `\ud83d\udccb *Order IDs:* \\`${unfulfilledOrders.map(o => o.orderId).join(', ')}\\`${vipLine}`,\n  ``,\n  `\ud83d\udd50 _Detected at ${runTimestamp} UTC \u00b7 Order SLA Monitor_`\n].join('\\n');\n\nconst emailSubject = `\ud83d\udea8 Action Required: ${orderCount} Unfulfilled Order${orderCount > 1 ? 's' : ''} Breached ${slaHours}h SLA`;\n\nreturn [{\n  json: {\n    runTimestamp,\n    unfulfilledOrderIds: unfulfilledOrders.map(o => o.orderId).join(', '),\n    vipOrderIds: vipOrders.length > 0 ? vipOrders.join(', ') : 'None',\n    totalOrders: orderCount,\n    alertStatus: 'Yes',\n    slackSummary,\n    emailSubject,\n    orderCount,\n    slaHours,\n    currencySymbol,\n    highestValueFormatted,\n    vipOrderIdsArray: vipOrders.join(', '),\n    vipCount: vipOrders.length,\n    vipThreshold,\n    allOrderIds: unfulfilledOrders.map(o => o.orderId).join(', ')\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "736fb619-2b48-4f4b-b452-75d0584c2281",
      "name": "Log Breach to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4896,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "alertStatus": "={{ $json.alertStatus }}",
            "totalOrders": "={{ $json.totalOrders }}",
            "vipOrderIds": "={{ $json.vipOrderIds }}",
            "runTimestamp": "={{ $json.runTimestamp }}",
            "unfulfilledOrderIds": "={{ $json.unfulfilledOrderIds }}"
          },
          "schema": [
            {
              "id": "runTimestamp",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "runTimestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "unfulfilledOrderIds",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "unfulfilledOrderIds",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "vipOrderIds",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "vipOrderIds",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "totalOrders",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "totalOrders",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "alertStatus",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "alertStatus",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $('Set SLA Config').item.json.googleSheetUrl }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "cbd734a6-9123-4ccd-ac8b-6f9a647d35ae",
      "name": "Slack - Post Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        5344,
        160
      ],
      "parameters": {
        "text": "={{ $('Process & Deduplicate').item.json.slackSummary }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set SLA Config').item.json.slackEscalationChannel }}"
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "83987994-1db4-4055-a7f9-909e078b88dd",
      "name": "Email SLA Breach Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5344,
        336
      ],
      "parameters": {
        "sendTo": "={{ $('Set SLA Config').item.json.recipientMail }}",
        "message": "={{ $json.emailBody }}",
        "options": {
          "emailType": "html"
        },
        "subject": "={{ $json.emailSubject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "fab5363f-ffdc-41d9-a0f0-3b5b09e147af",
      "name": "Global Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        3488,
        912
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ead06ac0-e991-44df-93dd-dce0ee75c38a",
      "name": "Slack - Error Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        3728,
        912
      ],
      "parameters": {
        "text": "={{ '\ud83d\udea8 *Workflow Error \u2014 SLA Breach Monitor*\\n\\n*Failed Node:* ' + $json.execution.error.node.name + '\\n*Error:* ' + $json.execution.error.message }}",
        "user": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "select": "=channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "="
        },
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "24203542-3e13-4491-b746-3e1b9282b77e",
      "name": "Template Overview1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2640,
        -320
      ],
      "parameters": {
        "width": 748,
        "height": 1504,
        "content": "# \ud83d\udea8 Shopify Dispatch SLA Breach Monitor \u2013 Auto-Notify via Slack & Email\n\n### Who this workflow is for\nThis workflow is ideal for Shopify store owners, e-commerce operations teams, and fulfillment managers who need automated dispatch SLA monitoring for delayed Shopify orders without getting spammed by duplicate alerts.\n\n### What this workflow does\nThis automation monitors your Shopify store every hour and detects open, unfulfilled orders that have exceeded your dispatch SLA timeline. It automatically tracks previously alerted orders using Google Sheets, ensuring your team only receives notifications for new SLA breaches.\n\nWhen a dispatch SLA breach is detected, the workflow instantly sends alerts to Slack and email, helping your team respond faster, reduce fulfillment delays, maintain operational efficiency, and improve customer experience.\n\n### How it works\n1. **Hourly Monitoring** \u2014 The workflow runs automatically every hour using the n8n Schedule Trigger.\n2. **Fetch Shopify Orders** \u2014 Retrieves up to 250 open, unfulfilled Shopify orders older than your configured SLA window.\n3. **Smart Deduplication** \u2014 Checks Google Sheets to avoid sending duplicate alerts for the same order.\n4. **Order Processing** \u2014 Calculates order age, identifies VIP orders, and formats Slack and email notifications.\n5. **Generate Email HTML** \u2014 Dedicated node that builds the full HTML email body.\n6. **Instant Notifications** \u2014 Sends real-time Slack alerts and HTML email notifications for newly breached orders only.\n7. **Error Alerts** \u2014 If any workflow step fails, a global Error Trigger immediately sends the error details to Slack.\n\n### How to set it up\n1. Connect Shopify OAuth2, Google Sheets OAuth2, Gmail OAuth2, and Slack API credentials in n8n.\n2. Open the `Set SLA Config` node and configure:\n   * SLA hours\n   * VIP order threshold\n   * Recipient email\n   * Google Sheet URL\n   * Slack channel ID\n3. Create a Google Sheet with these exact headers:\n   * `runTimestamp`\n   * `unfulfilledOrderIds`\n   * `vipOrderIds`\n   * `totalOrders`\n   * `alertStatus`\n4. Select your preferred Slack channel inside both Slack nodes.\n5. Activate the workflow.\n\n### Requirements\n* **n8n Version:** 2.20+\n* **Shopify Store:** Active Shopify store with API access\n* **Required Integrations:** Shopify, Google Sheets, Gmail, Slack\n\n### Customization Options\n* Update the `slaHours` value to change your dispatch SLA window.\n* Adjust `vipThreshold` to flag high-value Shopify orders.\n* Add multiple recipients by duplicating the Gmail node or using a distribution email.\n* Increase monitoring frequency by changing the Schedule Trigger to every 30 minutes.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9ef786af-96da-4ec4-8994-a554ee86b93c",
      "name": "Zone ",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3440,
        -320
      ],
      "parameters": {
        "color": 6,
        "width": 460,
        "height": 940,
        "content": "## 1. \u2699\ufe0f Trigger & SLA Config\n\n**Schedule Trigger**\nRuns every hour automatically. Adjust the interval in the node for higher-frequency checks (e.g. every 30 min for high-volume stores).\n\n**Set SLA Config**\nSingle node to configure the entire workflow:\n- `slaHours` \u2192 Hours before an order is flagged as a breach (e.g. `48`)\n- `vipThreshold` \u2192 Order value (\u20b9) that marks an order as VIP (e.g. `3000`)\n- `recipientMail` \u2192 Email address for breach alert delivery\n- `googleSheetUrl` \u2192 Full URL of your Google Sheet log\n- `slackEscalationChannel` \u2192 Paste the Slack channel ID (e.g. C01ABC123), not the channel name. Right-click the channel in Slack \u2192 Copy Link to find the ID."
      },
      "typeVersion": 1
    },
    {
      "id": "4d9273b6-2e88-4e40-8d49-dfe2c76e5e55",
      "name": "Zone 7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3920,
        -320
      ],
      "parameters": {
        "color": 4,
        "width": 868,
        "height": 892,
        "content": "## 2. \ud83e\udde0 Smart Fetch & Deduplication\n\n**Fetch Unfulfilled Orders**\nRetrieves up to 250 open, unfulfilled Shopify orders using `createdAtMax` to pre-filter at the API level \u2014 only orders older than the SLA window are returned.\n\n**Read Previous Sheet Logs**\nLoads all existing rows from the Google Sheet breach log. `continueRegularOutput` ensures an empty sheet does not halt the workflow.\n\n**Process & Deduplicate**\nCore deduplication and alert-prep logic:\n- Builds a Set of every order ID previously logged (handles comma-separated rows)\n- Calculates order age in hours for each Shopify order\n- Skips any order already present in the Set \u2014 no repeat alerts\n- Tags VIP orders above the value threshold\n- Formats Slack message, email subject/body, and run timestamp\n- Returns empty array if no net-new breaches exist, halting the workflow cleanly"
      },
      "typeVersion": 1
    },
    {
      "id": "2157a6bd-50f3-4e9a-8c82-01a26a301e24",
      "name": "Zone 8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3984,
        464
      ],
      "parameters": {
        "color": 4,
        "width": 740,
        "height": 556,
        "content": "\ud83d\udccb **GOOGLE SHEET SETUP**\n\n**Create Your Sheet:**\n\n1. Go to sheets.google.com/create\n2. Create a blank Google Sheet\n\n**Add Headers to Row 1 (CASE-SENSITIVE):**\n`runTimestamp` | `unfulfilledOrderIds` | `vipOrderIds` | `totalOrders` | `alertStatus`\n\n**Copy & Paste URL:**\n\n1. Copy your Google Sheet URL\n   (format: `docs.google.com/spreadsheets/d/[SHEET_ID]/edit`)\n2. Open the `Set SLA Config` node\n3. Paste the URL into the `googleSheetUrl` field\n\n\u26a0\ufe0f **Important Notes:**\n\n* Do NOT rename or delete the `unfulfilledOrderIds` column\n* This column stores previously alerted order IDs\n* The workflow reads this column on every cron run to prevent duplicate alerts (deduplication)\n\n\u2705 **That's it!** Your Shopify Unfulfilled Orders cron workflow will now automatically log alerts and track sent orders.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d2566e35-dff0-4fa9-93ab-2c1cb6e3c6e7",
      "name": "Zone 5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3440,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 514,
        "height": 440,
        "content": "## 5. \ud83d\udea8 Global Error Handling\nCatches any workflow failures.\n\n**Action:**\n- Sends error trace to Slack for quick debugging.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "922d704b-ebbc-4fdd-89db-edce08dbc732",
      "name": "Hourly SLA Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3536,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "de954abe-cea8-4614-98d7-fb7766cd267f",
      "name": "Generate Email HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        5088,
        336
      ],
      "parameters": {
        "jsCode": "const d = $('Process & Deduplicate').first().json;\n\nconst vipEmailSection = d.vipCount > 0\n  ? `<tr>\n      <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0;\">\n        <span style=\"color:#6b7280; font-size:13px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em;\">\ud83c\udf1f VIP Order IDs</span><br/>\n        <span style=\"color:#111827; font-size:15px; font-family:monospace;\">${d.vipOrderIdsArray}</span>\n      </td>\n    </tr>`\n  : '';\n\nconst emailBody = `<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"UTF-8\"/><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/></head>\n<body style=\"margin:0; padding:0; background:#f4f6f8; font-family:'Segoe UI',Arial,sans-serif;\">\n  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#f4f6f8; padding:32px 16px;\">\n    <tr>\n      <td align=\"center\">\n        <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff; border-radius:12px; overflow:hidden;\">\n\n          <tr>\n            <td style=\"background:#c41c1c; padding:28px 32px;\">\n              <p style=\"margin:0; color:#fecaca; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.1em;\">Order Operations Alert</p>\n              <h1 style=\"margin:8px 0 0; color:#ffffff; font-size:22px; font-weight:700; line-height:1.3;\">\ud83d\udea8 ${d.orderCount} Unfulfilled Order${d.orderCount > 1 ? 's' : ''} Beyond SLA</h1>\n            </td>\n          </tr>\n\n          <tr>\n            <td style=\"padding:24px 32px 8px;\">\n              <p style=\"margin:0; color:#374151; font-size:15px; line-height:1.7;\">\n                <strong>${d.orderCount} order${d.orderCount > 1 ? 's have' : ' has'}</strong> exceeded your <strong>${d.slaHours}-hour fulfillment SLA</strong> and ${d.orderCount === 1 ? 'has' : 'have'} not yet been fulfilled. Please review and take action immediately.\n              </p>\n            </td>\n          </tr>\n\n          <tr>\n            <td style=\"padding:20px 32px;\">\n              <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n                <tr>\n                  <td width=\"48%\" style=\"background:#fef2f2; border:1px solid #fecaca; border-radius:8px; padding:16px; text-align:center;\">\n                    <p style=\"margin:0; color:#dc2626; font-size:28px; font-weight:700;\">${d.orderCount}</p>\n                    <p style=\"margin:4px 0 0; color:#6b7280; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em;\">Orders Breached SLA</p>\n                  </td>\n                  <td width=\"4%\"></td>\n                  <td width=\"48%\" style=\"background:#fffbeb; border:1px solid #fcd34d; border-radius:8px; padding:16px; text-align:center;\">\n                    <p style=\"margin:0; color:#b45309; font-size:28px; font-weight:700;\">${d.currencySymbol}${d.highestValueFormatted}</p>\n                    <p style=\"margin:4px 0 0; color:#6b7280; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em;\">Highest Order Value</p>\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n\n          <tr>\n            <td style=\"padding:0 32px 24px;\">\n              <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"border:1px solid #e5e7eb; border-radius:8px; overflow:hidden;\">\n                <tr>\n                  <td style=\"padding:12px 16px; background:#f9fafb; border-bottom:1px solid #e5e7eb;\">\n                    <span style=\"color:#6b7280; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em;\">Detail</span>\n                  </td>\n                  <td style=\"padding:12px 16px; background:#f9fafb; border-bottom:1px solid #e5e7eb;\">\n                    <span style=\"color:#6b7280; font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:0.05em;\">Value</span>\n                  </td>\n                </tr>\n                <tr>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#374151; font-size:14px;\">\u23f1 SLA Threshold</td>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#111827; font-size:14px; font-weight:600;\">${d.slaHours} hours</td>\n                </tr>\n                <tr>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#374151; font-size:14px;\">\ud83c\udf1f VIP Orders</td>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#111827; font-size:14px; font-weight:600;\">${d.vipCount > 0 ? d.vipCount + ' order(s)' : 'None'}</td>\n                </tr>\n                <tr>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#374151; font-size:14px; vertical-align:top;\">\ud83d\udce6 Breached Order IDs</td>\n                  <td style=\"padding:12px 16px; border-bottom:1px solid #f0f0f0; color:#111827; font-size:13px; font-family:monospace; word-break:break-all;\">${d.allOrderIds}</td>\n                </tr>\n                ${vipEmailSection}\n              </table>\n            </td>\n          </tr>\n\n          <tr>\n            <td style=\"background:#f9fafb; border-top:1px solid #e5e7eb; padding:16px 32px;\">\n              <p style=\"margin:0; color:#9ca3af; font-size:12px; text-align:center;\">\n                \ud83d\udd50 Alert generated at <strong>${d.runTimestamp} UTC</strong> \u00b7 Order SLA Monitor \u00b7 This is an automated notification\n              </p>\n            </td>\n          </tr>\n\n        </table>\n      </td>\n    </tr>\n  </table>\n</body>\n</html>`;\n\nreturn [{\n  json: {\n    ...d,\n    emailBody\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3d3b435b-0d3f-4f19-81bf-73085ab5cedc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        1088
      ],
      "parameters": {
        "color": 6,
        "width": 310,
        "height": 144,
        "content": "\u26a0\ufe0f **ACTION REQUIRED**\nOpen Slack node and select your Slack channel from the dropdown before activating the workflow."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "3e2bc6f2-14b4-45df-b8c1-7f95f0f6d242",
  "connections": {
    "Set SLA Config": {
      "main": [
        [
          {
            "node": "Fetch Unfulfilled Orders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hourly SLA Trigger": {
      "main": [
        [
          {
            "node": "Set SLA Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Email HTML": {
      "main": [
        [
          {
            "node": "Email SLA Breach Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Global Error Trigger": {
      "main": [
        [
          {
            "node": "Slack - Error Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Breach to Sheets": {
      "main": [
        [
          {
            "node": "Generate Email HTML",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack - Post Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process & Deduplicate": {
      "main": [
        [
          {
            "node": "Log Breach to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Unfulfilled Orders": {
      "main": [
        [
          {
            "node": "Read Previous Sheet Logs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Previous Sheet Logs": {
      "main": [
        [
          {
            "node": "Process & Deduplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow is built to monitor your Shopify store every hour and automatically detect open, unfulfilled orders that have gone past your dispatch SLA window. The moment a breach is detected, alerts go out to Slack and Email instantly, helping your team respond faster, reduce…

Source: https://n8n.io/workflows/15941/ — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

Sync your Google Calendar events with Google Sheets and get daily Slack summaries with meeting statistics. FEATURES:

Google Calendar Trigger, Google Sheets, Slack +3
Email & Gmail

Automate daily KPI tracking and reporting by integrating ClickUp tasks and Google Sheets lead data into a unified dashboard. This workflow computes performance metrics, analyzes sentiment, and deliver

Error Trigger, Slack, ClickUp +2
Email & Gmail

error-handler. Uses errorTrigger, gmail, slack, googleSheets. Event-driven trigger; 5 nodes.

Error Trigger, Gmail, Slack +1
Email & Gmail

Automate event registration with capacity management, a waitlist, and multi-tier PDF ticket generation using PDF Generator API. When attendees register, the workflow checks available spots, routes by

Form Trigger, Google Sheets, @Pdfgeneratorapi/N8N Nodes Pdf Generator Api +2
Email & Gmail

Categories: Payments, Project Operations, Client Onboarding

Stripe Trigger, Google Drive, ClickUp +4