← All Developer Tools Workflows
BillionVerifyScraperapi

Send daily price-drop digest emails for Amazon, Walmart and Google via ScraperAPI

Pull contacts, verify each address with BillionVerify, and continue to ScraperAPI — only deliverable addresses get through.

Why verify before the send

Sending to invalid, risky, catch-all, or disposable addresses spikes your bounce rate and erodes sender reputation. A verification gate before the ScraperAPI step removes that risk automatically — only deliverable addresses continue, the rest are flagged.

The workflow

BillionVerify — verification sits right before the send.

+13
n8n steps
+9
n8n steps

Node by node

  1. 1
    Morning Schedule TriggerTrigger· n8n

    Starts the workflow — on a schedule, a webhook, or manually while you test.

  2. 2
    Manual Trigger SetupTrigger· n8n

    Starts the workflow — on a schedule, a webhook, or manually while you test.

  3. 3
    Send Digest via GmailSend· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

  4. 4
    Fetch Products DataSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  5. 5
    Initialize Products TableSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  6. 6
    Iterate Products BatchSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  7. 7
    Initialize History TableSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  8. 8
    Retrieve Today's Price DropsSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  9. 9
    Extract Product URLsSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  10. 10
    Read Existing ProductsSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  11. 11
    Compile Price Drop DigestSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  12. 12
    Scrape Amazon DataSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  13. 13
    Generate Sample ProductsSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  14. 14
    Verify Email (BillionVerify)Verify· billionverify

    The BillionVerify node verifies the address — status (valid / invalid / risky / catch-all / role / disposable), is_deliverable, and a confidence score — before anything is sent.

  15. 15
    Scrape Walmart DataSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  16. 16
    Add Samples to Products TableSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  17. 17
    IF deliverableLogic· n8n

    Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.

  18. 18
    Scrape Google Shopping DataSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  19. 19
    Email Price Drop NoticeSend· n8n

    Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.

  20. 20
    Analyze Product DataSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  21. 21
    Divide History ResultsSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  22. 22
    Refresh Product PricesSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  23. 23
    Add to Price HistorySource· n8n

    Provides or transforms the contact data flowing through the workflow.

Workflow JSON

Copy or download this workflow, then import it in n8n (Workflows → Import from File / Paste). Install the BillionVerify community node first, then add your API key credential.

verify-emails-in-scraperapi.json
{
  "name": "Send daily price-drop digest emails for Amazon, Walmart and Google via ScraperAPI + BillionVerify",
  "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 === '' ? '—' : '$' + 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 + '%' : '—'}</td>\n</tr>`).join('');\n\nconst subject = `Price drop alert — ${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 — 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
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $json.email || $json.Email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        584,
        1184
      ],
      "name": "Verify Email (BillionVerify)",
      "credentials": {
        "billionVerifyApi": {
          "id": "",
          "name": "BillionVerify account"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-deliverable",
              "leftValue": "={{ $json.is_deliverable }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        764,
        1184
      ],
      "name": "IF deliverable"
    }
  ],
  "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": "Verify Email (BillionVerify)",
            "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
          }
        ]
      ]
    },
    "Verify Email (BillionVerify)": {
      "main": [
        [
          {
            "node": "IF deliverable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable": {
      "main": [
        [
          {
            "node": "Email Price Drop Notice",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {}
}

When to use this

  • Cleaning a list before a ScraperAPI send or sync.
  • Protecting ScraperAPI deliverability and sender reputation.
  • Keeping bounce rates low so your sending is never throttled.

FAQ

Why verify before sending in ScraperAPI?

Verifying first keeps your bounce rate low, which protects your sender reputation and your results.

How do I import this workflow?

Download the JSON, then in n8n go to Workflows → Import from File (or paste it). Install the BillionVerify community node and add your API key credential.

What happens to risky or catch-all addresses?

They are routed to the false branch and excluded from the send. You decide whether to retry, review, or drop them.

Add verification to your workflow

Create a free account, grab your API key, and stop bounces before they happen.

Get started free