{
  "id": "ZLYpvDUbFyNpTcyg",
  "name": "Monitor competitor prices and alert via Slack and email",
  "tags": [
    {
      "id": "mHQntpbppFUpZPGm",
      "name": "template",
      "createdAt": "2026-02-17T08:30:26.885Z",
      "updatedAt": "2026-02-17T08:30:26.885Z"
    }
  ],
  "nodes": [
    {
      "id": "df38f72a-ee3a-4877-894a-4a8fdbf32362",
      "name": "Daily Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        928,
        608
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7a378618-0238-44b8-84f9-4e6f2a94cc78",
      "name": "Get Products from Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1152,
        608
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_PRODUCTS_SHEET_GID",
          "cachedResultUrl": "YOUR_PRODUCTS_SHEET_URL",
          "cachedResultName": "products"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Your Products Sheet"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "10216fd3-331c-47da-bab8-104b27fca398",
      "name": "Clear Memory and Normalize Columns",
      "type": "n8n-nodes-base.code",
      "position": [
        1376,
        608
      ],
      "parameters": {
        "jsCode": "// Clear previous run data AND normalize column names\n// This handles any column name format from your sheet\nconst staticData = $getWorkflowStaticData('global');\nstaticData.allResults = [];\n\nconst items = $input.all();\nconst normalized = [];\n\nfor (const item of items) {\n  const raw = item.json;\n  const keys = Object.keys(raw);\n  \n  // Find product name column (flexible matching)\n  const nameKey = keys.find(k => \n    k.toLowerCase().replace(/[^a-z]/g, '') === 'productname' ||\n    k.toLowerCase() === 'product_name' ||\n    k.toLowerCase() === 'product name' ||\n    k.toLowerCase() === 'name' ||\n    k.toLowerCase() === 'title' ||\n    k.toLowerCase() === 'item' ||\n    k.toLowerCase() === 'product'\n  );\n  \n  // Find SKU column\n  const skuKey = keys.find(k => \n    k.toLowerCase() === 'sku' ||\n    k.toLowerCase() === 'id' ||\n    k.toLowerCase() === 'product_id'\n  );\n  \n  // Find price column\n  const priceKey = keys.find(k => \n    k.toLowerCase().replace(/[^a-z]/g, '') === 'myprice' ||\n    k.toLowerCase() === 'my_price' ||\n    k.toLowerCase() === 'price' ||\n    k.toLowerCase() === 'selling_price' ||\n    k.toLowerCase() === 'our price' ||\n    k.toLowerCase() === 'my price'\n  );\n  \n  // Find cost column\n  const costKey = keys.find(k => \n    k.toLowerCase().replace(/[^a-z]/g, '') === 'mycost' ||\n    k.toLowerCase() === 'my_cost' ||\n    k.toLowerCase() === 'cost' ||\n    k.toLowerCase() === 'our cost' ||\n    k.toLowerCase() === 'my cost'\n  );\n  \n  const productName = nameKey ? raw[nameKey] : null;\n  const price = priceKey ? raw[priceKey] : null;\n  \n  if (!productName || !price) continue; // Skip empty rows\n  \n  normalized.push({\n    json: {\n      product_name: String(productName).trim(),\n      sku: skuKey ? String(raw[skuKey]).trim() : '',\n      my_price: String(price).replace(/[^0-9.]/g, ''),\n      my_cost: costKey ? String(raw[costKey]).replace(/[^0-9.]/g, '') : '0'\n    }\n  });\n}\n\nif (normalized.length === 0) {\n  return [{ json: { error: 'No products found. Check your sheet column names. Found columns: ' + Object.keys(items[0]?.json || {}).join(', ') } }];\n}\n\nreturn normalized;"
      },
      "typeVersion": 2
    },
    {
      "id": "caa91554-2313-4564-b41a-4cf47b33eb00",
      "name": "Loop Through Each Product",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1600,
        608
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "678bc0f1-7512-427c-a604-1e18f55a6aa7",
      "name": "Prepare Search Query",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        320
      ],
      "parameters": {
        "jsCode": "// Pass through product data for search\nconst item = $input.first().json;\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "9c474c8e-9eef-4f12-bd32-3b75a27d58f4",
      "name": "Search Google Shopping Prices",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2048,
        320
      ],
      "parameters": {
        "url": "https://www.searchapi.io/api/v1/search",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "api_key",
              "value": "={{ $env.SEARCHAPI_KEY }}"
            },
            {
              "name": "engine",
              "value": "google_shopping"
            },
            {
              "name": "q",
              "value": "={{ $json.product_name }}"
            },
            {
              "name": "gl",
              "value": "us"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4699bd51-4c99-430a-9b3e-416014a48cf0",
      "name": "Analyze Pricing Gap",
      "type": "n8n-nodes-base.code",
      "position": [
        2272,
        320
      ],
      "parameters": {
        "jsCode": "const product = $('Loop Through Each Product').first().json;\nconst response = $input.first().json;\n\nconst results = response.shopping_results || response.inline_shopping_results || [];\nconst prices = [];\n\nfor (const item of results) {\n  let price = item.price || item.extracted_price || 0;\n  if (typeof price === 'string') {\n    price = parseFloat(price.replace(/[^0-9.]/g, ''));\n  }\n  if (price > 0) {\n    prices.push({\n      seller: item.source || item.merchant || 'Unknown',\n      price: price,\n      title: item.title || ''\n    });\n  }\n}\n\nif (prices.length === 0) {\n  return [{\n    json: {\n      product_name: product.product_name,\n      sku: product.sku || '',\n      my_price: parseFloat(product.my_price),\n      my_cost: parseFloat(product.my_cost || 0),\n      margin_now: null,\n      competitor_lowest: null,\n      competitor_average: null,\n      competitor_highest: null,\n      competitor_count: 0,\n      gap_pct: null,\n      signal: 'NO_DATA',\n      severity: 'info',\n      suggested_price: null,\n      margin_suggested: null,\n      action: 'No competitor prices found',\n      date: new Date().toISOString().split('T')[0]\n    }\n  }];\n}\n\nconst priceValues = prices.map(p => p.price).sort((a, b) => a - b);\nconst lowest = Math.min(...priceValues);\nconst highest = Math.max(...priceValues);\nconst average = parseFloat((priceValues.reduce((a, b) => a + b, 0) / priceValues.length).toFixed(2));\n\nconst myPrice = parseFloat(product.my_price);\nconst myCost = parseFloat(product.my_cost || 0);\nconst gapPct = parseFloat(((myPrice - average) / average * 100).toFixed(1));\n\nlet signal, severity, action, suggestedPrice;\n\nif (myPrice < average * 0.85) {\n  signal = 'UNDERPRICED';\n  severity = 'critical';\n  suggestedPrice = parseFloat((average * 0.95).toFixed(2));\n  if (myCost > 0 && suggestedPrice < myCost * 1.15) suggestedPrice = parseFloat((myCost * 1.15).toFixed(2));\n  action = 'RAISE to $' + suggestedPrice + ' \u2014 you are ' + Math.abs(gapPct) + '% below market';\n} else if (myPrice < average * 0.95) {\n  signal = 'SLIGHTLY_UNDER';\n  severity = 'warning';\n  suggestedPrice = parseFloat((average * 0.97).toFixed(2));\n  action = 'Consider raising to $' + suggestedPrice;\n} else if (myPrice > average * 1.15) {\n  signal = 'OVERPRICED';\n  severity = 'critical';\n  suggestedPrice = parseFloat((average * 1.05).toFixed(2));\n  if (myCost > 0 && suggestedPrice < myCost * 1.15) suggestedPrice = parseFloat((myCost * 1.15).toFixed(2));\n  action = 'LOWER to $' + suggestedPrice + ' \u2014 you are ' + gapPct + '% above market';\n} else if (myPrice > average * 1.05) {\n  signal = 'SLIGHTLY_OVER';\n  severity = 'warning';\n  suggestedPrice = parseFloat((average * 1.03).toFixed(2));\n  action = 'Consider lowering to $' + suggestedPrice;\n} else {\n  signal = 'COMPETITIVE';\n  severity = 'ok';\n  suggestedPrice = myPrice;\n  action = 'Price is competitive \u2014 no change needed';\n}\n\nconst marginNow = myCost > 0 ? parseFloat(((myPrice - myCost) / myPrice * 100).toFixed(1)) : null;\nconst marginSuggested = myCost > 0 ? parseFloat(((suggestedPrice - myCost) / suggestedPrice * 100).toFixed(1)) : null;\n\nreturn [{\n  json: {\n    product_name: product.product_name,\n    sku: product.sku || '',\n    my_price: myPrice,\n    my_cost: myCost,\n    margin_now: marginNow,\n    competitor_lowest: lowest,\n    competitor_average: average,\n    competitor_highest: highest,\n    competitor_count: prices.length,\n    gap_pct: gapPct,\n    signal: signal,\n    severity: severity,\n    suggested_price: suggestedPrice,\n    margin_suggested: marginSuggested,\n    action: action,\n    date: new Date().toISOString().split('T')[0]\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c8b1a0b1-1df0-4119-9be1-4e9cb6216db2",
      "name": "Save Result to Memory",
      "type": "n8n-nodes-base.code",
      "position": [
        2496,
        320
      ],
      "parameters": {
        "jsCode": "// Save analyzed result to memory for daily summary\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.allResults) staticData.allResults = [];\nstaticData.allResults.push($input.first().json);\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "06766599-5dff-4982-9075-f7f156874786",
      "name": "Log to Price History Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2720,
        512
      ],
      "parameters": {
        "columns": {
          "value": {
            "sku": "={{ $json.sku }}",
            "date": "={{ $json.date }}",
            "action": "={{ $json.action }}",
            "signal": "={{ $json.signal }}",
            "gap_pct": "={{ $json.gap_pct }}",
            "my_cost": "={{ $json.my_cost }}",
            "my_price": "={{ $json.my_price }}",
            "margin_now": "={{ $json.margin_now }}",
            "product_name": "={{ $json.product_name }}",
            "suggested_price": "={{ $json.suggested_price }}",
            "competitor_count": "={{ $json.competitor_count }}",
            "competitor_lowest": "={{ $json.competitor_lowest }}",
            "competitor_average": "={{ $json.competitor_average }}",
            "competitor_highest": "={{ $json.competitor_highest }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "product_name",
              "type": "string",
              "required": false,
              "displayName": "product_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "sku",
              "type": "string",
              "required": false,
              "displayName": "sku",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "my_price",
              "type": "string",
              "required": false,
              "displayName": "my_price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "my_cost",
              "type": "string",
              "required": false,
              "displayName": "my_cost",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "margin_now",
              "type": "string",
              "required": false,
              "displayName": "margin_now",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "competitor_lowest",
              "type": "string",
              "required": false,
              "displayName": "competitor_lowest",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "competitor_average",
              "type": "string",
              "required": false,
              "displayName": "competitor_average",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "competitor_highest",
              "type": "string",
              "required": false,
              "displayName": "competitor_highest",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "competitor_count",
              "type": "string",
              "required": false,
              "displayName": "competitor_count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "gap_pct",
              "type": "string",
              "required": false,
              "displayName": "gap_pct",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "signal",
              "type": "string",
              "required": false,
              "displayName": "signal",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "suggested_price",
              "type": "string",
              "required": false,
              "displayName": "suggested_price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "action",
              "type": "string",
              "required": false,
              "displayName": "action",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "price_log"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "YOUR_GOOGLE_SHEET_URL",
          "cachedResultName": "Your Products Sheet"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "88e0edd7-d292-44b0-b12a-1da798f8586f",
      "name": "Filter Critical or Warning Alerts",
      "type": "n8n-nodes-base.filter",
      "position": [
        2720,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "critical-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.severity }}",
              "rightValue": "critical"
            },
            {
              "id": "warning-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.severity }}",
              "rightValue": "warning"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "04641da4-3cd7-4f39-94a2-7840021a34a2",
      "name": "Send Slack Pricing Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2944,
        128
      ],
      "parameters": {
        "text": "={{ $json.severity === 'critical' ? '\ud83d\udea8' : '\u26a0\ufe0f' }} *{{ $json.signal }}* \u2014 {{ $json.product_name }}\n\n\ud83d\udcb2 Your Price: *${{ $json.my_price }}*\n\ud83d\udcca Market Avg: *${{ $json.competitor_average }}* ({{ $json.competitor_count }} competitors)\n\ud83d\udcc9 Lowest: ${{ $json.competitor_lowest }} | Highest: ${{ $json.competitor_highest }}\n\ud83d\udcd0 Gap: *{{ $json.gap_pct }}%*\n\n\u2705 *Action:* {{ $json.action }}\n{{ $json.margin_now ? '\ud83d\udcb0 Current Margin: ' + $json.margin_now + '% \u2192 Suggested Margin: ' + $json.margin_suggested + '%' : '' }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "62106fa8-8ea8-4364-89bf-2ffc95f00255",
      "name": "Continue to Next Product",
      "type": "n8n-nodes-base.code",
      "position": [
        2944,
        784
      ],
      "parameters": {
        "jsCode": "// Pass through to loop back to next product\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "27ac6923-5c0e-4c9a-820e-2595d1f182a7",
      "name": "Build Daily Summary Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        608
      ],
      "parameters": {
        "jsCode": "// Build daily summary from ALL collected results in memory\nconst staticData = $getWorkflowStaticData('global');\nconst allResults = staticData.allResults || [];\n\n// Clear for next run\nstaticData.allResults = [];\n\nif (allResults.length === 0) {\n  return [{\n    json: {\n      summary: 'DAILY PRICING REPORT \u2014 ' + new Date().toISOString().split('T')[0] + '\\n\\nNo products were analyzed today.',\n      summary_html: '<h2>Daily Pricing Report</h2><p>No products analyzed today.</p>',\n      has_alerts: false,\n      date: new Date().toISOString().split('T')[0],\n      total_products: 0,\n      critical_count: 0,\n      warning_count: 0\n    }\n  }];\n}\n\nconst critical = allResults.filter(r => r.severity === 'critical');\nconst warnings = allResults.filter(r => r.severity === 'warning');\nconst ok = allResults.filter(r => r.severity === 'ok');\nconst noData = allResults.filter(r => r.signal === 'NO_DATA');\nconst underpriced = allResults.filter(r => r.signal === 'UNDERPRICED' || r.signal === 'SLIGHTLY_UNDER');\nconst overpriced = allResults.filter(r => r.signal === 'OVERPRICED' || r.signal === 'SLIGHTLY_OVER');\n\nconst today = new Date().toISOString().split('T')[0];\n\nlet summary = 'DAILY PRICING REPORT \u2014 ' + today + '\\n';\nsummary += 'Products checked: ' + allResults.length + '\\n\\n';\nsummary += 'Critical: ' + critical.length + '\\n';\nsummary += 'Warnings: ' + warnings.length + '\\n';\nsummary += 'Competitive: ' + ok.length + '\\n';\nsummary += 'No data: ' + noData.length + '\\n\\n';\n\nif (underpriced.length > 0) {\n  summary += 'Underpriced (raise for margin):\\n';\n  for (const p of underpriced) {\n    summary += '\u2022 ' + p.product_name + ': $' + p.my_price + ' \u2192 $' + p.suggested_price + ' (' + Math.abs(p.gap_pct) + '% below avg)\\n';\n  }\n  summary += '\\n';\n}\n\nif (overpriced.length > 0) {\n  summary += 'Overpriced (lower to compete):\\n';\n  for (const p of overpriced) {\n    summary += '\u2022 ' + p.product_name + ': $' + p.my_price + ' \u2192 $' + p.suggested_price + ' (' + p.gap_pct + '% above avg)\\n';\n  }\n}\n\nlet html = '<h2>Daily Pricing Report \u2014 ' + today + '</h2>';\nhtml += '<p>Products checked: <strong>' + allResults.length + '</strong></p>';\nhtml += '<table style=\"border-collapse:collapse;width:100%;\">';\nhtml += '<tr style=\"background:#f0f0f0;\"><th style=\"padding:8px;border:1px solid #ddd;\">Status</th><th style=\"padding:8px;border:1px solid #ddd;\">Count</th></tr>';\nhtml += '<tr><td style=\"padding:8px;border:1px solid #ddd;\">Critical</td><td style=\"padding:8px;border:1px solid #ddd;\">' + critical.length + '</td></tr>';\nhtml += '<tr><td style=\"padding:8px;border:1px solid #ddd;\">Warnings</td><td style=\"padding:8px;border:1px solid #ddd;\">' + warnings.length + '</td></tr>';\nhtml += '<tr><td style=\"padding:8px;border:1px solid #ddd;\">Competitive</td><td style=\"padding:8px;border:1px solid #ddd;\">' + ok.length + '</td></tr>';\nhtml += '<tr><td style=\"padding:8px;border:1px solid #ddd;\">No Data</td><td style=\"padding:8px;border:1px solid #ddd;\">' + noData.length + '</td></tr>';\nhtml += '</table>';\n\nif (underpriced.length > 0) {\n  html += '<h3>Underpriced</h3><table style=\"border-collapse:collapse;width:100%;\">';\n  html += '<tr style=\"background:#d4edda;\"><th style=\"padding:6px;border:1px solid #ddd;\">Product</th><th style=\"padding:6px;border:1px solid #ddd;\">Your Price</th><th style=\"padding:6px;border:1px solid #ddd;\">Suggested</th><th style=\"padding:6px;border:1px solid #ddd;\">Gap</th></tr>';\n  for (const p of underpriced) {\n    html += '<tr><td style=\"padding:6px;border:1px solid #ddd;\">' + p.product_name + '</td><td style=\"padding:6px;border:1px solid #ddd;\">$' + p.my_price + '</td><td style=\"padding:6px;border:1px solid #ddd;\"><strong>$' + p.suggested_price + '</strong></td><td style=\"padding:6px;border:1px solid #ddd;\">' + Math.abs(p.gap_pct) + '% below</td></tr>';\n  }\n  html += '</table>';\n}\n\nif (overpriced.length > 0) {\n  html += '<h3>Overpriced</h3><table style=\"border-collapse:collapse;width:100%;\">';\n  html += '<tr style=\"background:#f8d7da;\"><th style=\"padding:6px;border:1px solid #ddd;\">Product</th><th style=\"padding:6px;border:1px solid #ddd;\">Your Price</th><th style=\"padding:6px;border:1px solid #ddd;\">Suggested</th><th style=\"padding:6px;border:1px solid #ddd;\">Gap</th></tr>';\n  for (const p of overpriced) {\n    html += '<tr><td style=\"padding:6px;border:1px solid #ddd;\">' + p.product_name + '</td><td style=\"padding:6px;border:1px solid #ddd;\">$' + p.my_price + '</td><td style=\"padding:6px;border:1px solid #ddd;\"><strong>$' + p.suggested_price + '</strong></td><td style=\"padding:6px;border:1px solid #ddd;\">' + p.gap_pct + '% above</td></tr>';\n  }\n  html += '</table>';\n}\n\nreturn [{\n  json: {\n    summary: summary,\n    summary_html: html,\n    has_alerts: critical.length + warnings.length > 0,\n    date: today,\n    total_products: allResults.length,\n    critical_count: critical.length,\n    warning_count: warnings.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "357a57b2-56bd-40ca-aa6d-fbd4bbaa2921",
      "name": "Post Daily Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2048,
        512
      ],
      "parameters": {
        "text": "={{ $json.summary }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "52a3aa2a-ef6e-4e64-98ef-6b4e126a073f",
      "name": "Email Daily Report",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2048,
        704
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.summary_html }}",
        "options": {},
        "subject": "=Pricing Report \u2014 {{ $json.date }} | {{ $json.critical_count }} critical, {{ $json.warning_count }} warnings"
      },
      "typeVersion": 2.2
    },
    {
      "id": "6d52984d-1aa6-490a-b6d9-e22f5290da96",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -368
      ],
      "parameters": {
        "color": "#8E8F42",
        "width": 660,
        "height": 880,
        "content": "## Monitor competitor prices and alert via Slack and email\n\nThis workflow automatically monitors your product prices against competitors on Google Shopping, identifies pricing gaps, and sends alerts when action is needed.\n\n## How it works\n1. **Runs daily** on a schedule (default: every 24 hours)\n2. **Reads your product catalog** from a Google Sheet (flexible column name matching)\n3. **Searches Google Shopping** for each product via SearchAPI\n4. **Analyzes pricing gaps** \u2014 classifies each product as UNDERPRICED, OVERPRICED, COMPETITIVE, or NO_DATA\n5. **Calculates suggested prices** based on market averages while protecting your margins\n6. **Sends instant Slack alerts** for critical/warning pricing issues\n7. **Logs all results** to a `price_log` sheet for historical tracking\n8. **Sends a daily summary** via Slack and email with full breakdown\n\n## Setup steps\n1. **Create a Google Sheet** with a `products` tab containing columns: `product_name`, `my_price`, and optionally `sku` and `my_cost`\n2. Add a second tab called `price_log` with these column headers: `date`, `product_name`, `sku`, `my_price`, `my_cost`, `margin_now`, `competitor_lowest`, `competitor_average`, `competitor_highest`, `competitor_count`, `gap_pct`, `signal`, `suggested_price`, `action`\n3. **Get a SearchAPI key** from [searchapi.io](https://www.searchapi.io) and set it as an n8n environment variable called `SEARCHAPI_KEY`\n4. **Connect Google Sheets OAuth2** credentials\n5. **Connect Slack OAuth2** credentials and configure the channel\n6. **Connect Gmail OAuth2** credentials and update `YOUR_EMAIL@EXAMPLE.COM`\n7. Update the Google Sheet ID in the \"Get Products from Sheet\" and \"Log to Price History Sheet\" nodes\n\n## Customization\n- Adjust pricing thresholds in the \"Analyze Pricing Gap\" node (default: \u00b15% warning, \u00b115% critical)\n- Change the schedule frequency in the trigger node\n- Add more notification channels (Telegram, Discord, etc.)\n- Modify the minimum margin protection (default: 15% above cost)"
      },
      "typeVersion": 1
    },
    {
      "id": "a82235af-fdef-4f3f-b8a8-4919a7fbe3f1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        368
      ],
      "parameters": {
        "color": "#4D4242",
        "width": 264,
        "height": 164,
        "content": "## 1. Load Products\nSchedule trigger fires daily. Products are read from Google Sheets. Column names are auto-detected (supports many naming conventions)."
      },
      "typeVersion": 1
    },
    {
      "id": "86733f0c-7e5d-4f49-a7a8-bbd3befd1e73",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        96
      ],
      "parameters": {
        "color": "#4D4242",
        "width": 312,
        "height": 164,
        "content": "## 2. Search and Analyze\nEach product is searched on Google Shopping via SearchAPI. Prices are compared to calculate gap %, signal severity, and a suggested price."
      },
      "typeVersion": 1
    },
    {
      "id": "f5818320-0511-4e94-bdd3-88d279767591",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2704,
        -96
      ],
      "parameters": {
        "color": "#4D4242",
        "width": 224,
        "height": 164,
        "content": "## 3. Log and Alert\nResults are saved to memory and logged to the `price_log` sheet. Critical and warning signals trigger instant Slack alerts."
      },
      "typeVersion": 1
    },
    {
      "id": "5d87a00f-455e-4e30-954c-893985d18bcd",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        528
      ],
      "parameters": {
        "color": "#4D4242",
        "width": 252,
        "height": 196,
        "content": "## 4. Daily Summary\nAfter all products are processed, a summary report is compiled from memory and sent via Slack message and HTML email."
      },
      "typeVersion": 1
    },
    {
      "id": "8792930c-81a3-4c25-9a75-b1c33ed96e9f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2016,
        -80
      ],
      "parameters": {
        "color": 3,
        "width": 440,
        "height": 100,
        "content": "\u26a0\ufe0f **Set your SearchAPI key** as an n8n environment variable: `SEARCHAPI_KEY`. Do NOT hardcode it here. See [n8n docs on environment variables](https://docs.n8n.io/hosting/configuration/environment-variables/)."
      },
      "typeVersion": 1
    },
    {
      "id": "f9acefa6-b811-499d-ac08-de02d7d229d3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2192,
        816
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 80,
        "content": "\u26a0\ufe0f **Update the email address** in this node to your own email before activating."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "9b837a78-b593-4e2b-8ae4-23e7f0279b80",
  "connections": {
    "Analyze Pricing Gap": {
      "main": [
        [
          {
            "node": "Save Result to Memory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Search Query": {
      "main": [
        [
          {
            "node": "Search Google Shopping Prices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Result to Memory": {
      "main": [
        [
          {
            "node": "Log to Price History Sheet",
            "type": "main",
            "index": 0
          },
          {
            "node": "Filter Critical or Warning Alerts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Products from Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Products from Sheet": {
      "main": [
        [
          {
            "node": "Clear Memory and Normalize Columns",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Continue to Next Product": {
      "main": [
        [
          {
            "node": "Loop Through Each Product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Through Each Product": {
      "main": [
        [
          {
            "node": "Prepare Search Query",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Daily Summary Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Daily Summary Report": {
      "main": [
        [
          {
            "node": "Post Daily Summary to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Daily Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Price History Sheet": {
      "main": [
        [
          {
            "node": "Continue to Next Product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search Google Shopping Prices": {
      "main": [
        [
          {
            "node": "Analyze Pricing Gap",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Critical or Warning Alerts": {
      "main": [
        [
          {
            "node": "Send Slack Pricing Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clear Memory and Normalize Columns": {
      "main": [
        [
          {
            "node": "Loop Through Each Product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}