Monitor competitor prices with Firecrawl, GPT-4.1, Sheets & Gmail alerts
Pull contacts, verify each address with BillionVerify, and continue to Firecrawl β 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 Firecrawl step removes that risk automatically β only deliverable addresses continue, the rest are flagged.
The workflow
BillionVerify β verification sits right before the send.
Node by node
- 1π Read Historical DataSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 2When clicking βExecute workflowβTriggerΒ· n8n
Starts the workflow β on a schedule, a webhook, or manually while you test.
- 3π Merge Current with HistoricalSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 4Scrape URL: nike.comSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 5Scrape URL: adidas.comSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 6Scrape URL: sneakerpricer.comSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 7π Detect Price & Stock ChangesSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 8π€ AI Extract Product Data using GPT-4.1-miniSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 9π Aggregate Daily DigestSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 10πΎ Update Historical DataSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 11π Log Alert DetailsSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 12Converts unstructured AI text into organized, usable data fieldsSourceΒ· n8n
Provides or transforms the contact data flowing through the workflow.
- 13Verify 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.
- 14IF deliverableLogicΒ· n8n
Branches on the verification result: only deliverable addresses continue to the send; the rest are skipped and flagged.
- 15Send a messageSendΒ· n8n
Sends only to verified, deliverable addresses. Swap in your own provider node if you send elsewhere.
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.
{
"name": "Monitor competitor prices with Firecrawl, GPT-4.1, Sheets & Gmail alerts + BillionVerify",
"nodes": [
{
"id": "1a7684b3-eb26-414f-ad30-e613306b50b1",
"name": "π Read Historical Data",
"type": "n8n-nodes-base.googleSheets",
"notes": "Loads previous scan data for comparison",
"position": [
-1472,
176
],
"parameters": {
"options": {},
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "credential-id",
"name": "googleSheetsOAuth2Api Credential"
}
},
"typeVersion": 4.7
},
{
"id": "9239f63d-2717-4248-918a-b886016c9a98",
"name": "π Merge Current with Historical",
"type": "n8n-nodes-base.merge",
"notes": "Combines current scrape with historical data for comparison",
"position": [
-1296,
0
],
"parameters": {
"mode": "combine",
"options": {},
"fieldsToMatchString": "rawResponse.message.content"
},
"typeVersion": 3
},
{
"id": "3b453791-3aca-4563-97d7-34d1bc751824",
"name": "π Detect Price & Stock Changes",
"type": "n8n-nodes-base.code",
"notes": "Intelligent change detection with alert level classification",
"position": [
-1120,
0
],
"parameters": {
"jsCode": "// Compare current prices with historical and detect changes\nconst results = [];\n\nfor (const item of $input.all()) {\n const current = item.json;\n \n // Skip error items\n if (current.error) {\n results.push({ json: current });\n continue;\n }\n \n // Find historical data for this competitor\n const historical = $('π Read Historical Data').all()\n .find(h => h.json.competitorName === current.competitorName);\n \n let alertLevel = 'none';\n let changes = [];\n \n if (historical && historical.json.currentPrice) {\n const oldPrice = parseFloat(historical.json.currentPrice);\n const newPrice = parseFloat(current.currentPrice);\n const priceChange = newPrice - oldPrice;\n const priceChangePercent = ((priceChange / oldPrice) * 100).toFixed(2);\n \n current.priceChange = priceChange;\n current.priceChangePercent = parseFloat(priceChangePercent);\n current.previousPrice = oldPrice;\n \n // Determine alert level based on price changes\n if (Math.abs(priceChangePercent) >= 20) {\n alertLevel = 'critical';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 10) {\n alertLevel = 'warning';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 5) {\n alertLevel = 'info';\n changes.push(`Minor price change: ${priceChangePercent}%`);\n }\n \n // Check if it's a new low price\n const historicalLow = parseFloat(historical.json.lowestPrice || oldPrice);\n if (newPrice < historicalLow) {\n current.isNewLow = true;\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('π― NEW LOWEST PRICE!');\n }\n current.lowestPrice = Math.min(newPrice, historicalLow);\n \n // Stock level changes\n if (historical.json.stockLevel !== current.stockLevel) {\n if (current.stockLevel === 'Out of Stock') {\n alertLevel = 'critical';\n changes.push('π¦ Product went OUT OF STOCK');\n } else if (current.stockLevel === 'Low Stock') {\n alertLevel = alertLevel === 'none' ? 'warning' : alertLevel;\n changes.push('β οΈ Stock level is LOW');\n } else if (historical.json.stockLevel === 'Out of Stock' && current.inStock) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('β
Back in stock!');\n }\n }\n \n // Rating changes\n const oldRating = parseFloat(historical.json.rating || 0);\n const newRating = parseFloat(current.rating || 0);\n const ratingChange = newRating - oldRating;\n \n if (Math.abs(ratingChange) >= 0.5) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push(`β Rating ${ratingChange > 0 ? 'improved' : 'dropped'} by ${Math.abs(ratingChange).toFixed(1)} stars`);\n }\n \n // Review count changes\n const oldReviews = parseInt(historical.json.reviewCount || 0);\n const newReviews = parseInt(current.reviewCount || 0);\n const reviewDiff = newReviews - oldReviews;\n \n if (reviewDiff > 0) {\n changes.push(`π¬ ${reviewDiff} new review${reviewDiff > 1 ? 's' : ''}`);\n }\n } else {\n // First time seeing this competitor\n alertLevel = 'info';\n changes.push('π First time tracking this competitor');\n current.lowestPrice = current.currentPrice;\n }\n \n current.alertLevel = alertLevel;\n current.changesSummary = changes.join(' | ');\n current.hasChanges = alertLevel !== 'none';\n \n results.push({ json: current });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "c83f80f9-8954-406e-a43a-20299f94d4ef",
"name": "πΎ Update Historical Data",
"type": "n8n-nodes-base.googleSheets",
"notes": "Saves current data to historical tracking sheet",
"position": [
-944,
80
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"inStock": "={{ $json.inStock }}",
"currency": "={{ $json.currency }}",
"scrapedAt": "={{ $json.scrapedAt }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"lowestPrice": "={{ $json.lowestPrice }}",
"productName": "={{ $json.productName }}",
"reviewCount": "={{ $json.reviewCount }}",
"currentPrice": "={{ $json.currentPrice }}",
"originalPrice": "={{ $json.originalPrice }}",
"competitorName": "={{ $json.competitorName }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "= {{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "credential-id",
"name": "googleSheetsOAuth2Api Credential"
}
},
"typeVersion": 4.7
},
{
"id": "c1ba6419-781d-4c2d-bfa6-dfe202751e19",
"name": "π Log Alert Details",
"type": "n8n-nodes-base.googleSheets",
"notes": "Logs all alerts to separate tracking sheet",
"position": [
-944,
-80
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"timestamp": "={{ $json.scrapedAt }}",
"alertLevel": "={{ $json.alertLevel }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"priceChange": "={{ $json.priceChange || 0 }}",
"productName": "={{ $json.productName }}",
"currentPrice": "={{ $json.currentPrice }}",
"previousPrice": "={{ $json.previousPrice || 'N/A' }}",
"changesSummary": "={{ $json.changesSummary }}",
"competitorName": "={{ $json.competitorName }}",
"priceChangePercent": "={{ $json.priceChangePercent || 0 }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Alert Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "credential-id",
"name": "googleSheetsOAuth2Api Credential"
}
},
"typeVersion": 4.7
},
{
"id": "233f07c7-c121-4e14-abf1-0a917b023a11",
"name": "π Aggregate Daily Digest",
"type": "n8n-nodes-base.code",
"notes": "Combines all alerts into a comprehensive summary",
"position": [
-944,
-240
],
"parameters": {
"jsCode": "// Aggregate all items for daily digest email\nconst allItems = $input.all();\n\nconst criticalAlerts = allItems.filter(item => item.json.alertLevel === 'critical');\nconst warningAlerts = allItems.filter(item => item.json.alertLevel === 'warning');\nconst infoAlerts = allItems.filter(item => item.json.alertLevel === 'info');\nconst noChanges = allItems.filter(item => item.json.alertLevel === 'none');\n\nconst summary = {\n totalCompetitors: allItems.length,\n criticalCount: criticalAlerts.length,\n warningCount: warningAlerts.length,\n infoCount: infoAlerts.length,\n noChangeCount: noChanges.length,\n timestamp: new Date().toISOString(),\n criticalAlerts: criticalAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary,\n price: `${i.json.currency} ${i.json.currentPrice}`,\n priceChange: i.json.priceChangePercent ? `${i.json.priceChangePercent}%` : 'N/A',\n stock: i.json.stockLevel,\n url: i.json.productUrl\n })),\n warningAlerts: warningAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary,\n price: `${i.json.currency} ${i.json.currentPrice}`,\n priceChange: i.json.priceChangePercent ? `${i.json.priceChangePercent}%` : 'N/A',\n stock: i.json.stockLevel\n })),\n infoAlerts: infoAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary\n }))\n};\n\nreturn [{ json: summary }];"
},
"typeVersion": 2
},
{
"id": "bb79bf76-29bd-44d9-8064-48c14622e1f5",
"name": "Scrape URL: nike.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
-176
],
"parameters": {
"url": "https://www.nike.com/sg/w/mens-shoes-nik1zy7ok",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "credential-id",
"name": "firecrawlApi Credential"
}
},
"typeVersion": 1
},
{
"id": "905a1a25-92aa-4459-8d4e-8392d6ca4e61",
"name": "When clicking βExecute workflowβ",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-2096,
32
],
"parameters": {},
"typeVersion": 1
},
{
"id": "fb7b71da-f70d-48d8-afef-ee42bdb48567",
"name": "Send a message",
"type": "n8n-nodes-base.gmail",
"position": [
-800,
-240
],
"webhookId": "db581b6f-5b87-4fcb-be62-37ad7ab7a26f",
"parameters": {
"sendTo": " info@example.com",
"message": "The pricing of the competitors is attached",
"options": {},
"subject": "Shoes pricing"
},
"credentials": {
"gmailOAuth2": {
"id": "credential-id",
"name": "gmailOAuth2 Credential"
}
},
"typeVersion": 2.1
},
{
"id": "5c08b0c2-5bc1-4594-be36-938e67308a1f",
"name": "Scrape URL: adidas.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
-16
],
"parameters": {
"url": "=https://www.adidas.com/us/men-shoes",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "credential-id",
"name": "firecrawlApi Credential"
}
},
"typeVersion": 1
},
{
"id": "57ac5f72-cc18-4919-bdea-4e16939b8080",
"name": "Scrape URL: sneakerpricer.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
144
],
"parameters": {
"url": "=https://www.sneakerpricer.com/us-EN",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "credential-id",
"name": "firecrawlApi Credential"
}
},
"typeVersion": 1
},
{
"id": "5eb1ffd8-d98d-4682-b200-92ab2432fd81",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2656,
-400
],
"parameters": {
"width": 2032,
"height": 880,
"content": "## Introduction\nAutomate price monitoring for e-commerce competitorsβideal for retailers, analysts, and pricing teams.\n\n**β οΈ Self-Hosted Only:** Requires self-hosted n8n instance.\n## How It Works\nScrapes competitor URLs, extracts data via AI, detects price/stock changes, logs to Google Sheets with email alerts.\n## Workflow Template\nTrigger β Scrape β AI Extract β Parse β Compare β Detect Changes β Update Sheets + Alert\n## Workflow Steps\n1. **Scraping:** Firecrawl fetches Nike, Adidas, Sneaker data\n2. **AI Extraction:** Processes product details\n3. **Parsing:** Structures response\n4. **Historical Check:** Reads Sheets data\n5. **Change Detection:** Identifies price/stock updates\n6. **Dual Output:** Updates Sheets + sends alerts\n## Setup Instructions\n1. **Firecrawl API**\nGet key from dashboard β Add to n8n\n2. **OpenAI API**\nGet key from platform β Add to n8n\n3. **Google Sheets OAuth2**\nCreate OAuth2 in Google Cloud Console β Authorize in n8n β Enable API\n4. **Gmail OAuth2**\nUse same project β Authorize in n8n β Enable API\n5. **Spreadsheet Setup**\nCreate Sheet with required columns β Copy ID from URL β Paste in workflow\n## Prerequisites\nSelf-hosted n8n, Firecrawl account, OpenAI key, Google account (Sheets + Gmail OAuth2)\n## Customization\nAdd URLs, adjust thresholds, integrate Slack\n## Benefits\nSaves 2+ hours daily, real-time tracking, automated alerts-time competitor tracking, automated alerts, historical data analysis."
},
"typeVersion": 1
},
{
"id": "738fd9c2-91ff-4f15-b7a3-1aa0e4c1f8a1",
"name": "Converts unstructured AI text into organized, usable data fields",
"type": "n8n-nodes-base.code",
"notes": "Parses and validates the AI extracted data",
"position": [
-1504,
-16
],
"parameters": {
"jsCode": "// Parse AI response and clean data\nconst items = [];\n\nfor (const item of $input.all()) {\n try {\n // Parse the AI response\n let parsed;\n const response = item.json.choices?.[0]?.message?.content || item.json.message || '';\n \n // Remove markdown code blocks if present\n const cleaned = response.replace(/```json\\n?|```\\n?/g, '').trim();\n \n try {\n parsed = JSON.parse(cleaned);\n } catch (e) {\n // Try to extract JSON from the response\n const jsonMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n parsed = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error('Could not parse JSON from AI response');\n }\n }\n \n // Enrich with metadata\n items.push({\n json: {\n ...parsed,\n scrapedAt: new Date().toISOString(),\n priceChange: 0, // Will be calculated in comparison\n priceChangePercent: 0,\n isNewLow: false,\n alertLevel: 'none'\n }\n });\n } catch (error) {\n console.error('Failed to parse item:', error.message);\n // Add error item for debugging\n items.push({\n json: {\n error: error.message,\n rawResponse: item.json,\n competitorName: 'Parse Error',\n scrapedAt: new Date().toISOString()\n }\n });\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "2a1d727b-8147-42e7-bfcd-c3ed37af7bc7",
"name": "π€ AI Extract Product Data using GPT-4.1-mini",
"type": "n8n-nodes-base.openAi",
"notes": "Uses OpenAI to intelligently extract structured data from HTML",
"position": [
-1696,
-16
],
"parameters": {
"prompt": {
"messages": [
{
"role": "system",
"content": "You are a precise e-commerce data extraction expert. Extract shoes information from HTML and return ONLY valid JSON with no markdown formatting.\n\nExtract these fields:\n- productName: string\n- currentPrice: number (numeric value only, no currency symbols)\n- originalPrice: number (if discounted, otherwise same as currentPrice)\n- currency: string (USD, EUR, etc.)\n- inStock: boolean\n- stockLevel: string (\"In Stock\", \"Low Stock\", \"Out of Stock\", \"Limited\", etc.)\n- rating: number (0-5 scale)\n- reviewCount: number\n- lastUpdated: string (current ISO timestamp)\n- productUrl: string (from context)\n- competitorName: string (from context)\n\nReturn ONLY the JSON object, no explanations."
},
{
"content": "HTML Content:\n{{ $json.body }}\n\nProduct URL: {{ $json.url || 'unknown' }}\nCompetitor: {{ $json.competitor || 'unknown' }}\n\nExtract the product data as JSON:"
}
]
},
"options": {
"temperature": 0.1
},
"resource": "chat",
"chatModel": "gpt-4.1-mini",
"requestOptions": {}
},
"credentials": {
"openAiApi": {
"id": "credential-id",
"name": "openAiApi Credential"
}
},
"typeVersion": 1.1
},
{
"id": "652a2128-6e01-4d6e-8bcb-b3f844cec2a8",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1584,
-352
],
"parameters": {
"color": 6,
"width": 352,
"height": 240,
"content": "## Google Sheets Structure\n**Required Columns:**\n- **Product Name** (Column A)\n- **Current Price** (Column B)\n- **Previous Price** (Column C)\n- **Stock Status** (Column D)\n- **Last Updated** (Column E)\n- **URL** (Column F)\n- **Change Detected** (Column G)"
},
"typeVersion": 1
},
{
"parameters": {
"operation": "verify",
"email": "={{ $json.email || $json.Email }}",
"additionalOptions": {}
},
"type": "n8n-nodes-billionverify.billionVerify",
"typeVersion": 1,
"position": [
-1160,
-240
],
"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": [
-980,
-240
],
"name": "IF deliverable"
}
],
"connections": {
"Send a message": {
"main": [
[]
]
},
"Scrape URL: nike.com": {
"main": [
[
{
"node": "π€ AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"Scrape URL: adidas.com": {
"main": [
[
{
"node": "π€ AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"π Read Historical Data": {
"main": [
[
{
"node": "π Merge Current with Historical",
"type": "main",
"index": 1
}
]
]
},
"π Aggregate Daily Digest": {
"main": [
[
{
"node": "Verify Email (BillionVerify)",
"type": "main",
"index": 0
}
]
]
},
"Scrape URL: sneakerpricer.com": {
"main": [
[
{
"node": "π€ AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"π Detect Price & Stock Changes": {
"main": [
[
{
"node": "π Aggregate Daily Digest",
"type": "main",
"index": 0
},
{
"node": "πΎ Update Historical Data",
"type": "main",
"index": 0
},
{
"node": "π Log Alert Details",
"type": "main",
"index": 0
}
]
]
},
"π Merge Current with Historical": {
"main": [
[
{
"node": "π Detect Price & Stock Changes",
"type": "main",
"index": 0
}
]
]
},
"When clicking βExecute workflowβ": {
"main": [
[
{
"node": "Scrape URL: nike.com",
"type": "main",
"index": 0
},
{
"node": "Scrape URL: adidas.com",
"type": "main",
"index": 0
},
{
"node": "Scrape URL: sneakerpricer.com",
"type": "main",
"index": 0
}
]
]
},
"π€ AI Extract Product Data using GPT-4.1-mini": {
"main": [
[
{
"node": "Converts unstructured AI text into organized, usable data fields",
"type": "main",
"index": 0
}
]
]
},
"Converts unstructured AI text into organized, usable data fields": {
"main": [
[
{
"node": "π Merge Current with Historical",
"type": "main",
"index": 0
}
]
]
},
"Verify Email (BillionVerify)": {
"main": [
[
{
"node": "IF deliverable",
"type": "main",
"index": 0
}
]
]
},
"IF deliverable": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
],
[]
]
}
},
"settings": {
"executionOrder": "v1"
}
}When to use this
- Cleaning a list before a Firecrawl send or sync.
- Protecting Firecrawl deliverability and sender reputation.
- Keeping bounce rates low so your sending is never throttled.
FAQ
Why verify before sending in Firecrawl?
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