← All Analytics Workflows

Detect content decay from Google Search Console and alert via Slack and email

Pull contacts, verify each address with BillionVerify, and continue to Google Search Console — 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 Google Search Console step removes that risk automatically — only deliverable addresses continue, the rest are flagged.

The workflow

BillionVerify — verification sits right before the send.

+10
n8n steps
+4
n8n steps

Node by node

  1. 1
    Weekly Monday 8AM TriggerTrigger· n8n

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

  2. 2
    Calculate Date RangesSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  3. 3
    Fetch GSC Recent 7 DaysSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  4. 4
    Fetch GSC Previous 28 DaysSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  5. 5
    Compare Periods and Detect DecaySource· n8n

    Provides or transforms the contact data flowing through the workflow.

  6. 6
    Filter Only Decaying PagesLogic· n8n

    Routes items based on the workflow logic.

  7. 7
    Log All Results to Decay SheetSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  8. 8
    Build Weekly Decay ReportSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  9. 9
    Check If Any Critical PagesLogic· n8n

    Routes items based on the workflow logic.

  10. 10
    Post Report to SlackSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  11. 11
    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.

  12. 12
    Generate Fix Tasks for Critical PagesSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  13. 13
    IF deliverableLogic· n8n

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

  14. 14
    Log Fix Tasks to SheetSource· n8n

    Provides or transforms the contact data flowing through the workflow.

  15. 15
    Email Weekly ReportSend· 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.

verify-emails-in-google-search-console.json
{
  "name": "Detect content decay from Google Search Console and alert via Slack and email + BillionVerify",
  "nodes": [
    {
      "id": "1fd97044-715a-4542-a99e-140e1d73ce73",
      "name": "Weekly Monday 8AM Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        320,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "34069535-dd52-4536-bbc8-f9abc5d7b9cc",
      "name": "Calculate Date Ranges",
      "type": "n8n-nodes-base.code",
      "position": [
        528,
        320
      ],
      "parameters": {
        "jsCode": "// Calculate date ranges\nconst now = new Date();\n\n// Recent period: last 7 days\nconst recentEnd = new Date(now);\nrecentEnd.setDate(recentEnd.getDate() - 3); // GSC data has 3-day delay\nconst recentStart = new Date(recentEnd);\nrecentStart.setDate(recentStart.getDate() - 7);\n\n// Previous period: 28 days before that\nconst prevEnd = new Date(recentStart);\nprevEnd.setDate(prevEnd.getDate() - 1);\nconst prevStart = new Date(prevEnd);\nprevStart.setDate(prevStart.getDate() - 28);\n\nconst fmt = (d) => d.toISOString().split('T')[0];\n\nreturn [{\n  json: {\n    recent_start: fmt(recentStart),\n    recent_end: fmt(recentEnd),\n    previous_start: fmt(prevStart),\n    previous_end: fmt(prevEnd)\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "fdecf37e-0b0a-4542-9b18-6af9c37e894b",
      "name": "Fetch GSC Recent 7 Days",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        768,
        224
      ],
      "parameters": {
        "url": "https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($env.GSC_SITE_URL) }}/searchAnalytics/query",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={\n  \"startDate\": \"{{ $json.recent_start }}\",\n  \"endDate\": \"{{ $json.recent_end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 500,\n  \"startRow\": 0\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "7a9e78b0-dd0c-4fec-b8f2-6fffd93e56de",
      "name": "Fetch GSC Previous 28 Days",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        768,
        416
      ],
      "parameters": {
        "url": "https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($env.GSC_SITE_URL) }}/searchAnalytics/query",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "jsonBody": "={\n  \"startDate\": \"{{ $json.previous_start }}\",\n  \"endDate\": \"{{ $json.previous_end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 500,\n  \"startRow\": 0\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleSearchConsoleOAuth2Api"
      },
      "typeVersion": 4.2
    },
    {
      "id": "81576c4a-ae86-4edf-b2d6-fe218c9023e8",
      "name": "Compare Periods and Detect Decay",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        320
      ],
      "parameters": {
        "jsCode": "// Compare recent vs previous performance per page\nconst recentData = $('Fetch GSC Recent 7 Days').first().json;\nconst prevData = $('Fetch GSC Previous 28 Days').first().json;\n\nconst recentRows = recentData.rows || [];\nconst prevRows = prevData.rows || [];\n\n// Build lookup from previous period (normalize to weekly average)\nconst prevMap = {};\nfor (const row of prevRows) {\n  const page = row.keys[0];\n  prevMap[page] = {\n    clicks: row.clicks / 4,       // 28 days → weekly avg\n    impressions: row.impressions / 4,\n    ctr: row.ctr,\n    position: row.position\n  };\n}\n\nconst results = [];\n\nfor (const row of recentRows) {\n  const page = row.keys[0];\n  const prev = prevMap[page];\n\n  if (!prev) continue; // New page, skip\n  if (prev.clicks < 2) continue; // Too little traffic to matter\n\n  const clickChange = row.clicks - prev.clicks;\n  const clickChangePct = prev.clicks > 0 ? parseFloat(((clickChange / prev.clicks) * 100).toFixed(1)) : 0;\n  const impressionChange = row.impressions - prev.impressions;\n  const impressionChangePct = prev.impressions > 0 ? parseFloat(((impressionChange / prev.impressions) * 100).toFixed(1)) : 0;\n  const positionChange = parseFloat((row.position - prev.position).toFixed(1)); // positive = worse\n\n  // Determine decay severity\n  let signal = 'STABLE';\n  let severity = 'ok';\n\n  if (clickChangePct <= -50 || (positionChange >= 5 && clickChangePct <= -30)) {\n    signal = 'CRITICAL_DECAY';\n    severity = 'critical';\n  } else if (clickChangePct <= -30 || positionChange >= 3) {\n    signal = 'DECAYING';\n    severity = 'warning';\n  } else if (clickChangePct <= -15 || positionChange >= 1.5) {\n    signal = 'EARLY_DECAY';\n    severity = 'watch';\n  } else if (clickChangePct >= 20) {\n    signal = 'GROWING';\n    severity = 'good';\n  }\n\n  // Clean page path\n  let pagePath = page;\n  try {\n    pagePath = new URL(page).pathname;\n  } catch(e) {}\n\n  results.push({\n    json: {\n      page_url: page,\n      page_path: pagePath,\n      \n      clicks_now: row.clicks,\n      clicks_before: parseFloat(prev.clicks.toFixed(1)),\n      click_change: clickChange,\n      click_change_pct: clickChangePct,\n      \n      impressions_now: row.impressions,\n      impressions_before: parseFloat(prev.impressions.toFixed(1)),\n      impression_change_pct: impressionChangePct,\n      \n      position_now: parseFloat(row.position.toFixed(1)),\n      position_before: parseFloat(prev.position.toFixed(1)),\n      position_change: positionChange,\n      \n      ctr_now: parseFloat((row.ctr * 100).toFixed(2)),\n      ctr_before: parseFloat((prev.ctr * 100).toFixed(2)),\n      \n      signal: signal,\n      severity: severity,\n      \n      date: new Date().toISOString().split('T')[0]\n    }\n  });\n}\n\n// Sort by worst decay first\nresults.sort((a, b) => a.json.click_change_pct - b.json.click_change_pct);\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "7a51c471-9f95-470c-b668-b5ea0aa46648",
      "name": "Log All Results to Decay Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1280,
        416
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{ $json.date }}",
            "signal": "={{ $json.signal }}",
            "ctr_now": "={{ $json.ctr_now }}",
            "page_path": "={{ $json.page_path }}",
            "clicks_now": "={{ $json.clicks_now }}",
            "position_now": "={{ $json.position_now }}",
            "clicks_before": "={{ $json.clicks_before }}",
            "impressions_now": "={{ $json.impressions_now }}",
            "position_before": "={{ $json.position_before }}",
            "position_change": "={{ $json.position_change }}",
            "click_change_pct": "={{ $json.click_change_pct }}",
            "impression_change_pct": "={{ $json.impression_change_pct }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Decay Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $env.DECAY_SHEET_URL }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "15898af0-2461-4f42-9804-6b02650ab69d",
      "name": "Filter Only Decaying Pages",
      "type": "n8n-nodes-base.filter",
      "position": [
        1280,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "is-critical",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.severity }}",
              "rightValue": "critical"
            },
            {
              "id": "is-warning",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.severity }}",
              "rightValue": "warning"
            },
            {
              "id": "is-watch",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.severity }}",
              "rightValue": "watch"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "51aa6fc9-3b2f-46d1-832a-aba1ef343635",
      "name": "Build Weekly Decay Report",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        224
      ],
      "parameters": {
        "jsCode": "// Build the weekly decay report\nconst allPages = $('Compare Periods and Detect Decay').all().map(i => i.json);\nconst decaying = $input.all().map(i => i.json);\n\nconst critical = decaying.filter(p => p.severity === 'critical');\nconst warning = decaying.filter(p => p.severity === 'warning');\nconst watch = decaying.filter(p => p.severity === 'watch');\nconst stable = allPages.filter(p => p.severity === 'ok');\nconst growing = allPages.filter(p => p.severity === 'good');\n\nconst today = new Date().toISOString().split('T')[0];\n\n// Total clicks lost\nconst totalClicksLost = decaying\n  .filter(p => p.click_change < 0)\n  .reduce((sum, p) => sum + Math.abs(p.click_change), 0);\n\nlet report = `*CONTENT DECAY REPORT — ${today}*\\n`;\nreport += `Pages analyzed: ${allPages.length}\\n\\n`;\n\nreport += `Critical decay: ${critical.length}\\n`;\nreport += `Decaying: ${warning.length}\\n`;\nreport += `Early signs: ${watch.length}\\n`;\nreport += `Stable: ${stable.length}\\n`;\nreport += `Growing: ${growing.length}\\n`;\nreport += `\\n*Est. weekly clicks lost: ${Math.round(totalClicksLost)}*\\n`;\n\nif (critical.length > 0) {\n  report += `\\n\\n*CRITICAL — Act now:*\\n`;\n  for (const p of critical.slice(0, 10)) {\n    report += `• \\`${p.page_path}\\`\\n`;\n    report += `  Clicks: ${p.clicks_before} → ${p.clicks_now} (*${p.click_change_pct}%*)\\n`;\n    report += `  Position: ${p.position_before} → ${p.position_now} (${p.position_change > 0 ? '+' : ''}${p.position_change})\\n`;\n  }\n}\n\nif (warning.length > 0) {\n  report += `\\n*DECAYING — Update soon:*\\n`;\n  for (const p of warning.slice(0, 10)) {\n    report += `• \\`${p.page_path}\\` — clicks ${p.click_change_pct}%, pos ${p.position_change > 0 ? '+' : ''}${p.position_change}\\n`;\n  }\n}\n\nif (watch.length > 0) {\n  report += `\\n*EARLY SIGNS — Keep watching:*\\n`;\n  for (const p of watch.slice(0, 5)) {\n    report += `• \\`${p.page_path}\\` — clicks ${p.click_change_pct}%\\n`;\n  }\n}\n\nif (growing.length > 0) {\n  report += `\\n*TOP GROWING:*\\n`;\n  const topGrow = growing.sort((a, b) => b.click_change_pct - a.click_change_pct).slice(0, 3);\n  for (const p of topGrow) {\n    report += `• \\`${p.page_path}\\` — clicks +${p.click_change_pct}%\\n`;\n  }\n}\n\nreturn [{\n  json: {\n    report: report,\n    total_pages: allPages.length,\n    critical_count: critical.length,\n    warning_count: warning.length,\n    watch_count: watch.length,\n    clicks_lost: Math.round(totalClicksLost),\n    has_critical: critical.length > 0,\n    critical_pages: critical.slice(0, 10),\n    warning_pages: warning.slice(0, 10)\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "48e028a0-fce2-4585-a9b4-ddef05f29639",
      "name": "Post Report to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1760,
        112
      ],
      "webhookId": "3d3e49f9-3bf4-463d-9e58-456befa19c00",
      "parameters": {
        "text": "={{ $json.report }}",
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "aff9be9e-2e4f-4df1-ad5f-e32c1669476f",
      "name": "Email Weekly Report",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        1760,
        320
      ],
      "webhookId": "8628387b-cbc1-4f0d-99e4-994e14ecfa8b",
      "parameters": {
        "options": {},
        "subject": "=Content Decay Report — {{ $json.critical_count }} critical, {{ $json.warning_count }} decaying | {{ $json.clicks_lost }} clicks lost"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4cf96f14-e1e9-4164-aee7-1002121f0d90",
      "name": "Check If Any Critical Pages",
      "type": "n8n-nodes-base.filter",
      "position": [
        1760,
        512
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-critical",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.has_critical }}",
              "rightValue": "={{true}}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "52ef07b8-caa3-43a0-9898-efc5ff320bc1",
      "name": "Generate Fix Tasks for Critical Pages",
      "type": "n8n-nodes-base.code",
      "position": [
        1968,
        512
      ],
      "parameters": {
        "jsCode": "// Create individual tasks for critical decaying pages\nconst data = $input.first().json;\nconst tasks = [];\n\nfor (const page of data.critical_pages) {\n  let action = '';\n  \n  if (page.position_change >= 5) {\n    action = 'Major ranking drop. Check: lost backlinks, technical errors (404/redirect), or competitor new content. Run a backlink audit.';\n  } else if (page.impression_change_pct <= -40) {\n    action = 'Impressions tanking. Possible: Google algorithm change, keyword cannibalization, or index issues. Check Coverage report in GSC.';\n  } else if (page.ctr_now < page.ctr_before * 0.7) {\n    action = 'CTR dropped significantly. Update title tag and meta description. Check if SERP features (featured snippets, ads) pushed you down.';\n  } else {\n    action = 'Content likely outdated. Refresh with updated stats, new sections, better internal links. Check if search intent has shifted.';\n  }\n\n  tasks.push({\n    json: {\n      page_path: page.page_path,\n      page_url: page.page_url,\n      signal: page.signal,\n      click_change_pct: page.click_change_pct,\n      position_change: page.position_change,\n      recommended_action: action,\n      priority: 'HIGH',\n      created: new Date().toISOString().split('T')[0]\n    }\n  });\n}\n\nreturn tasks;"
      },
      "typeVersion": 2
    },
    {
      "id": "76f2b5b6-32c3-4261-8f6b-9183c61e38e1",
      "name": "Log Fix Tasks to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2192,
        512
      ],
      "parameters": {
        "columns": {
          "value": {
            "signal": "={{ $json.signal }}",
            "created": "={{ $json.created }}",
            "page_url": "={{ $json.page_url }}",
            "priority": "={{ $json.priority }}",
            "page_path": "={{ $json.page_path }}",
            "position_change": "={{ $json.position_change }}",
            "click_change_pct": "={{ $json.click_change_pct }}",
            "recommended_action": "={{ $json.recommended_action }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Fix Tasks"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $env.DECAY_SHEET_URL }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "dd580d9b-91f2-4e75-b5d3-5467860eb2e7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -224
      ],
      "parameters": {
        "color": "#8D9C44",
        "width": 660,
        "height": 976,
        "content": "## Detect content decay and alert via Slack and email\n\nThis workflow automatically detects declining content performance by comparing your recent Google Search Console data against a historical baseline, then alerts you with actionable fix recommendations.\n\n## How it works\n1. **Runs every Monday at 8 AM** on a weekly schedule\n2. **Pulls last 7 days** of page-level data from Google Search Console (recent performance)\n3. **Pulls previous 28 days** as a baseline (normalized to weekly averages)\n4. **Compares per-page metrics** — clicks, impressions, position, and CTR\n5. **Classifies each page** into decay severity levels:\n   - **CRITICAL_DECAY** — clicks dropped 50%+ or position fell 5+ spots\n   - **DECAYING** — clicks dropped 30%+ or position fell 3+ spots\n   - **EARLY_DECAY** — clicks dropped 15%+ or position fell 1.5+ spots\n   - **STABLE** — no significant change\n   - **GROWING** — clicks up 20%+\n6. **Logs all results** to a Google Sheet for historical trend tracking\n7. **Sends a weekly report** via Slack and email with full breakdown\n8. **Auto-generates fix tasks** for critical pages with specific recommended actions\n\n## Setup steps\n1. Set **n8n environment variable** `GSC_SITE_URL` to your site (e.g., `https://yoursite.com`)\n2. Set **n8n environment variable** `DECAY_SHEET_URL` to your Google Sheet URL\n3. Create a Google Sheet with two tabs:\n   - `Decay Log` — headers: `date`, `page_path`, `signal`, `clicks_now`, `clicks_before`, `click_change_pct`, `position_now`, `position_before`, `position_change`, `impressions_now`, `impression_change_pct`, `ctr_now`\n   - `Fix Tasks` — headers: `created`, `priority`, `page_path`, `page_url`, `signal`, `click_change_pct`, `position_change`, `recommended_action`\n4. Connect **Google Search Console OAuth2** credentials\n5. Connect **Google Sheets OAuth2** credentials\n6. Connect **Slack OAuth2** credentials and set target channel\n7. Configure **SMTP / email** credentials and update recipient address\n\n## Customization\n- Adjust decay thresholds in the \"Compare Periods and Detect Decay\" node\n- Change schedule frequency (daily, bi-weekly, monthly)\n- Add more notification channels (Telegram, Discord, etc.)\n- Extend fix task logic with AI-powered content refresh suggestions"
      },
      "typeVersion": 1
    },
    {
      "id": "4ef309df-5f0e-44da-9136-6275543ae50e",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        432,
        48
      ],
      "parameters": {
        "color": "#413A3A",
        "width": 328,
        "height": 164,
        "content": "## 1. Fetch GSC Data\nCalculates date ranges (recent 7 days vs. previous 28-day baseline) and fetches page-level performance from Google Search Console in parallel."
      },
      "typeVersion": 1
    },
    {
      "id": "6881902b-441f-45b6-84d2-030540de839f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        0
      ],
      "parameters": {
        "color": "#413A3A",
        "width": 332,
        "height": 164,
        "content": "## 2. Detect Decay\nCompares recent vs. baseline metrics per page. Classifies each page as CRITICAL_DECAY, DECAYING, EARLY_DECAY, STABLE, or GROWING based on click and position changes."
      },
      "typeVersion": 1
    },
    {
      "id": "89fa3787-df97-47dd-8dda-505d1c1d1416",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        -96
      ],
      "parameters": {
        "color": "#413A3A",
        "width": 276,
        "height": 180,
        "content": "## 3. Report and Alert\nBuilds a comprehensive weekly report with decay breakdown. Sends to Slack and email simultaneously. Pages with no decay are logged but not alerted."
      },
      "typeVersion": 1
    },
    {
      "id": "02088b0f-83f2-4b51-afa7-ac7345e04529",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        752
      ],
      "parameters": {
        "color": "#413A3A",
        "width": 332,
        "height": 180,
        "content": "## 4. Auto-Generate Fix Tasks\nFor critical pages, generates specific remediation tasks (backlink audit, content refresh, CTR optimization) and logs them to a separate Google Sheet tab."
      },
      "typeVersion": 1
    },
    {
      "id": "d1e6cb92-3abe-4f97-a433-4c80070e718b",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        512
      ],
      "parameters": {
        "color": 3,
        "width": 480,
        "height": 140,
        "content": "⚠️ **Set environment variables** before activating:\n- `GSC_SITE_URL` → your verified site URL (e.g., `https://yoursite.com`)\n- `DECAY_SHEET_URL` → your Google Sheet URL for logging\n\nSee [n8n docs on environment variables](https://docs.n8n.io/hosting/configuration/environment-variables/)."
      },
      "typeVersion": 1
    },
    {
      "id": "8ab794a9-6860-45d1-80ff-bf6fba6ee2d0",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1984,
        304
      ],
      "parameters": {
        "color": 3,
        "width": 360,
        "height": 80,
        "content": "⚠️ **Update the email address** in this node to your own recipient address before activating."
      },
      "typeVersion": 1
    },
    {
      "parameters": {
        "operation": "verify",
        "email": "={{ $json.email || $json.Email }}",
        "additionalOptions": {}
      },
      "type": "n8n-nodes-billionverify.billionVerify",
      "typeVersion": 1,
      "position": [
        1400,
        320
      ],
      "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": [
        1580,
        320
      ],
      "name": "IF deliverable"
    }
  ],
  "connections": {
    "Calculate Date Ranges": {
      "main": [
        [
          {
            "node": "Fetch GSC Recent 7 Days",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch GSC Previous 28 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch GSC Recent 7 Days": {
      "main": [
        [
          {
            "node": "Compare Periods and Detect Decay",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Weekly Decay Report": {
      "main": [
        [
          {
            "node": "Check If Any Critical Pages",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post Report to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Verify Email (BillionVerify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Monday 8AM Trigger": {
      "main": [
        [
          {
            "node": "Calculate Date Ranges",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch GSC Previous 28 Days": {
      "main": [
        [
          {
            "node": "Compare Periods and Detect Decay",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Only Decaying Pages": {
      "main": [
        [
          {
            "node": "Build Weekly Decay Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Any Critical Pages": {
      "main": [
        [
          {
            "node": "Generate Fix Tasks for Critical Pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Periods and Detect Decay": {
      "main": [
        [
          {
            "node": "Filter Only Decaying Pages",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log All Results to Decay Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Fix Tasks for Critical Pages": {
      "main": [
        [
          {
            "node": "Log Fix Tasks to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify Email (BillionVerify)": {
      "main": [
        [
          {
            "node": "IF deliverable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF deliverable": {
      "main": [
        [
          {
            "node": "Email Weekly Report",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  }
}

When to use this

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

FAQ

Why verify before sending in Google Search Console?

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