AutomationFlowsEmail & Gmail › Monitor Competitor Prices with Google Shopping and Google Sheets, Alert via…

Monitor Competitor Prices with Google Shopping and Google Sheets, Alert via…

Original n8n title: Monitor Competitor Prices with Google Shopping and Google Sheets, Alert via Slack and Gmail

ByVeena Pandian @veenapandian on n8n.io

E-commerce store owners, product managers, marketplace sellers, and pricing analysts who want to automatically track competitor pricing and get actionable alerts when their products are overpriced or underpriced relative to the market.

Cron / scheduled trigger★★★★☆ complexity22 nodesGoogle SheetsHTTP RequestSlackGmail
Email & Gmail Trigger: Cron / scheduled Nodes: 22 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → Google Sheets 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": "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
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

E-commerce store owners, product managers, marketplace sellers, and pricing analysts who want to automatically track competitor pricing and get actionable alerts when their products are overpriced or underpriced relative to the market.

Source: https://n8n.io/workflows/13836/ — 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

This template is ideal for developers, agencies, hosting providers, and website owners who need real-time alerts when a website goes down. It helps teams react quickly to downtime by sending multi-cha

Slack, HTTP Request, Gmail +1
Email & Gmail

Schedule Slack. Uses scheduleTrigger, googleSheets, slack, gmail. Scheduled trigger; 15 nodes.

Google Sheets, Slack, Gmail +1
Email & Gmail

This n8n workflow demonstrates how to build a simple uptime monitoring service using scheduled triggers.

Google Sheets, Slack, Gmail +1
Email & Gmail

Restaurant owners, retail store managers, and small business owners who want to stay on top of customer feedback without manually checking Google reviews multiple times a day. Perfect for businesses t

HTTP Request, Chain Llm, Google Sheets +3
Email & Gmail

YOUR_ID 4. Uses gmail, googleDrive, googleSheets, httpRequest. Scheduled trigger; 53 nodes.

Gmail, Google Drive, Google Sheets +1