This workflow corresponds to n8n.io template #15700 — we link there as the canonical source.
This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Daily CVE Intelligence & Prioritization Notifier",
"tags": [],
"nodes": [
{
"id": "7dd8e05b-b223-4ff9-b8ce-5368bb4e866e",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
256
],
"parameters": {
"width": 720,
"height": 320,
"content": "## Daily CVE risk intelligence\n\nMonitors a technology watchlist, finds newly published CVEs, enriches them with EPSS and CISA KEV, removes duplicate alerts, and sends a prioritized daily digest.\n\nUse this when you want a lightweight vulnerability intelligence feed for the technologies your team cares about."
},
"typeVersion": 1
},
{
"id": "55b2913b-4349-4055-83b7-d27f078972b2",
"name": "Setup checklist",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
256
],
"parameters": {
"width": 720,
"height": 320,
"content": "## Setup checklist\n\n- Replace the sample CSV URL in **Fetch Technology Watchlist CSV**.\n- Add your NVD API key in **Fetch Recent CVEs from NVD**.\n- Connect Gmail credentials in **Send Email Digest**.\n- Connect Slack credentials and choose a channel in **Send Slack Digest**.\n- Test the workflow once, then activate it."
},
"typeVersion": 1
},
{
"id": "5759e26e-92f7-4a4b-bffe-8de4dc180b05",
"name": "Sample CSV",
"type": "n8n-nodes-base.stickyNote",
"position": [
1184,
256
],
"parameters": {
"width": 720,
"height": 320,
"content": "## Watchlist CSV format\n\n```csv\nTechnology,Keywords,Severity,Notify,Enabled\nnginx,\"nginx,openresty\",\"HIGH,CRITICAL\",both,TRUE\nwordpress,\"wordpress,woocommerce\",\"MEDIUM,HIGH,CRITICAL\",both,TRUE\nkubernetes,\"kubernetes,k8s\",\"HIGH,CRITICAL\",slack,TRUE\napache,\"apache,httpd\",CRITICAL,email,TRUE\n```\n\n`Notify` supports `email`, `slack`, `both`, or `none`."
},
"typeVersion": 1
},
{
"id": "7d18d45c-9363-455e-ba3b-d9cba669e3e0",
"name": "Trigger annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
672
],
"parameters": {
"color": 7,
"width": 224,
"height": 256,
"content": "## Trigger\n\nRuns once daily and starts the CVE monitoring workflow."
},
"typeVersion": 1
},
{
"id": "755033d6-362d-4b7c-83d8-02d93612c284",
"name": "Watchlist annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
-80,
672
],
"parameters": {
"color": 7,
"width": 464,
"height": 256,
"content": "## Load watchlist\n\nDownloads the public CSV or Google Sheet export and parses it into structured rows.\n\nEach row controls technology keywords, severity filters, notification route, and whether monitoring is enabled."
},
"typeVersion": 1
},
{
"id": "9f2861f9-f3be-4a43-95c2-f03449a00d65",
"name": "NVD annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
672
],
"parameters": {
"color": 7,
"width": 224,
"height": 256,
"content": "## Fetch recent CVEs\n\nRetrieves CVEs published during the last 24 hours from the NVD API.\n\nThis uses one request per run, instead of one request per technology."
},
"typeVersion": 1
},
{
"id": "5c688b20-8158-4763-b0a3-abd247e52ccd",
"name": "Match and dedup annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
672
],
"parameters": {
"color": 7,
"width": 464,
"height": 256,
"content": "## Match and deduplicate\n\nMatches CVEs against watchlist keywords, applies the configured severity filter, and skips CVEs that were already sent.\n\nDeduplication uses workflow static data."
},
"typeVersion": 1
},
{
"id": "8a4d3328-28c1-4c42-baea-2b41f33e4779",
"name": "EPSS annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
1120,
672
],
"parameters": {
"color": 7,
"width": 704,
"height": 256,
"content": "## EPSS enrichment\n\nLooks up EPSS scores from FIRST.org and attaches exploit probability data to each matched CVE.\n\nThis helps prioritize CVEs that are more likely to be exploited."
},
"typeVersion": 1
},
{
"id": "059ee736-a64f-4e9f-bc2e-a869eeec19c0",
"name": "KEV annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
1840,
672
],
"parameters": {
"color": 7,
"width": 464,
"height": 256,
"content": "## CISA KEV enrichment\n\nChecks matched CVEs against the CISA Known Exploited Vulnerabilities catalog.\n\nKEV matches are treated as higher priority in the final digest."
},
"typeVersion": 1
},
{
"id": "b2842178-9043-4762-991f-a80780034192",
"name": "Digest annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
2320,
672
],
"parameters": {
"color": 7,
"width": 224,
"height": 256,
"content": "## Prioritize and build digest\n\nSorts findings by KEV status, severity, and EPSS score.\n\nBuilds a concise HTML email digest and Slack-friendly summary."
},
"typeVersion": 1
},
{
"id": "64a00e46-eec6-452a-a690-ca4646b1fd03",
"name": "Notification annotation",
"type": "n8n-nodes-base.stickyNote",
"position": [
2560,
672
],
"parameters": {
"color": 7,
"width": 464,
"height": 256,
"content": "## Send notifications\n\nSends the daily digest to Gmail and Slack.\n\nBoth notification nodes continue on failure, so one channel can still work even if the other needs credential setup."
},
"typeVersion": 1
},
{
"id": "93441795-cb0c-4ab3-9b16-15fca384d3a4",
"name": "Feedback note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
1264
],
"parameters": {
"color": 4,
"width": 720,
"height": 112,
"content": "### How can this template be improved?\n\nCustomize the watchlist, severity filters, and notification channels based on your team\u2019s triage process."
},
"typeVersion": 1
},
{
"id": "af1791c0-f206-4e22-a6de-1b0f10750c49",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-320,
976
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 11
}
]
}
},
"typeVersion": 1.3
},
{
"id": "b150a202-1948-4e64-af0d-2682c1fb3679",
"name": "Read Watchlist CSV",
"type": "n8n-nodes-base.extractFromFile",
"position": [
160,
976
],
"parameters": {
"options": {}
},
"typeVersion": 1.1
},
{
"id": "fcee3438-4a6a-4c12-872c-63226b3c2f0a",
"name": "Match CVEs to Watchlist",
"type": "n8n-nodes-base.code",
"position": [
640,
976
],
"parameters": {
"jsCode": "// 1. Get technologies from sheet\nconst technologies = $('Read Watchlist CSV').all()\n .map(i => i.json)\n .filter(row => String(row.Enabled).toUpperCase() === 'TRUE');\n\n// 2. Get CVEs\nconst cves = $('Fetch Recent CVEs from NVD').first().json.vulnerabilities || [];\n\nconst results = [];\n\n// Helper to get CVSS\nfunction getCvss(cve) {\n return (\n cve.metrics?.cvssMetricV31?.[0]?.cvssData ||\n cve.metrics?.cvssMetricV30?.[0]?.cvssData ||\n cve.metrics?.cvssMetricV2?.[0]?.cvssData ||\n {}\n );\n}\n\nfor (const vuln of cves) {\n const cve = vuln.cve;\n const cvss = getCvss(cve);\n\n const severity = String(cvss.baseSeverity || 'N/A').toUpperCase();\n\n // Combine searchable text\n const text = [\n cve.id,\n ...(cve.descriptions || []).map(d => d.value),\n ...(cve.configurations || []).flatMap(cfg =>\n (cfg.nodes || []).flatMap(node =>\n (node.cpeMatch || []).map(cpe => cpe.criteria || '')\n )\n )\n ].join(' ').toLowerCase();\n\n for (const tech of technologies) {\n const keywords = String(tech.Keywords || tech.Technology || '')\n .split(',')\n .map(k => k.trim().toLowerCase())\n .filter(Boolean);\n\n const allowedSeverities = String(tech.Severity || 'HIGH,CRITICAL')\n .split(',')\n .map(s => s.trim().toUpperCase())\n .filter(Boolean);\n\n const matched = keywords.some(k => text.includes(k));\n const severityAllowed = allowedSeverities.includes(severity);\n\n if (matched && severityAllowed) {\n results.push({\n dedupKey: `${tech.Technology}-${cve.id}`,\n technology: tech.Technology,\n notify: String(tech.Notify || 'both').toLowerCase(),\n cveId: cve.id,\n severity,\n score: cvss.baseScore || 'N/A',\n published: cve.published,\n summary: cve.descriptions?.[0]?.value || '',\n url: `https://nvd.nist.gov/vuln/detail/${cve.id}`\n });\n }\n }\n}\n\nreturn results.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "67c9ee45-cdd8-419d-bdce-a09c7519f996",
"name": "Deduplicate Alerts",
"type": "n8n-nodes-base.code",
"position": [
880,
976
],
"parameters": {
"jsCode": "const staticData = $getWorkflowStaticData('global');\n\nif (!staticData.sentCves) {\n staticData.sentCves = {};\n}\n\nconst fresh = [];\n\nfor (const item of $input.all()) {\n const key = item.json.dedupKey;\n\n if (!staticData.sentCves[key]) {\n staticData.sentCves[key] = {\n sentAt: new Date().toISOString(),\n technology: item.json.technology,\n cveId: item.json.cveId,\n severity: item.json.severity,\n };\n\n fresh.push(item);\n }\n}\n\nreturn fresh;"
},
"typeVersion": 2
},
{
"id": "222ec401-80eb-4413-aea0-de60d51ca057",
"name": "Send Email Digest",
"type": "n8n-nodes-base.gmail",
"onError": "continueRegularOutput",
"position": [
2560,
880
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{$json.body}}",
"options": {},
"subject": "={{$json.subject}}"
},
"typeVersion": 2.2
},
{
"id": "7afcebd8-781b-4b5a-9f67-f4ed7ecb623a",
"name": "Prioritize & Build Digest",
"type": "n8n-nodes-base.code",
"position": [
2320,
976
],
"parameters": {
"jsCode": "const items = $input.all();\n\nif (items.length === 0) return [];\n\nconst severityRank = { CRITICAL: 1, HIGH: 2, MEDIUM: 3, LOW: 4, 'N/A': 5 };\n\nconst severityColor = {\n CRITICAL: '#b91c1c',\n HIGH: '#ea580c',\n MEDIUM: '#ca8a04',\n LOW: '#2563eb',\n 'N/A': '#6b7280',\n};\n\nconst sorted = items\n .map(i => i.json)\n .sort((a, b) => {\n // KEV first\n if (a.kev !== b.kev) return b.kev - a.kev;\n\n // Then severity\n const sevDiff = (severityRank[a.severity] || 9) - (severityRank[b.severity] || 9);\n if (sevDiff !== 0) return sevDiff;\n\n // Then EPSS\n return (b.epss || 0) - (a.epss || 0);\n});\n\nconst topFindings = sorted.slice(0, 5);\n\nlet rows = '';\n\nfor (const cve of sorted) {\n const color = severityColor[cve.severity] || '#6b7280';\n\n rows += `\n <tr>\n <td style=\"padding:10px;border-bottom:1px solid #eee;\">${cve.technology}</td>\n <td style=\"padding:10px;border-bottom:1px solid #eee;\">\n <a href=\"${cve.url}\" style=\"color:#2563eb;text-decoration:none;font-weight:600;\">${cve.cveId}</a>\n </td>\n <td style=\"padding:10px;border-bottom:1px solid #eee;\">\n <span style=\"background:${color};color:#fff;padding:4px 8px;border-radius:12px;font-size:12px;font-weight:600;\">\n ${cve.severity} ${cve.kev ? '\u26a1' : ''}\n </span>\n </td>\n <td style=\"padding:10px;border-bottom:1px solid #eee;\"> ${cve.score}<br/> <span style=\"font-size:11px;color:#6b7280;\">EPSS: ${cve.epssPercent}</span> </td>\n <td style=\"padding:10px;border-bottom:1px solid #eee;\">${(cve.summary || '').slice(0, 180)}...</td>\n </tr>\n `;\n}\n\nconst critical = sorted.filter(i => i.severity === 'CRITICAL').length;\nconst high = sorted.filter(i => i.severity === 'HIGH').length;\n\nconst techSummaryMap = {};\n\nfor (const item of sorted) {\n const tech = item.technology;\n const sev = item.severity;\n\n if (!techSummaryMap[tech] || severityRank[sev] < severityRank[techSummaryMap[tech]]) {\n techSummaryMap[tech] = sev;\n }\n}\n\nconst techSummary = Object.entries(techSummaryMap)\n .sort((a, b) => severityRank[a[1]] - severityRank[b[1]])\n .map(([tech, sev]) => ({ tech, severity: sev }));\n\nconst body = `\n<div style=\"font-family:Arial,sans-serif;color:#111827;max-width:900px;\">\n <h2 style=\"margin-bottom:4px;\">Daily CVE Digest</h2>\n <p style=\"color:#6b7280;margin-top:0;\">New matched CVEs from your technology watchlist.</p>\n\n <div style=\"margin:18px 0;\">\n <strong>Summary:</strong>\n ${critical} Critical, ${high} High, ${sorted.length} Total\n </div>\n\n <table style=\"width:100%;border-collapse:collapse;border:1px solid #eee;\">\n <thead>\n <tr style=\"background:#f9fafb;text-align:left;\">\n <th style=\"padding:10px;\">Tech</th>\n <th style=\"padding:10px;\">CVE</th>\n <th style=\"padding:10px;\">Severity</th>\n <th style=\"padding:10px;\">Score</th>\n <th style=\"padding:10px;\">Summary</th>\n </tr>\n </thead>\n <tbody>${rows}</tbody>\n </table>\n\n <p style=\"font-size:12px;color:#6b7280;margin-top:16px;\">\n Deduplicated alert. Only newly matched CVEs are included.\n </p>\n</div>\n`;\n\nreturn [\n {\n json: {\n subject: `Daily CVE Digest: ${critical} Critical, ${high} High`,\n body,\n critical,\n high,\n total: sorted.length,\n topFindings,\n techSummary\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "3265d671-a57e-4dfc-9dc0-11a99069eee8",
"name": "Send Slack Digest",
"type": "n8n-nodes-base.slack",
"onError": "continueRegularOutput",
"position": [
2560,
1072
],
"parameters": {
"text": "=\ud83d\udea8 *Daily CVE Digest*\n\n*Summary*\n\u2022 \ud83d\udd34 Critical: {{$json.critical}}\n\u2022 \ud83d\udfe0 High: {{$json.high}}\n\u2022 \ud83d\udcca Total: {{$json.total}}\n\n*Actively Exploited (KEV)*\n{{ $json.topFindings.filter(i => i.kev).map(i => \n`\u2022 \u26a1 *${i.technology}* | <${i.url}|${i.cveId}>`\n).join('\\n') || 'None' }}\n\n*Top Risks*\n{{ $json.topFindings.map(i => {\n const emoji = i.kev ? '\u26a1' : (i.severity === 'CRITICAL' ? '\ud83d\udd34' : '\ud83d\udfe0');\n return `\u2022 ${emoji} *${i.technology}* | <${i.url}|${i.cveId}> | ${i.severity} | EPSS ${(i.epss * 100).toFixed(1)}%`;\n}).join('\\n') }}\n\n_Full details sent via email._",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "security-alerts"
},
"otherOptions": {}
},
"typeVersion": 2.4
},
{
"id": "a3a932d8-08ec-4b23-9b41-d0d3a4c02c5a",
"name": "Prepare EPSS Lookup",
"type": "n8n-nodes-base.code",
"position": [
1120,
976
],
"parameters": {
"jsCode": "const items = $input.all();\n\nif (items.length === 0) return [];\n\nconst cves = [...new Set(items.map(i => i.json.cveId))];\n\nreturn [\n {\n json: {\n cves: cves.join(','),\n findings: items.map(i => i.json)\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "94753dc0-411a-4ee9-8419-612438d19fca",
"name": "Fetch EPSS",
"type": "n8n-nodes-base.httpRequest",
"position": [
1360,
976
],
"parameters": {
"url": "https://api.first.org/data/v1/epss",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "cve",
"value": "={{$json.cves}}"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "6783bc6a-9b96-4340-a7d0-88b8d88f75b4",
"name": "Attach EPSS Score",
"type": "n8n-nodes-base.code",
"position": [
1600,
976
],
"parameters": {
"jsCode": "const findings = $('Prepare EPSS Lookup').first().json.findings || [];\nconst epssData = $('Fetch EPSS').first().json.data || [];\n\nconst epssMap = {};\n\nfor (const item of epssData) {\n epssMap[item.cve] = {\n epss: Number(item.epss || 0),\n percentile: Number(item.percentile || 0),\n };\n}\n\nconst enriched = findings.map(finding => {\n const epss = epssMap[finding.cveId] || {\n epss: 0,\n percentile: 0,\n };\n\n return {\n json: {\n ...finding,\n epss: epss.epss,\n epssPercentile: epss.percentile,\n epssPercent: `${(epss.epss * 100).toFixed(2)}%`,\n },\n };\n});\n\nreturn enriched;"
},
"typeVersion": 2
},
{
"id": "c23e8d71-3441-4969-a39e-f0527a9a9b1b",
"name": "Fetch KEV from GitHub Mirror",
"type": "n8n-nodes-base.httpRequest",
"position": [
1840,
976
],
"parameters": {
"url": "https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json",
"options": {
"redirect": {
"redirect": {}
}
}
},
"typeVersion": 4.3
},
{
"id": "eca9207a-911c-4c08-b4a3-fd4699dd1e52",
"name": "Attach KEV",
"type": "n8n-nodes-base.code",
"position": [
2080,
976
],
"parameters": {
"jsCode": "const findings = $('Attach EPSS Score').all().map(i => i.json);\nconst kevData = $('Fetch KEV from GitHub Mirror').first().json.vulnerabilities || [];\n\nconst kevSet = new Set(\n kevData.map(v => v.cveID)\n);\n\nconst enriched = findings.map(finding => {\n const isKev = kevSet.has(finding.cveId);\n\n return {\n json: {\n ...finding,\n kev: isKev,\n kevLabel: isKev ? 'YES' : 'NO'\n }\n };\n});\n\nreturn enriched;"
},
"typeVersion": 2
},
{
"id": "fd2b1021-0848-4cad-902a-d7284d81f64e",
"name": "Fetch Technology Watchlist CSV",
"type": "n8n-nodes-base.httpRequest",
"position": [
-80,
976
],
"parameters": {
"url": "https://example.com/technology-watchlist.csv",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
},
"typeVersion": 4.3,
"alwaysOutputData": false
},
{
"id": "77306447-e06d-4afd-b185-46752c4e13a7",
"name": "Fetch Recent CVEs from NVD",
"type": "n8n-nodes-base.httpRequest",
"position": [
400,
976
],
"parameters": {
"url": "https://services.nvd.nist.gov/rest/json/cves/2.0",
"options": {},
"sendQuery": true,
"sendHeaders": true,
"queryParameters": {
"parameters": [
{
"name": "pubStartDate",
"value": "={{$now.minus({ days: 1 }).toISO()}}"
},
{
"name": "pubEndDate",
"value": "={{$now.toISO()}}"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "apiKey",
"value": "PASTE_YOUR_NVD_API_KEY_HERE"
}
]
}
},
"typeVersion": 4.3
}
],
"active": false,
"settings": {
"availableInMCP": false,
"executionOrder": "v1"
},
"connections": {
"Attach KEV": {
"main": [
[
{
"node": "Prioritize & Build Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch EPSS": {
"main": [
[
{
"node": "Attach EPSS Score",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Fetch Technology Watchlist CSV",
"type": "main",
"index": 0
}
]
]
},
"Attach EPSS Score": {
"main": [
[
{
"node": "Fetch KEV from GitHub Mirror",
"type": "main",
"index": 0
}
]
]
},
"Deduplicate Alerts": {
"main": [
[
{
"node": "Prepare EPSS Lookup",
"type": "main",
"index": 0
}
]
]
},
"Read Watchlist CSV": {
"main": [
[
{
"node": "Fetch Recent CVEs from NVD",
"type": "main",
"index": 0
}
]
]
},
"Prepare EPSS Lookup": {
"main": [
[
{
"node": "Fetch EPSS",
"type": "main",
"index": 0
}
]
]
},
"Match CVEs to Watchlist": {
"main": [
[
{
"node": "Deduplicate Alerts",
"type": "main",
"index": 0
}
]
]
},
"Prioritize & Build Digest": {
"main": [
[
{
"node": "Send Email Digest",
"type": "main",
"index": 0
},
{
"node": "Send Slack Digest",
"type": "main",
"index": 0
}
]
]
},
"Fetch Recent CVEs from NVD": {
"main": [
[
{
"node": "Match CVEs to Watchlist",
"type": "main",
"index": 0
}
]
]
},
"Fetch KEV from GitHub Mirror": {
"main": [
[
{
"node": "Attach KEV",
"type": "main",
"index": 0
}
]
]
},
"Fetch Technology Watchlist CSV": {
"main": [
[
{
"node": "Read Watchlist CSV",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Security teams often struggle to keep up with the volume of newly published CVEs and manually determine which vulnerabilities are actually relevant to their environment.
Source: https://n8n.io/workflows/15700/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Instead of providing a routine check, it focuses on significant movements by: Sending a Slack alert only if a query crosses a defined movement threshold. Emailing a structured report with the Top 25 i
This workflow automates the complete end-to-end processing of daily revenue transactions for finance and accounting teams. It systematically retrieves, validates, and standardizes transaction data fro
E-commerce store owners, product managers, marketplace sellers, and pricing analysts who want to automatically track competitor pricing and get actionable alerts when their products are overpriced or
This template is ideal for developers, agencies, hosting providers, and website owners who need real-time alerts when a website goes down. It helps teams react quickly to downtime by sending multi-cha
Schedule Slack. Uses scheduleTrigger, googleSheets, slack, gmail. Scheduled trigger; 15 nodes.