{
  "id": "f9AthtT0W1WRa0iy",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "WF1 \u2013 Amazon Price Drop Alert",
  "tags": [],
  "nodes": [
    {
      "id": "d98f91fd-5437-4887-bbdb-e07bab933f02",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2176,
        416
      ],
      "parameters": {
        "width": 688,
        "height": 880,
        "content": "## WF1 \u2013 Amazon Price Drop Alert\n\n### How it works\n\nThis workflow monitors Amazon product prices on a recurring schedule. Every four hours it loads a product list from Google Sheets, loops through each item, scrapes the Amazon page, extracts the title and price, logs the result, and sends a Gmail alert if the price is below the product\u2019s threshold. A separate manual trigger is present on the canvas but is not connected to the active flow.\n\n### Setup steps\n\n- Configure Google Sheets credentials for both reading the tracked product list and writing price history.\n- Set up the product sheet with the expected columns, such as product URL, target price threshold, and any alert recipient or product metadata used by the nodes.\n- Configure the scraping service credentials or settings in the Scrape Amazon Product Page node so Amazon pages can be fetched reliably.\n- Configure Gmail credentials and verify the Send Price Drop Alert node has the correct recipient, subject, and message body.\n- Review the schedule trigger interval and activate the workflow when credentials and sheet mappings are confirmed.\n\n### Customization\n\nAdjust the schedule frequency, threshold logic, email template, product sheet columns, or the extraction code if Amazon page structure or required product fields change."
      },
      "typeVersion": 1
    },
    {
      "id": "67fb9919-3bec-44fb-a56d-924cce43d7f2",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2944,
        448
      ],
      "parameters": {
        "color": 7,
        "height": 272,
        "content": "## Manual test trigger\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "bdf1e1e2-6954-4ae6-b45b-539f3afa6e6c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2944,
        768
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 400,
        "content": "## Load product list\n\nStarts the scheduled run every four hours, reads the tracked products from Google Sheets, and feeds them one at a time into the batch loop."
      },
      "typeVersion": 1
    },
    {
      "id": "380525a7-d75a-47b9-8f79-e18bb2104efa",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3696,
        528
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 640,
        "content": "## Scrape product details\n\nFor each product in the loop, fetches the Amazon product page through the scraping service and uses custom code to extract the product title and current price."
      },
      "typeVersion": 1
    },
    {
      "id": "049d1f6f-ba63-4d54-86d1-a15c42c2cd6d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4384,
        656
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 528,
        "content": "## Log and alert\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2a3954af-5d23-4201-9b95-d6d0cb822986",
      "name": "Manual Execution Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        2992,
        576
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "8a870a38-543c-49ec-9038-7df3c215a92c",
      "name": "Scrape Amazon Product Details",
      "type": "n8n-nodes-scrapeunblocker.scrapeUnblocker",
      "position": [
        3808,
        752
      ],
      "parameters": {
        "url": "https://www.amazon.com/Logitech-Wireless-Lightspeed-Headset-Headphone/dp/B081PP4CB6/?th=1"
      },
      "credentials": {},
      "typeVersion": 1
    },
    {
      "id": "80581ff2-16e2-4042-b763-ba32f1f4be9b",
      "name": "Extract Product Details",
      "type": "n8n-nodes-base.code",
      "position": [
        4128,
        768
      ],
      "parameters": {
        "jsCode": "const returnItems = [];\n\nfor (const item of $input.all()) {\n  // 1. Find the HTML string no matter where ScrapeUnblocker hid it\n  let html = \"\";\n  if (typeof item.json === 'string') {\n    html = item.json;\n  } else if (item.json && typeof item.json === 'object') {\n    html = item.json.data || item.json.html || item.json.body || JSON.stringify(item.json);\n  }\n\n  // FAIL-SAFE: If the previous node actually sent nothing, stop here and report it.\n  if (!html || html.trim() === \"\" || html === \"{}\") {\n    returnItems.push({\n      json: {\n        error: \"No HTML received. Check the ScrapeUnblocker node output to ensure it is actually loading the page.\"\n      }\n    });\n    continue;\n  }\n\n  // 2. Extract Title (id=\"productTitle\" is highly stable on Amazon)\n  let title = \"Not found\";\n  const titleMatch = html.match(/id=\"productTitle\"[^>]*>([\\s\\S]*?)<\\//i);\n  if (titleMatch) {\n    title = titleMatch[1].replace(/<[^>]+>/g, '').trim();\n  }\n\n  // 3. Extract Price (Looks for the hidden screen-reader text inside the price block)\n  let price = \"Not found\";\n  const priceMatch = html.match(/class=\"a-price\"[^>]*>[\\s\\S]*?class=\"a-offscreen\">([^<]+)<\\//i)\n                  || html.match(/id=\"corePrice[^>]*>[\\s\\S]*?class=\"a-offscreen\">([^<]+)<\\//i);\n  if (priceMatch) {\n    price = priceMatch[1].trim();\n  }\n\n  // 3b. Clean price -> pure number for comparisons (strips S$, $, \u00a3, commas, spaces, etc.)\n  let price_clean = null;\n  if (price !== \"Not found\") {\n    const num = parseFloat(price.replace(/[^0-9.]/g, ''));\n    price_clean = isNaN(num) ? null : num;\n  }\n\n  // 4. Create a clean text block for Gemini to read the features/description\n  let safeText = html.replace(/<(script|style|svg|path)[^>]*>[\\s\\S]*?<\\/\\1>/gi, ' ');\n  safeText = safeText.replace(/<[^>]+>/g, ' ');\n  safeText = safeText.replace(/&nbsp;|&#160;/gi, ' ');\n  safeText = safeText.replace(/\\s+/g, ' ').trim();\n  safeText = safeText.substring(0, 20000);\n\n  // 5. Output the results\n  returnItems.push({\n    json: {\n      scraped_title: title,\n      scraped_price: price,\n      price_clean: price_clean,\n      clean_text_for_gemini: safeText\n    }\n  });\n}\n\nreturn returnItems;"
      },
      "typeVersion": 2
    },
    {
      "id": "7a89d379-13aa-405e-ae4d-6a9f9e87e566",
      "name": "Read Products from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3232,
        976
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs/edit#gid=0",
          "cachedResultName": "Products"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs/edit?usp=drivesdk",
          "cachedResultName": "Amazon Price Tracker"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "3a62ae6a-dd7a-4aa1-a63b-68ee158f3800",
      "name": "Loop Over Product Batches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        3520,
        976
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "de309ffe-34a9-4616-a7ce-dde8be432a80",
      "name": "Update Price History in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4464,
        768
      ],
      "parameters": {
        "columns": {
          "value": {
            "Last Price": "={{ $('Loop Over Product Batches').item.json[\"Price Threshold\"] }}",
            "row_number": "={{ $('Loop Over Product Batches').item.json.row_number }}",
            "price_clean": "={{ $json.price_clean }}",
            "Last Checked": "={{ $now.format('yyyy-MM-dd') }}",
            "Product Title": "={{ $json.scraped_title }}",
            "Price Threshold": "={{ $json.scraped_price }}"
          },
          "schema": [
            {
              "id": "Product URL",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Product URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Price Threshold",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Price Threshold",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Product Title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Product Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "price_clean",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "price_clean",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Checked",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Checked",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "row_number"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs/edit#gid=0",
          "cachedResultName": "Products"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1FosokWfr3ouHhVsERNO7yvOcB58TO2EV6un9HzDFPAs/edit?usp=drivesdk",
          "cachedResultName": "Amazon Price Tracker"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "ab673cf2-293d-42e7-b488-3d83227f986c",
      "name": "Check Price Threshold",
      "type": "n8n-nodes-base.if",
      "position": [
        4704,
        768
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-below",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $('Extract Product Details').item.json.price_clean }}",
              "rightValue": "={{ $('Loop Over Product Batches').item.json.price_clean }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "80bb0ed1-062f-4b8d-ad1d-c502684bfd02",
      "name": "Send Email Price Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5008,
        944
      ],
      "parameters": {
        "sendTo": "YOUR EMAIL",
        "message": "=The price for a product you're tracking has dropped below your threshold.\n\nProduct: {{ $('Extract Product Details').item.json.scraped_title }}\nCurrent Price: {{ $('Extract Product Details').item.json.scraped_price }}\nYour Threshold: {{ $('Loop Over Product Batches').item.json['Price Threshold'] }}\n\nView / Buy: {{ $('Loop Over Product Batches').item.json['Product URL'] }}\n\nChecked at: {{ $now.format('yyyy-MM-dd HH:mm') }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "=\ud83d\udd3b Price Drop Alert: {{ $('Extract Product Details').item.json.scraped_title }}",
        "emailType": "text"
      },
      "typeVersion": 2.2
    },
    {
      "id": "fbc6f3ae-162c-4b1c-b88f-26f8dd153c1f",
      "name": "Trigger Every 4 Hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3008,
        976
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 4
            }
          ]
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "322e9be5-cbf1-4c54-80af-d430c92ee2bb",
  "nodeGroups": [],
  "connections": {
    "Check Price Threshold": {
      "main": [
        [
          {
            "node": "Loop Over Product Batches",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Email Price Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger Every 4 Hours": {
      "main": [
        [
          {
            "node": "Read Products from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Price Alert": {
      "main": [
        [
          {
            "node": "Loop Over Product Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Product Details": {
      "main": [
        [
          {
            "node": "Update Price History in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Product Batches": {
      "main": [
        [],
        [
          {
            "node": "Scrape Amazon Product Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Products from Sheets": {
      "main": [
        [
          {
            "node": "Loop Over Product Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Amazon Product Details": {
      "main": [
        [
          {
            "node": "Extract Product Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Price History in Sheets": {
      "main": [
        [
          {
            "node": "Check Price Threshold",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}