{
  "id": "r3lMp8YtaKakgEp6",
  "name": "AppSumo Lifetime Deal Monitor with ScrapeOps and Google Sheets",
  "tags": [
    {
      "id": "Doq8CwkJgEUz5O8W",
      "name": "AppSumo Deal Tracker",
      "createdAt": "2026-03-10T07:27:04.779Z",
      "updatedAt": "2026-03-10T07:27:04.779Z"
    },
    {
      "id": "IzlHq5HmwX4xIIHt",
      "name": "Lifetime Deal Monitor",
      "createdAt": "2026-03-10T07:27:36.260Z",
      "updatedAt": "2026-03-10T07:27:36.260Z"
    },
    {
      "id": "Nabo4ItzmGCcNoDA",
      "name": "SaaS Deal Tracking",
      "createdAt": "2026-03-10T07:27:41.598Z",
      "updatedAt": "2026-03-10T07:27:41.598Z"
    },
    {
      "id": "lZKSh2IoxHklnOUw",
      "name": "ScrapeOps",
      "createdAt": "2025-10-20T20:27:13.410Z",
      "updatedAt": "2025-10-20T20:27:13.410Z"
    },
    {
      "id": "yzylwxvLF3YwGBRm",
      "name": "Google Sheets Automation",
      "createdAt": "2026-03-10T07:03:25.329Z",
      "updatedAt": "2026-03-10T07:03:25.329Z"
    }
  ],
  "nodes": [
    {
      "id": "d1bad033-e9cb-45f7-b7de-03b88d3340c2",
      "name": "Daily Schedule Trigger (09:00)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        800,
        512
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "bc66da17-e038-4442-9899-0ce0d04a4b72",
      "name": "Fetch AppSumo Deals via ScrapeOps (JS Render)",
      "type": "@scrapeops/n8n-nodes-scrapeops.ScrapeOps",
      "position": [
        1024,
        512
      ],
      "parameters": {
        "url": "https://appsumo.com/browse/?tags=lifetime-deal",
        "advancedOptions": {
          "render_js": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "78501754-4e71-43f1-8c76-35ac809f86e8",
      "name": "Parse AppSumo Deals HTML \u2192 Structured JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        512
      ],
      "parameters": {
        "jsCode": "// Parse AppSumo HTML to extract deals using String Splitting (Robust & No External Libs)\nconst item = items[0].json;\n// Extract HTML from ScrapeOps response - try multiple formats\nlet html = '';\n\n// ScrapeOps can return data in different formats\nif (typeof item === 'string') {\n  html = item;\n} else if (item.response?.body) {\n  html = item.response.body;\n} else if (item.body) {\n  html = item.body;\n} else if (item.content) {\n  html = item.content;\n} else if (item.data) {\n  html = item.data;\n} else if (Array.isArray(item)) {\n  // If it's an array, join it\n  html = item.join('');\n} else {\n  // Last resort - stringify and check\n  const str = JSON.stringify(item);\n  if (str.includes('<!DOCTYPE') || str.includes('<html')) {\n    html = str;\n  }\n}\n\nconst deals = [];\n\n// Split by the deal container identified in the HTML\n// Using a broader regex to catch variations in class spacing\nconst cardChunks = html.split(/class=[\"'][^\"']*relative h-full[^\"']*[\"']/i);\n\n// Skip the first chunk (header/nav)\nfor (let i = 1; i < cardChunks.length; i++) {\n  const card = cardChunks[i];\n  \n  // Extract Title and URL\n  const urlMatch = card.match(/href=[\"'](\\/products\\/[^\"']+)[\"']/i);\n  const titleMatch = card.match(/<span class=[\"']sr-only[\"']>([^<]+)<\\/span>/i);\n  \n  if (urlMatch && titleMatch) {\n    const url = `https://appsumo.com${urlMatch[1]}`;\n    const title = titleMatch[1];\n    \n    // Image\n    const imgMatch = card.match(/<img[^>]+src=[\"']([^\"']+)[\"']/i);\n    const img = imgMatch ? imgMatch[1] : null;\n    \n    // Price\n    const priceMatch = card.match(/id=[\"']deal-price[\"'][^>]*>([^<]+)</i);\n    const currentPrice = priceMatch ? priceMatch[1].trim() : null;\n    \n    // Original Price\n    const lastPriceMatch = card.match(/id=[\"']deal-price-original[\"'][^>]*>([^<]+)</i);\n    const lastPrice = lastPriceMatch ? lastPriceMatch[1].trim() : null;\n    \n    // Discount Calculation\n    let discount = null;\n    if (currentPrice && lastPrice) {\n        try {\n            const curr = parseFloat(currentPrice.replace(/[^0-9.]/g, ''));\n            const last = parseFloat(lastPrice.replace(/[^0-9.]/g, ''));\n            if (last > 0) {\n                const off = Math.round(((last - curr) / last) * 100);\n                discount = `${off}% Off`;\n            }\n        } catch (e) {}\n    }\n    \n    // Category\n    // HTML: in<!-- --> <a ...>Productivity</a>\n    // Regex needs to be loose about spaces and attributes\n    const catMatch = card.match(/in<!-- -->\\s*<a[^>]*>([^<]+)<\\/a>/i);\n    const category = catMatch ? catMatch[1].trim() : null;\n    \n    // Rating\n    const ratingMatch = card.match(/alt=[\"']([0-9.]+)\\s*stars[\"']/i);\n    const rating = ratingMatch ? ratingMatch[1] : null;\n    \n    // Reviews\n    // HTML: <span>92<!-- --> reviews</span>\n    const reviewsMatch = card.match(/<span>(\\d+)<!-- -->\\s*reviews<\\/span>/i);\n    const reviewsCount = reviewsMatch ? reviewsMatch[1] : null;\n    \n    // Status\n    let dealStatus = \"Active\";\n    if (card.match(/Deal ends in/i)) dealStatus = \"Ending Soon\";\n    if (card.match(/Sold Out/i)) dealStatus = \"Sold Out\";\n    if (card.match(/Expired/i)) dealStatus = \"Expired\";\n\n    deals.push({\n      \"Product Name\": title,\n      \"AppSumo URL\": url,\n      \"Category\": category,\n      \"Current Price\": currentPrice,\n      \"Last Price\": lastPrice,\n      \"Discount\": discount,\n      \"Deal Status\": dealStatus,\n      \"Rating\": rating,\n      \"Reviews Count\": reviewsCount,\n      \"Image\": img,\n      \"scraped_at\": new Date().toISOString()\n    });\n  }\n}\n\nif (deals.length === 0) {\n  return [{\n    json: {\n      \"Debug\": \"No deals found\",\n      \"HTML Length\": html ? html.length : 0,\n      \"Input Keys\": Object.keys(item),\n      \"HTML Preview\": html ? html.substring(0, 500) : \"No HTML found\",\n      \"Split Chunks\": cardChunks.length,\n      \"Item Type\": typeof item,\n      \"Is Array\": Array.isArray(item)\n    }\n  }];\n}\n\nreturn deals.map(deal => ({ json: deal }));"
      },
      "typeVersion": 2
    },
    {
      "id": "cb136318-0c2b-4db8-be62-d0bb91e9522a",
      "name": "Lookup Deal in Google Sheet (by URL)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2032,
        512
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json[\"AppSumo URL\"] }}",
              "lookupColumn": "AppSumo URL"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4,
      "continueOnFail": true
    },
    {
      "id": "123561d8-ec1c-4c1e-9a1e-ca717dc85f24",
      "name": "Merge Scrape + Existing Row (Enrich by URL)",
      "type": "n8n-nodes-base.merge",
      "position": [
        2240,
        512
      ],
      "parameters": {
        "mode": "combine",
        "options": {
          "fuzzyCompare": false,
          "multipleMatches": "first",
          "disableDotNotation": false
        },
        "joinMode": "enrichInput1",
        "mergeByFields": {
          "values": [
            {
              "field1": "AppSumo URL",
              "field2": "AppSumo URL"
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "28505fc8-7c3d-45fc-bcb3-54125d32465e",
      "name": "IF New Deal (Last Checked is empty)",
      "type": "n8n-nodes-base.if",
      "position": [
        2480,
        512
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json['Last Checked'] }}",
              "operation": "isEmpty"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a87a44c2-7db6-4a15-b4bf-ed053b718002",
      "name": "Stamp Last Checked (New Deal Path)",
      "type": "n8n-nodes-base.code",
      "position": [
        2720,
        416
      ],
      "parameters": {
        "jsCode": "// Prepare New Deal - Process ALL items\nconst output = [];\n\nfor (const item of items) {\n  output.push({\n    json: {\n      ...item.json,\n      'Last Checked': new Date().toISOString()\n    }\n  });\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "efb4c2a2-56b3-4305-8e54-dc21a627b22a",
      "name": "Stamp Last Checked (Existing Deal Path)",
      "type": "n8n-nodes-base.code",
      "position": [
        2720,
        624
      ],
      "parameters": {
        "jsCode": "// Compare Existing Deal - Process ALL items\nconst output = [];\n\nfor (const item of items) {\n  // Just update timestamp\n  output.push({\n    json: {\n      ...item.json,\n      'Last Checked': new Date().toISOString()\n    }\n  });\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "cf999433-15af-44cd-966b-07bc4664a1bb",
      "name": "Append New Deal to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2928,
        416
      ],
      "parameters": {
        "columns": {
          "value": {
            "Image": "={{ $json['Image'] }}",
            "Rating": "={{ $json['Rating'] }}",
            "Category": "={{ $json['Category'] }}",
            "Discount": "={{ $json['Discount'] }}",
            "Last Price": "={{ $json['Last Price'] }}",
            "AppSumo URL": "={{ $json['AppSumo URL'] }}",
            "Deal Status": "={{ $json['Deal Status'] }}",
            "Last Checked": "={{ $json['Last Checked'] }}",
            "Product Name": "={{ $json['Product Name'] }}",
            "Current Price": "={{ $json['Current Price'] }}",
            "Reviews Count": "={{ $json['Reviews Count'] }}"
          },
          "schema": [
            {
              "id": "Product Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Product Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "AppSumo URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "AppSumo URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Current Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Current Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Discount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Discount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Deal Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Deal Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rating",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reviews Count",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reviews Count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Checked",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Checked",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Change Detected",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Change Detected",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "New Deal",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "New Deal",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Image",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Image",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "74076d65-8611-4360-9591-65ef5c11bce1",
      "name": "Update Existing Deal Row in Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2928,
        624
      ],
      "parameters": {
        "columns": {
          "value": {
            "Image": "={{ $json['Image'] }}",
            "Rating": "={{ $json['Rating'] }}",
            "Category": "={{ $json['Category'] }}",
            "Discount": "={{ $json['Discount'] }}",
            "Last Price": "={{ $json['Last Price'] }}",
            "AppSumo URL": "={{ $json['AppSumo URL'] }}",
            "Deal Status": "={{ $json['Deal Status'] }}",
            "Last Checked": "={{ $json['Last Checked'] }}",
            "Product Name": "={{ $json['Product Name'] }}",
            "Current Price": "={{ $json['Current Price'] }}",
            "Reviews Count": "={{ $json['Reviews Count'] }}"
          },
          "schema": [
            {
              "id": "Product Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Product Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "AppSumo URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "AppSumo URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Current Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Current Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Discount",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Discount",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Deal Status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Deal Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rating",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Rating",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reviews Count",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reviews Count",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Checked",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Checked",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Change Detected",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Change Detected",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "New Deal",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "New Deal",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Image",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Image",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "AppSumo URL"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "fdefa234-338b-4616-8e86-542e8cf22371",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 688,
        "height": 784,
        "content": "# \ud83d\udecd\ufe0f AppSumo Lifetime Deal Monitor \u2192 Google Sheets\n\nThis workflow automatically tracks AppSumo lifetime deals daily. It fetches the AppSumo browse page using **ScrapeOps Proxy with JS rendering**, parses each deal into structured data, filters by your target categories, and saves new or updated deals to Google Sheets with full deduplication.\n\n### How it works\n1. \u23f0 **Daily Schedule Trigger** fires every day at 09:00 automatically.\n2. \ud83c\udf10 **Fetch AppSumo Deals via ScrapeOps** loads the browse page with JavaScript rendering enabled.\n3. \ud83d\udd0d **Parse AppSumo Deals HTML \u2192 Structured JSON** extracts name, URL, category, prices, discount, rating, reviews, image, and timestamps.\n4. \ud83d\udd0e **Filter Relevant Deal Categories** keeps only deals matching your target keywords.\n5. \ud83d\udccb **Lookup Deal in Google Sheet (by URL)** checks if the deal already exists in your sheet.\n6. \ud83d\udd00 **Merge Scrape + Existing Row** combines scraped data with any existing sheet row.\n7. \ud83d\udd00 **IF New Deal** routes new vs. existing deals to separate write paths.\n8. \ud83d\udcbe **Append New Deal to Sheet** adds a fresh row for new deals.\n9. \ud83d\udd04 **Update Existing Deal Row** refreshes pricing and status for known deals.\n\n### Setup steps\n- Register for a free ScrapeOps API key: https://scrapeops.io/app/register/n8n\n- Add ScrapeOps credentials in n8n. Docs: https://scrapeops.io/docs/n8n/overview/\n- [Duplicate the Google Sheet](https://docs.google.com/spreadsheets/d/1L_Fn_wm55wc5EXi_ZIVmmXyFrz9nJ-29Xd71Ndz_XIk/edit#gid=0\") and paste your Sheet URL into the Lookup, Append, and Update nodes.\n- Edit the category/keyword list in **Filter Relevant Deal Categories** to match your interests.\n- Run once manually to confirm, then activate.\n\n### Customization\n- Add Slack, Gmail, or Discord alerts on the **New Deal** path.\n- Change the schedule time in the trigger node to suit your timezone."
      },
      "typeVersion": 1
    },
    {
      "id": "d4da614f-e5ca-4b2e-8062-49e840ff0e75",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        400
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 288,
        "content": "## 1. Trigger & Fetch\nRuns daily at 09:00 and loads the AppSumo browse page via [ScrapeOps Proxy](https://scrapeops.io/docs/n8n/proxy-api/) with JavaScript rendering enabled."
      },
      "typeVersion": 1
    },
    {
      "id": "3c8d58b5-7f66-46c2-9f65-259aa428abde",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        400
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 288,
        "content": "## 2. Parse Deals\nExtracts title, URL, prices, discount, category, rating, reviews, image, status, and timestamps from the raw HTML into clean structured JSON."
      },
      "typeVersion": 1
    },
    {
      "id": "7b2a13d0-3b48-427a-8cb2-51663953981a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        400
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 288,
        "content": "## 3. Filter by Category\nKeeps only deals that match your target categories or keywords. Edit the list inside this node to customize what gets tracked."
      },
      "typeVersion": 1
    },
    {
      "id": "c65b592c-690c-4abe-be87-517ede20c410",
      "name": "Filter Relevant Deal Categories",
      "type": "n8n-nodes-base.code",
      "position": [
        1712,
        512
      ],
      "parameters": {
        "jsCode": "// Filter for specific categories\nconst targetCategories = ['No-Code', 'Automation', 'AI', 'Productivity', 'Marketing'];\n\nreturn items.filter(item => {\n  const category = item.json['Category'] || '';\n  const title = item.json['Product Name'] || '';\n  \n  // Check if any target category is mentioned in tags or title\n  const isRelevant = targetCategories.some(cat => \n    category.toLowerCase().includes(cat.toLowerCase()) || \n    title.toLowerCase().includes(cat.toLowerCase())\n  );\n  \n  return isRelevant;\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "af8f272e-50e8-4b9f-8bda-8b4c8195b63b",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1952,
        400
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 288,
        "content": "## 4. Deduplicate & Merge\nLooks up each deal by its AppSumo URL in Google Sheets, then merges scraped data with any existing row to enrich the record."
      },
      "typeVersion": 1
    },
    {
      "id": "db326fc5-7633-4926-9658-5139bb6efbbc",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 480,
        "content": "## 5. Route & Write to Google Sheets\nNew deals are appended as fresh rows. Existing deals have their price, status, and timestamps updated in place."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "47b7f578-3c75-4173-bad1-0b4b064b6bd9",
  "connections": {
    "Daily Schedule Trigger (09:00)": {
      "main": [
        [
          {
            "node": "Fetch AppSumo Deals via ScrapeOps (JS Render)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Relevant Deal Categories": {
      "main": [
        [
          {
            "node": "Lookup Deal in Google Sheet (by URL)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge Scrape + Existing Row (Enrich by URL)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Stamp Last Checked (New Deal Path)": {
      "main": [
        [
          {
            "node": "Append New Deal to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF New Deal (Last Checked is empty)": {
      "main": [
        [
          {
            "node": "Stamp Last Checked (New Deal Path)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stamp Last Checked (Existing Deal Path)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Lookup Deal in Google Sheet (by URL)": {
      "main": [
        [
          {
            "node": "Merge Scrape + Existing Row (Enrich by URL)",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Stamp Last Checked (Existing Deal Path)": {
      "main": [
        [
          {
            "node": "Update Existing Deal Row in Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Scrape + Existing Row (Enrich by URL)": {
      "main": [
        [
          {
            "node": "IF New Deal (Last Checked is empty)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AppSumo Deals HTML \u2192 Structured JSON": {
      "main": [
        [
          {
            "node": "Filter Relevant Deal Categories",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch AppSumo Deals via ScrapeOps (JS Render)": {
      "main": [
        [
          {
            "node": "Parse AppSumo Deals HTML \u2192 Structured JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}