{
  "nodes": [
    {
      "id": "d18bd12f-e041-449a-8bca-a8906a2e71cd",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1696,
        112
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Email daily price-drop digests from Amazon, Walmart and Google via ScraperAPI\n\n### How it works\n\n1. Triggers the workflow every morning at 8 am or manually during setup.\n2. Initializes or reads product and price tables.\n3. Loops through each product to fetch current product data from various sources.\n4. Compares fetched data and updates product prices.\n5. Checks for price drops, compiles a digest, and sends an email.\n6. Provides an option to send Gmail digest emails.\n\n### Setup steps\n\n- [ ] Set up and verify scheduler trigger timing\n- [ ] Configure database credentials\n- [ ] Set API keys for Amazon, Walmart, Google Shopping\n- [ ] Link an email service provider or SMTP\n- [ ] Ensure that the Gmail module is authenticated\n\n### Customization\n\nYou can customize the schedule to a different time and modify data sources or email templates."
      },
      "typeVersion": 1
    },
    {
      "id": "a4648c97-6bbd-4dec-a84b-808032d6e46d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1136,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Morning trigger setup\n\nTriggers the workflow every morning at 8 am."
      },
      "typeVersion": 1
    },
    {
      "id": "fe082e45-1237-44e0-91e8-85d220940959",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 1088,
        "height": 272,
        "content": "## Manual setup and table initialization\n\nManual trigger to set up product and price history tables."
      },
      "typeVersion": 1
    },
    {
      "id": "f170729a-c66e-42cf-bb59-ced6db967535",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        608
      ],
      "parameters": {
        "color": 7,
        "width": 1536,
        "height": 320,
        "content": "## Fetch and iterate products\n\nFetches all products and prepares for individual product processing."
      },
      "typeVersion": 1
    },
    {
      "id": "5a425a67-d3db-40d2-9b26-60e9efb40a30",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 272,
        "content": "## Parse and fetch product data\n\nParses product URLs and fetches data from Amazon, Walmart, and Google Shopping APIs."
      },
      "typeVersion": 1
    },
    {
      "id": "51e1d161-93c1-488b-b642-7309a7a9f949",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        304
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "## Process and compare product data\n\nCompares fetched product data and updates the database with new prices."
      },
      "typeVersion": 1
    },
    {
      "id": "bf40277a-85a9-4921-8e8d-c13f0f02d0b3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        448,
        1072
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "## Price drop analysis and email\n\nFetches today's price drops, creates a digest, and sends out an email notification."
      },
      "typeVersion": 1
    },
    {
      "id": "b8bdcf9d-8b4f-47d0-9de6-5a2c780793ad",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        896,
        1376
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Standalone Gmail email\n\nHandles sending the digest email via Gmail independently."
      },
      "typeVersion": 1
    },
    {
      "id": "daily-trigger",
      "name": "Morning Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1088,
        720
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "setup-trigger",
      "name": "Manual Trigger Setup",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        48,
        112
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "create-products-table",
      "name": "Initialize Products Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        240,
        112
      ],
      "parameters": {
        "columns": {
          "column": [
            {
              "name": "name",
              "type": "string"
            },
            {
              "name": "amazon_url",
              "type": "string"
            },
            {
              "name": "walmart_url",
              "type": "string"
            },
            {
              "name": "track_google_shopping",
              "type": "boolean"
            },
            {
              "name": "target_price",
              "type": "number"
            },
            {
              "name": "last_amazon_price",
              "type": "number"
            },
            {
              "name": "last_walmart_price",
              "type": "number"
            },
            {
              "name": "last_google_min_price",
              "type": "number"
            }
          ]
        },
        "options": {
          "createIfNotExists": true
        },
        "resource": "table",
        "operation": "create",
        "tableName": "products"
      },
      "typeVersion": 1.1
    },
    {
      "id": "create-history-table",
      "name": "Initialize History Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        416,
        112
      ],
      "parameters": {
        "columns": {
          "column": [
            {
              "name": "timestamp",
              "type": "string"
            },
            {
              "name": "product_name",
              "type": "string"
            },
            {
              "name": "platform",
              "type": "string"
            },
            {
              "name": "current_price",
              "type": "number"
            },
            {
              "name": "previous_price",
              "type": "number"
            },
            {
              "name": "dropped",
              "type": "boolean"
            },
            {
              "name": "pct_drop",
              "type": "number"
            },
            {
              "name": "error",
              "type": "string"
            }
          ]
        },
        "options": {
          "createIfNotExists": true
        },
        "resource": "table",
        "operation": "create",
        "tableName": "price_history"
      },
      "typeVersion": 1.1
    },
    {
      "id": "read-existing-products",
      "name": "Read Existing Products",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        592,
        112
      ],
      "parameters": {
        "filters": {
          "conditions": []
        },
        "resource": "row",
        "matchType": "anyCondition",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "products"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "seed-sample-products",
      "name": "Generate Sample Products",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        112
      ],
      "parameters": {
        "jsCode": "const existingNames = new Set(\n  $input.all()\n    .map(i => (i.json && i.json.name ? String(i.json.name).trim().toLowerCase() : ''))\n    .filter(Boolean)\n);\n\nconst samples = [\n  {\n    name: 'Samsung Galaxy Watch Ultra',\n    amazon_url: 'https://www.amazon.com/dp/B0F7Q4L81N?th=1',\n    walmart_url: 'https://www.walmart.com/ip/Samsung-Galaxy-Watch-Ultra-47mm-2025-Edition-Bluetooth-Wi-Fi-4GLTE-Smart-Fitness-Watch-International-Version-Titanium-Blue/17452806183',\n    track_google_shopping: true,\n    target_price: null,\n    last_amazon_price: null,\n    last_walmart_price: null,\n    last_google_min_price: null,\n  },\n  {\n    name: 'Apple Watch Series 11',\n    amazon_url: 'https://www.amazon.com/dp/B0FQFL8PZ5?th=1',\n    walmart_url: 'https://www.walmart.com/ip/Apple-Watch-Series-11-GPS-Cellular-42mm-Gold-Titanium-Case-with-Gold-Milanese-Loop/17828855921',\n    track_google_shopping: true,\n    target_price: null,\n    last_amazon_price: null,\n    last_walmart_price: null,\n    last_google_min_price: null,\n  },\n  {\n    name: 'XIAOMI Redmi Watch 5',\n    amazon_url: 'https://www.amazon.com/dp/B0DFZPR9Z4?th=1',\n    walmart_url: 'https://www.walmart.com/ip/Redmi-Watch-5-Active-Midnight-Black-18-Day-Battery-Life-2-Inch-Display-5ATM-Waterproof-Bluetooth-Calling-Alexa-Built-In/15893513091',\n    track_google_shopping: true,\n    target_price: null,\n    last_amazon_price: null,\n    last_walmart_price: null,\n    last_google_min_price: null,\n  },\n];\n\nreturn samples\n  .filter(s => !existingNames.has(s.name.toLowerCase()))\n  .map(s => ({ json: s }));\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "insert-sample-products",
      "name": "Add Samples to Products Table",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        944,
        112
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "amazon_url": "={{ $json.amazon_url }}",
            "walmart_url": "={{ $json.walmart_url }}",
            "target_price": "={{ $json.target_price }}",
            "last_amazon_price": "={{ $json.last_amazon_price }}",
            "last_walmart_price": "={{ $json.last_walmart_price }}",
            "last_google_min_price": "={{ $json.last_google_min_price }}",
            "track_google_shopping": "={{ $json.track_google_shopping }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "products"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "read-products",
      "name": "Fetch Products Data",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -864,
        720
      ],
      "parameters": {
        "filters": {
          "conditions": []
        },
        "resource": "row",
        "matchType": "anyCondition",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "products"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "per-product-loop",
      "name": "Iterate Products Batch",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -544,
        720
      ],
      "parameters": {
        "options": {},
        "batchSize": 1
      },
      "typeVersion": 3
    },
    {
      "id": "parse-urls",
      "name": "Extract Product URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        -416,
        416
      ],
      "parameters": {
        "jsCode": "const row = $input.first().json;\n\nconst stripQs = (u) => (u || '').split(/[?#]/)[0];\nconst amazonUrl = stripQs(row.amazon_url);\nconst walmartUrl = stripQs(row.walmart_url);\n\nconst asinMatch = amazonUrl.match(/\\/(?:dp|gp\\/product)\\/([A-Z0-9]{10})/i);\nconst walmartMatch = walmartUrl.match(/walmart\\.com\\/ip\\/(?:[^/]+\\/)?(\\d+)/i);\n\nconst trackGoogle = row.track_google_shopping === true || row.track_google_shopping === 'true' || row.track_google_shopping === 1;\n\nreturn [{\n  json: {\n    id: row.id,\n    name: row.name || '',\n    amazon_asin: asinMatch ? asinMatch[1].toUpperCase() : '',\n    walmart_product_id: walmartMatch ? walmartMatch[1] : '',\n    google_query: trackGoogle ? (row.name || '') : '',\n    target_price: row.target_price ?? null,\n    last_amazon_price: row.last_amazon_price ?? null,\n    last_walmart_price: row.last_walmart_price ?? null,\n    last_google_min_price: row.last_google_min_price ?? null,\n  },\n}];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "amazon-sde",
      "name": "Scrape Amazon Data",
      "type": "n8n-nodes-scraperapi-official.scraperApi",
      "maxTries": 2,
      "position": [
        -192,
        416
      ],
      "parameters": {
        "sdeAsin": "={{ $('Extract Product URLs').item.json.amazon_asin }}",
        "resource": "sde",
        "operation": "amazonProduct",
        "sdePlatform": "amazon",
        "sdeAmazonOptions": {}
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "continueOnFail": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "walmart-sde",
      "name": "Scrape Walmart Data",
      "type": "n8n-nodes-scraperapi-official.scraperApi",
      "maxTries": 2,
      "position": [
        32,
        416
      ],
      "parameters": {
        "resource": "sde",
        "operation": "walmartProduct",
        "sdePlatform": "walmart",
        "sdeProductId": "={{ $('Extract Product URLs').item.json.walmart_product_id }}",
        "sdeWalmartOptions": {}
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "continueOnFail": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "google-shopping-sde",
      "name": "Scrape Google Shopping Data",
      "type": "n8n-nodes-scraperapi-official.scraperApi",
      "maxTries": 2,
      "position": [
        256,
        416
      ],
      "parameters": {
        "resource": "sde",
        "sdeQuery": "={{ $('Extract Product URLs').item.json.google_query }}",
        "operation": "googleShopping",
        "sdePlatform": "google",
        "sdeGoogleOptions": {}
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "continueOnFail": true,
      "waitBetweenTries": 2000
    },
    {
      "id": "compare-results",
      "name": "Analyze Product Data",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        416
      ],
      "parameters": {
        "jsCode": "const parsed = $('Extract Product URLs').item.json;\n\nconst numOrNull = (v) => {\n  if (v === null || v === undefined || v === '') return null;\n  const s = typeof v === 'string' ? v.replace(/[^0-9.]/g, '') : v;\n  const n = parseFloat(s);\n  return Number.isFinite(n) ? n : null;\n};\n\nconst getBody = (item) => (item && item.response && item.response.body) || (item && item.body) || item;\n\nconst readPrice = (nodeName, paths) => {\n  try {\n    const item = $(nodeName).item.json;\n    if (!item) return { price: null, error: null };\n    if (item.error) {\n      const msg = (item.error && (item.error.message || item.error.description)) || String(item.error);\n      return { price: null, error: msg };\n    }\n    const body = getBody(item);\n    for (const path of paths) {\n      const parts = path.split('.');\n      let cur = body;\n      for (const p of parts) cur = cur == null ? cur : cur[p];\n      const n = numOrNull(cur);\n      if (n !== null) return { price: n, error: null };\n    }\n    return { price: null, error: null };\n  } catch (e) {\n    return { price: null, error: e.message };\n  }\n};\n\nconst amazon = readPrice('Amazon Product', ['pricing', 'list_price', 'price']);\nconst walmart = readPrice('Walmart Product', ['price', 'current_price.price', 'pricing']);\n\nlet google = { price: null, error: null };\ntry {\n  const g = $('Scrape Google Shopping Data').item.json;\n  if (g && g.error) {\n    google.error = (g.error && (g.error.message || g.error.description)) || String(g.error);\n  } else if (g) {\n    const body = getBody(g);\n    const items = body.shopping_results || body.results || [];\n    const prices = items\n      .map(it => numOrNull(it && (it.extracted_price !== undefined ? it.extracted_price : it.price)))\n      .filter(p => p !== null);\n    google.price = prices.length ? Math.min(...prices) : null;\n  }\n} catch (e) {\n  google.error = e.message;\n}\n\nconst ts = new Date().toISOString();\nconst target = parsed.target_price;\n\nconst evaluate = (platform, current, previous, error) => {\n  const dropped = current !== null && previous !== null && previous > 0 && current < previous;\n  const pct_drop = dropped ? Number((((previous - current) / previous) * 100).toFixed(2)) : 0;\n  const crossed_target = Number.isFinite(target) && Number.isFinite(current) && Number.isFinite(previous) && previous > target && current <= target;\n  return {\n    timestamp: ts,\n    product_name: parsed.name,\n    platform,\n    current_price: current,\n    previous_price: previous,\n    dropped: dropped || crossed_target,\n    pct_drop,\n    error: error || '',\n  };\n};\n\nconst results = [];\nif (parsed.amazon_asin) results.push(evaluate('amazon', amazon.price, parsed.last_amazon_price, amazon.error));\nif (parsed.walmart_product_id) results.push(evaluate('walmart', walmart.price, parsed.last_walmart_price, walmart.error));\nif (parsed.google_query) results.push(evaluate('google_shopping', google.price, parsed.last_google_min_price, google.error));\n\nconst update = {\n  id: parsed.id,\n  last_amazon_price: amazon.price !== null ? amazon.price : (parsed.last_amazon_price != null ? parsed.last_amazon_price : null),\n  last_walmart_price: walmart.price !== null ? walmart.price : (parsed.last_walmart_price != null ? parsed.last_walmart_price : null),\n  last_google_min_price: google.price !== null ? google.price : (parsed.last_google_min_price != null ? parsed.last_google_min_price : null),\n};\n\nreturn [{ json: { results, update } }];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "split-history",
      "name": "Divide History Results",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        704,
        416
      ],
      "parameters": {
        "include": "noOtherFields",
        "options": {},
        "fieldToSplitOut": "results"
      },
      "typeVersion": 1
    },
    {
      "id": "append-history",
      "name": "Add to Price History",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        928,
        416
      ],
      "parameters": {
        "columns": {
          "value": {
            "error": "={{ $json.error }}",
            "dropped": "={{ $json.dropped }}",
            "pct_drop": "={{ $json.pct_drop }}",
            "platform": "={{ $json.platform }}",
            "timestamp": "={{ $json.timestamp }}",
            "product_name": "={{ $json.product_name }}",
            "current_price": "={{ $json.current_price }}",
            "previous_price": "={{ $json.previous_price }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "resource": "row",
        "operation": "insert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "price_history"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "update-products",
      "name": "Refresh Product Prices",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        608,
        720
      ],
      "parameters": {
        "columns": {
          "value": {
            "last_amazon_price": "={{ $json.update.last_amazon_price }}",
            "last_walmart_price": "={{ $json.update.last_walmart_price }}",
            "last_google_min_price": "={{ $json.update.last_google_min_price }}"
          },
          "mappingMode": "defineBelow"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "id",
              "keyValue": "={{ $json.update.id }}",
              "condition": "eq"
            }
          ]
        },
        "options": {},
        "resource": "row",
        "matchType": "allConditions",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "products"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "read-todays-drops",
      "name": "Retrieve Today's Price Drops",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        496,
        1184
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "dropped",
              "keyValue": "true",
              "condition": "eq"
            },
            {
              "keyName": "timestamp",
              "keyValue": "={{ $now.startOf('day').toISO() }}",
              "condition": "gte"
            }
          ]
        },
        "resource": "row",
        "matchType": "allConditions",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "price_history"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.1
    },
    {
      "id": "build-digest",
      "name": "Compile Price Drop Digest",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        1184
      ],
      "parameters": {
        "jsCode": "const drops = $input.all().map(i => i.json);\nif (drops.length === 0) return [];\n\ndrops.sort((a, b) => (b.pct_drop || 0) - (a.pct_drop || 0));\n\nconst esc = (s) => String(s == null ? '' : s).replace(/[&<>\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[c]));\nconst fmt = (n) => n == null || n === '' ? '\u2014' : '$' + Number(n).toFixed(2);\n\nconst rowsHtml = drops.map(d => `<tr>\n  <td>${esc(d.product_name)}</td>\n  <td>${esc(d.platform)}</td>\n  <td style=\"text-align:right;\">${fmt(d.previous_price)}</td>\n  <td style=\"text-align:right;color:#0a7d27;font-weight:600;\">${fmt(d.current_price)}</td>\n  <td style=\"text-align:right;\">${d.pct_drop ? d.pct_drop + '%' : '\u2014'}</td>\n</tr>`).join('');\n\nconst subject = `Price drop alert \u2014 ${drops.length} product${drops.length === 1 ? '' : 's'} dropped today`;\nconst html = `<div style=\"font-family:-apple-system,Segoe UI,sans-serif;max-width:680px;\">\n  <h2 style=\"margin:0 0 12px;\">${subject}</h2>\n  <p style=\"color:#555;margin:0 0 16px;\">Fresh prices from Amazon, Walmart, and Google Shopping \u2014 collected on autopilot by ScraperAPI.</p>\n  <table cellpadding=\"8\" cellspacing=\"0\" style=\"border-collapse:collapse;width:100%;font-size:14px;\">\n    <thead><tr style=\"background:#f4f4f6;text-align:left;\">\n      <th>Product</th><th>Marketplace</th><th style=\"text-align:right;\">Was</th><th style=\"text-align:right;\">Now</th><th style=\"text-align:right;\">Drop</th>\n    </tr></thead>\n    <tbody>${rowsHtml}</tbody>\n  </table>\n</div>`;\n\nreturn [{ json: { count: drops.length, subject, html } }];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "send-email",
      "name": "Email Price Drop Notice",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        944,
        1184
      ],
      "parameters": {
        "html": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "",
        "fromEmail": "",
        "emailFormat": "html"
      },
      "typeVersion": 2.1
    },
    {
      "id": "send-email-gmail",
      "name": "Send Digest via Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        944,
        1504
      ],
      "parameters": {
        "sendTo": "",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "emailType": "html"
      },
      "typeVersion": 2.1
    }
  ],
  "connections": {
    "Scrape Amazon Data": {
      "main": [
        [
          {
            "node": "Scrape Walmart Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Products Data": {
      "main": [
        [
          {
            "node": "Iterate Products Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Walmart Data": {
      "main": [
        [
          {
            "node": "Scrape Google Shopping Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Product Data": {
      "main": [
        [
          {
            "node": "Divide History Results",
            "type": "main",
            "index": 0
          },
          {
            "node": "Refresh Product Prices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Product URLs": {
      "main": [
        [
          {
            "node": "Scrape Amazon Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger Setup": {
      "main": [
        [
          {
            "node": "Initialize Products Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Divide History Results": {
      "main": [
        [
          {
            "node": "Add to Price History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Iterate Products Batch": {
      "main": [
        [
          {
            "node": "Retrieve Today's Price Drops",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Extract Product URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Existing Products": {
      "main": [
        [
          {
            "node": "Generate Sample Products",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Refresh Product Prices": {
      "main": [
        [
          {
            "node": "Iterate Products Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Sample Products": {
      "main": [
        [
          {
            "node": "Add Samples to Products Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize History Table": {
      "main": [
        [
          {
            "node": "Read Existing Products",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Morning Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch Products Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compile Price Drop Digest": {
      "main": [
        [
          {
            "node": "Email Price Drop Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize Products Table": {
      "main": [
        [
          {
            "node": "Initialize History Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Google Shopping Data": {
      "main": [
        [
          {
            "node": "Analyze Product Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Today's Price Drops": {
      "main": [
        [
          {
            "node": "Compile Price Drop Digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}