AutomationFlowsAI & RAG › Send Wazuh Security Alerts as Gemini-enriched Email Reports and Notifications

Send Wazuh Security Alerts as Gemini-enriched Email Reports and Notifications

ByAlberto Flores @xpertix on n8n.io

This workflow queries Wazuh Indexer on a schedule or manually, summarizes alert activity, uses Google Gemini to generate structured security insights, and sends role-based HTML email reports or immediate alert notifications via SMTP, optionally attaching PDFs rendered by…

Event trigger★★★★★ complexityAI-powered35 nodesData TableHTTP RequestEmail SendChain LlmGoogle Gemini ChatOutput Parser Structured
AI & RAG Trigger: Event Nodes: 35 Complexity: ★★★★★ AI nodes: yes Added:
Send Wazuh Security Alerts as Gemini-enriched Email Reports and Notifications — n8n workflow card showing Data Table, HTTP Request, Email Send integration

This workflow corresponds to n8n.io template #18167 — we link there as the canonical source.

This workflow follows the Chainllm → Emailsend 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 →

Download .json
{
  "id": "fgZqLNO7d78wPuFS",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Wazuh Alert Intelligence, Notification & Reporting",
  "tags": [],
  "nodes": [
    {
      "id": "685130d3-2fe0-40d0-9cf9-cc190292226f",
      "name": "Run workflow manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        160
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "6f547050-f011-4bdd-9dea-845eb5bd3d28",
      "name": "Use manual reporting mode",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "run-mode-manual",
              "name": "runMode",
              "type": "string",
              "value": "manual_report"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "4d9f37df-7afd-4b01-a224-d8b644c45185",
      "name": "Configure alert intelligence workflow",
      "type": "n8n-nodes-base.set",
      "position": [
        528,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "organization-name",
              "name": "organizationName",
              "type": "string",
              "value": "Example Organization"
            },
            {
              "id": "organization-key",
              "name": "organizationKey",
              "type": "string",
              "value": "example-organization"
            },
            {
              "id": "indexer-url",
              "name": "wazuhIndexerUrl",
              "type": "string",
              "value": "https://wazuh-indexer.example.internal:9200"
            },
            {
              "id": "gotenberg-url",
              "name": "gotenbergUrl",
              "type": "string",
              "value": "http://gotenberg:3000"
            },
            {
              "id": "sender-name",
              "name": "senderName",
              "type": "string",
              "value": "Wazuh Security Intelligence"
            },
            {
              "id": "sender-email",
              "name": "senderEmail",
              "type": "string",
              "value": "security@example.com"
            },
            {
              "id": "critical-min",
              "name": "criticalLevelMin",
              "type": "number",
              "value": 12
            },
            {
              "id": "high-min",
              "name": "highLevelMin",
              "type": "number",
              "value": 10
            },
            {
              "id": "medium-min",
              "name": "mediumLevelMin",
              "type": "number",
              "value": 7
            },
            {
              "id": "low-min",
              "name": "lowLevelMin",
              "type": "number",
              "value": 3
            },
            {
              "id": "immediate-lookback",
              "name": "immediateLookbackMinutes",
              "type": "number",
              "value": 15
            },
            {
              "id": "manual-test-lookback",
              "name": "manualTestLookbackDays",
              "type": "number",
              "value": 30
            },
            {
              "id": "technical-limit",
              "name": "technicalEvidenceLimit",
              "type": "number",
              "value": 100
            },
            {
              "id": "generate-pdf",
              "name": "generatePdf",
              "type": "boolean",
              "value": false
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "8782615e-10ea-4c3f-9006-9256d7c76511",
      "name": "Check for urgent alerts every 5 minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        400
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "e0330028-c520-42e2-a3bf-d9545dd02b60",
      "name": "Use immediate notification mode",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        400
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "run-mode-immediate",
              "name": "runMode",
              "type": "string",
              "value": "immediate"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "99bd9e80-a2ee-4f79-9946-4909382f483e",
      "name": "Build weekly reports",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        640
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ec15471c-29d9-4b3d-aa14-603643838a08",
      "name": "Use weekly reporting mode",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        640
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "run-mode-weekly",
              "name": "runMode",
              "type": "string",
              "value": "weekly"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7fd905c5-d00c-4c34-8900-d1da4ba85456",
      "name": "Build monthly reports",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        880
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months",
              "triggerAtHour": 7
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "5835e845-7396-4fa7-82d8-9d31d82feeb6",
      "name": "Use monthly reporting mode",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        880
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "run-mode-monthly",
              "name": "runMode",
              "type": "string",
              "value": "monthly"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a52c1364-e5bf-45c7-ac46-84f350d80e9e",
      "name": "Load enabled delivery profiles",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        768,
        528
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "organizationKey",
              "keyValue": "={{ $(\"Configure alert intelligence workflow\").first().json.organizationKey }}"
            },
            {
              "keyName": "enabled",
              "condition": "isTrue"
            }
          ]
        },
        "matchType": "allConditions",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "x2D4WxDNA6gIdbNN",
          "cachedResultName": "wazuh_alert_delivery_profiles"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "5af5e65f-9003-4c53-a894-4ec64e7ae572",
      "name": "Prepare bounded Wazuh alert query",
      "type": "n8n-nodes-base.code",
      "position": [
        1008,
        528
      ],
      "parameters": {
        "jsCode": "const config = $('Configure alert intelligence workflow').first().json;\nconst now = new Date();\nlet from = new Date(now);\n\nif (config.runMode === 'immediate') from.setMinutes(from.getMinutes() - config.immediateLookbackMinutes);\nelse if (config.runMode === 'weekly') from.setDate(from.getDate() - 7);\nelse if (config.runMode === 'monthly' || config.runMode === 'manual_report') from.setDate(from.getDate() - 30);\nelse if (config.runMode === 'immediate_test') from.setDate(from.getDate() - config.manualTestLookbackDays);\n\nconst threatDetection = {\n  bool: {\n    should: [\n      { exists: { field: 'rule.mitre.tactic' } },\n      { exists: { field: 'rule.mitre.technique' } },\n    ],\n    minimum_should_match: 1,\n  },\n};\nconst fileIntegrity = {\n  bool: {\n    should: [\n      { term: { 'rule.groups': 'syscheck' } },\n      { term: { 'rule.groups': 'fim' } },\n    ],\n    minimum_should_match: 1,\n  },\n};\nconst configurationAssessment = { term: { 'rule.groups': 'sca' } };\nconst vulnerabilityDetection = { term: { 'rule.groups': 'vulnerability-detector' } };\nconst authentication = {\n  bool: {\n    should: [\n      { term: { 'rule.groups': 'authentication_success' } },\n      { term: { 'rule.groups': 'authentication_failed' } },\n      { term: { 'rule.groups': 'authentication_failures' } },\n      { term: { 'rule.groups': 'pam' } },\n      { term: { 'rule.groups': 'sshd' } },\n    ],\n    minimum_should_match: 1,\n  },\n};\nconst operational = {\n  bool: {\n    should: [\n      { term: { 'rule.groups': 'ossec' } },\n      { term: { 'rule.groups': 'docker' } },\n      { term: { 'rule.groups': 'rootcheck' } },\n      { term: { 'rule.groups': 'agentless' } },\n    ],\n    minimum_should_match: 1,\n  },\n};\nconst compliance = {\n  bool: {\n    should: [\n      { exists: { field: 'rule.pci_dss' } },\n      { exists: { field: 'rule.hipaa' } },\n      { exists: { field: 'rule.gdpr' } },\n      { exists: { field: 'rule.nist_800_53' } },\n      { exists: { field: 'rule.tsc' } },\n      { exists: { field: 'rule.cis' } },\n      { exists: { field: 'rule.soc_2' } },\n    ],\n    minimum_should_match: 1,\n  },\n};\nconst recognizedContext = {\n  bool: {\n    should: [\n      threatDetection,\n      fileIntegrity,\n      configurationAssessment,\n      vulnerabilityDetection,\n      authentication,\n      operational,\n      compliance,\n    ],\n    minimum_should_match: 1,\n  },\n};\n\nreturn [{\n  json: {\n    runMode: config.runMode,\n    periodStart: from.toISOString(),\n    periodEnd: now.toISOString(),\n    query: {\n      size: config.technicalEvidenceLimit,\n      track_total_hits: true,\n      query: ['immediate', 'immediate_test'].includes(config.runMode)\n        ? {\n            bool: {\n              filter: [\n                { range: { timestamp: { gte: from.toISOString(), lte: now.toISOString() } } },\n                { range: { 'rule.level': { gte: config.highLevelMin } } },\n              ],\n            },\n          }\n        : { range: { timestamp: { gte: from.toISOString(), lte: now.toISOString() } } },\n      sort: [{ timestamp: { order: 'desc', unmapped_type: 'date' } }],\n      aggs: {\n        by_level: { terms: { field: 'rule.level', size: 20 } },\n        top_rules: {\n          terms: { field: 'rule.id', size: 25 },\n          aggs: {\n            sample: {\n              top_hits: {\n                size: 1,\n                _source: ['timestamp', 'rule', 'agent', 'data', 'decoder', 'location'],\n              },\n            },\n          },\n        },\n        top_agents: { terms: { field: 'agent.name', size: 15, missing: 'unassigned' } },\n        use_case_coverage: {\n          filters: {\n            filters: {\n              threat_detection: threatDetection,\n              file_integrity_monitoring: fileIntegrity,\n              security_configuration_assessment: configurationAssessment,\n              vulnerability_detection: vulnerabilityDetection,\n              authentication_monitoring: authentication,\n              system_operational_monitoring: operational,\n              compliance_context: compliance,\n              other_unclassified: { bool: { must_not: [recognizedContext] } },\n            },\n          },\n        },\n        threat_mitre: {\n          filter: threatDetection,\n          aggs: {\n            tactics: { terms: { field: 'rule.mitre.tactic', size: 15 } },\n            techniques: { terms: { field: 'rule.mitre.technique', size: 15 } },\n          },\n        },\n        event_trend: {\n          date_histogram: {\n            field: 'timestamp',\n            fixed_interval: ['immediate', 'immediate_test'].includes(config.runMode) ? '1m' : '1d',\n            min_doc_count: 0,\n          },\n        },\n      },\n    },\n  },\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a70db907-e638-43d3-8870-788f6d8d741f",
      "name": "Query Wazuh alert evidence",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        1248,
        528
      ],
      "parameters": {
        "url": "={{ $(\"Configure alert intelligence workflow\").first().json.wazuhIndexerUrl.replace(/\\/$/, \"\") + \"/wazuh-alerts*/_search\" }}",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "allowUnauthorizedCerts": true
        },
        "jsonBody": "={{ $json.query }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "140b9407-89fc-46ff-af15-0e54c6b79e20",
      "name": "Build deterministic security context",
      "type": "n8n-nodes-base.code",
      "position": [
        1632,
        224
      ],
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst config = $('Configure alert intelligence workflow').first().json;\nconst queryMeta = $('Prepare bounded Wazuh alert query').first().json;\nconst hits = response.hits?.hits || [];\nconst total = Number(response.hits?.total?.value ?? response.hits?.total ?? hits.length);\nconst aggs = response.aggregations || {};\nconst severityBuckets = aggs.by_level?.buckets || [];\n\nconst countRange = (min, max = Infinity) => severityBuckets\n  .filter((bucket) => Number(bucket.key) >= min && Number(bucket.key) <= max)\n  .reduce((sum, bucket) => sum + Number(bucket.doc_count || 0), 0);\n\nconst counts = {\n  critical: countRange(config.criticalLevelMin),\n  high: countRange(config.highLevelMin, config.criticalLevelMin - 1),\n  medium: countRange(config.mediumLevelMin, config.highLevelMin - 1),\n  low: countRange(config.lowLevelMin, config.mediumLevelMin - 1),\n};\n\nconst normalizeGroups = (rule = {}) => (Array.isArray(rule.groups) ? rule.groups : [rule.groups])\n  .filter(Boolean)\n  .map((group) => String(group).toLowerCase());\nconst hasAny = (groups, candidates) => candidates.some((candidate) => groups.includes(candidate));\nconst classifyRule = (rule = {}) => {\n  const groups = normalizeGroups(rule);\n  const categories = [];\n  if ((rule.mitre?.tactic?.length || 0) > 0 || (rule.mitre?.technique?.length || 0) > 0) categories.push('Threat Detection');\n  if (hasAny(groups, ['syscheck', 'fim'])) categories.push('File Integrity Monitoring');\n  if (groups.includes('sca')) categories.push('Security Configuration Assessment');\n  if (groups.includes('vulnerability-detector')) categories.push('Vulnerability Detection');\n  if (hasAny(groups, ['authentication_success', 'authentication_failed', 'authentication_failures', 'pam', 'sshd'])) categories.push('Authentication Monitoring');\n  if (hasAny(groups, ['ossec', 'docker', 'rootcheck', 'agentless'])) categories.push('System & Operational Monitoring');\n  if (['pci_dss', 'hipaa', 'gdpr', 'nist_800_53', 'tsc', 'cis', 'soc_2'].some((field) => (rule[field]?.length || 0) > 0)) categories.push('Compliance Context');\n  return categories.length ? categories : ['Other / Unclassified'];\n};\n\nconst topRules = (aggs.top_rules?.buckets || []).map((bucket) => {\n  const source = bucket.sample?.hits?.hits?.[0]?._source || {};\n  return {\n    id: String(bucket.key),\n    count: Number(bucket.doc_count || 0),\n    description: source.rule?.description || 'No description',\n    level: Number(source.rule?.level || 0),\n    groups: source.rule?.groups || [],\n    useCases: classifyRule(source.rule || {}),\n  };\n});\n\nconst topAgents = (aggs.top_agents?.buckets || []).map((bucket) => ({\n  name: String(bucket.key),\n  count: Number(bucket.doc_count || 0),\n}));\n\nconst labels = {\n  threat_detection: 'Threat Detection',\n  file_integrity_monitoring: 'File Integrity Monitoring',\n  security_configuration_assessment: 'Security Configuration Assessment',\n  vulnerability_detection: 'Vulnerability Detection',\n  authentication_monitoring: 'Authentication Monitoring',\n  system_operational_monitoring: 'System & Operational Monitoring',\n  compliance_context: 'Compliance Context',\n  other_unclassified: 'Other / Unclassified',\n};\nconst coverageBuckets = aggs.use_case_coverage?.buckets || {};\nconst useCaseCoverage = Object.entries(labels).map(([key, name]) => {\n  const count = Number(coverageBuckets[key]?.doc_count || 0);\n  return {\n    key,\n    name,\n    count,\n    percentage: total > 0 ? Number(((count / total) * 100).toFixed(1)) : 0,\n  };\n});\n\nconst threatMitreCount = Number(aggs.threat_mitre?.doc_count || 0);\nconst tactics = (aggs.threat_mitre?.tactics?.buckets || []).map((bucket) => ({\n  name: String(bucket.key),\n  count: Number(bucket.doc_count || 0),\n}));\nconst techniques = (aggs.threat_mitre?.techniques?.buckets || []).map((bucket) => ({\n  name: String(bucket.key),\n  count: Number(bucket.doc_count || 0),\n}));\n\nconst evidence = hits.map((hit) => {\n  const source = hit._source || {};\n  const agent = source.agent?.name || source.agent?.id || 'unassigned';\n  const ruleId = String(source.rule?.id || '');\n  const timestamp = source.timestamp || '';\n  const location = source.location || '';\n  return {\n    eventId: String(hit._id || source.id || [timestamp, agent, ruleId, location].join('|')),\n    timestamp,\n    agent,\n    ruleId,\n    level: Number(source.rule?.level || 0),\n    description: source.rule?.description || '',\n    useCases: classifyRule(source.rule || {}),\n    tactics: source.rule?.mitre?.tactic || [],\n    techniques: source.rule?.mitre?.technique || [],\n    location,\n    decoder: source.decoder?.name || '',\n    sourceIp: source.data?.srcip || source.data?.src_ip || source.srcip || '',\n    destinationIp: source.data?.dstip || source.data?.dst_ip || source.dstip || '',\n    user: source.data?.srcuser || source.data?.dstuser || source.data?.user || '',\n  };\n});\n\nconst reportMode = ['manual_report', 'weekly', 'monthly'].includes(config.runMode);\nconst immediateMode = ['immediate', 'immediate_test'].includes(config.runMode);\nconst urgentEvents = evidence.filter((event) => event.level >= config.highLevelMin);\n\nreturn [{\n  json: {\n    runMode: config.runMode,\n    periodStart: queryMeta.periodStart,\n    periodEnd: queryMeta.periodEnd,\n    total,\n    counts,\n    topRules,\n    topAgents,\n    useCaseCoverage,\n    useCaseCoverageIsNonExclusive: true,\n    threatMitreCount,\n    threatMitrePercentage: total > 0 ? Number(((threatMitreCount / total) * 100).toFixed(1)) : 0,\n    tactics,\n    techniques,\n    evidence,\n    reportMode,\n    immediateMode,\n    urgentEvents,\n    shouldContinue: reportMode || (immediateMode && urgentEvents.length > 0),\n    aiContext: {\n      reportingWindow: { start: queryMeta.periodStart, end: queryMeta.periodEnd },\n      totalAlerts: total,\n      severityCounts: counts,\n      wazuhUseCaseCoverage: useCaseCoverage,\n      coverageInterpretation: 'Use-case counts are non-exclusive because one Wazuh event can support multiple security contexts.',\n      topRules: topRules.slice(0, 12).map(({ id, count, description, level, groups, useCases }) => ({ id, count, description, level, groups, useCases })),\n      threatDetectionMitre: {\n        mappedAlertCount: threatMitreCount,\n        percentageOfAllAlerts: total > 0 ? Number(((threatMitreCount / total) * 100).toFixed(1)) : 0,\n        tactics: tactics.slice(0, 12),\n        techniques: techniques.slice(0, 12),\n      },\n      dataHandling: 'Only aggregate metrics, rule summaries, Wazuh use-case coverage, and MITRE context for Threat Detection are provided. Asset names and complete event records are excluded.',\n    },\n  },\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d1c6cb08-6e4c-4e68-a1fe-b4c9b64c8fdf",
      "name": "Route reports and immediate alerts",
      "type": "n8n-nodes-base.if",
      "position": [
        1952,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-report",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.deliveryType }}",
              "rightValue": "report"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "f12bf846-64ab-4951-a266-c71d30499418",
      "name": "Convert report HTML to index file",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        2560,
        368
      ],
      "parameters": {
        "options": {
          "encoding": "utf8",
          "fileName": "index.html"
        },
        "operation": "toText",
        "sourceProperty": "reportHtml"
      },
      "typeVersion": 1.1
    },
    {
      "id": "e6c7fc48-4437-4849-9d4a-1dfcdc374dfb",
      "name": "Render report PDF with Gotenberg",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        2800,
        368
      ],
      "parameters": {
        "url": "={{ $(\"Configure alert intelligence workflow\").first().json.gotenbergUrl.replace(/\\/$/, \"\") + \"/forms/chromium/convert/html\" }}",
        "method": "POST",
        "options": {
          "timeout": 120000,
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "reportPdf"
            }
          }
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "files",
              "parameterType": "formBinaryData",
              "inputDataFieldName": "data"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "56022da3-9523-4e9b-bbec-da9a1fe82f9f",
      "name": "Send HTML report with PDF attachment",
      "type": "n8n-nodes-base.emailSend",
      "maxTries": 3,
      "position": [
        3280,
        368
      ],
      "parameters": {
        "html": "={{ $(\"Build role-based deliveries\").item.json.emailHtml }}",
        "options": {
          "fileAttachments": "reportPdf",
          "appendAttribution": false
        },
        "subject": "={{ $(\"Build role-based deliveries\").item.json.subject }}",
        "toEmail": "={{ $(\"Build role-based deliveries\").item.json.recipientEmail }}",
        "fromEmail": "={{ $(\"Configure alert intelligence workflow\").first().json.senderName + \" <\" + $(\"Configure alert intelligence workflow\").first().json.senderEmail + \">\" }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.1,
      "waitBetweenTries": 5000
    },
    {
      "id": "d5909367-b150-4f9e-a0f4-2d52007682a8",
      "name": "Log report delivery",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        3520,
        368
      ],
      "parameters": {
        "columns": {
          "value": {
            "runId": "={{ $(\"Build role-based deliveries\").item.json.runId }}",
            "sentAt": "={{ $now.toISO() }}",
            "severity": "={{ $(\"Build role-based deliveries\").item.json.severity }}",
            "periodEnd": "={{ $(\"Build role-based deliveries\").item.json.periodEnd }}",
            "deliveryKey": "={{ $(\"Build role-based deliveries\").item.json.deliveryKey }}",
            "periodStart": "={{ $(\"Build role-based deliveries\").item.json.periodStart }}",
            "deliveryType": "={{ $(\"Build role-based deliveries\").item.json.deliveryType }}",
            "deliveryStatus": "sent",
            "recipientEmail": "={{ $(\"Build role-based deliveries\").item.json.recipientEmail }}",
            "audienceProfile": "={{ $(\"Build role-based deliveries\").item.json.audienceProfile }}",
            "metadata_object": "={{ JSON.stringify({ executionId: $execution.id }) }}",
            "organizationKey": "={{ $(\"Build role-based deliveries\").item.json.organizationKey }}",
            "providerMessageId": "={{ $json.messageId || $json.response || \"accepted\" }}"
          },
          "schema": [
            {
              "id": "deliveryKey",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "deliveryKey",
              "defaultMatch": false
            },
            {
              "id": "runId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "runId",
              "defaultMatch": false
            },
            {
              "id": "organizationKey",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "organizationKey",
              "defaultMatch": false
            },
            {
              "id": "recipientEmail",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "recipientEmail",
              "defaultMatch": false
            },
            {
              "id": "deliveryType",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "deliveryType",
              "defaultMatch": false
            },
            {
              "id": "audienceProfile",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "audienceProfile",
              "defaultMatch": false
            },
            {
              "id": "severity",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "severity",
              "defaultMatch": false
            },
            {
              "id": "periodStart",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "periodStart",
              "defaultMatch": false
            },
            {
              "id": "periodEnd",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "periodEnd",
              "defaultMatch": false
            },
            {
              "id": "deliveryStatus",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "deliveryStatus",
              "defaultMatch": false
            },
            {
              "id": "providerMessageId",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "providerMessageId",
              "defaultMatch": false
            },
            {
              "id": "errorSummary",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "errorSummary",
              "defaultMatch": false
            },
            {
              "id": "sentAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentAt",
              "defaultMatch": false
            },
            {
              "id": "metadata_object",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "metadata_object",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "eFRljEojwfT812EE",
          "cachedResultName": "wazuh_alert_delivery_log"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f2b72615-d005-4f45-962c-49b8a40ad7e7",
      "name": "Send immediate operational notification",
      "type": "n8n-nodes-base.emailSend",
      "maxTries": 3,
      "position": [
        2592,
        880
      ],
      "parameters": {
        "html": "={{ $(\"Build role-based deliveries\").item.json.emailHtml }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "={{ $(\"Build role-based deliveries\").item.json.subject }}",
        "toEmail": "={{ $(\"Build role-based deliveries\").item.json.recipientEmail }}",
        "fromEmail": "={{ $(\"Configure alert intelligence workflow\").first().json.senderName + \" <\" + $(\"Configure alert intelligence workflow\").first().json.senderEmail + \">\" }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.1,
      "waitBetweenTries": 5000
    },
    {
      "id": "45d04284-b6a1-43c2-baa7-642fbf17fd87",
      "name": "Log immediate notification delivery",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2832,
        880
      ],
      "parameters": {
        "columns": {
          "value": {
            "runId": "={{ $(\"Build role-based deliveries\").item.json.runId }}",
            "sentAt": "={{ $now.toISO() }}",
            "severity": "={{ $(\"Build role-based deliveries\").item.json.severity }}",
            "periodEnd": "={{ $(\"Build role-based deliveries\").item.json.periodEnd }}",
            "deliveryKey": "={{ $(\"Build role-based deliveries\").item.json.deliveryKey }}",
            "periodStart": "={{ $(\"Build role-based deliveries\").item.json.periodStart }}",
            "deliveryType": "={{ $(\"Build role-based deliveries\").item.json.deliveryType }}",
            "errorSummary": "",
            "deliveryStatus": "sent",
            "recipientEmail": "={{ $(\"Build role-based deliveries\").item.json.recipientEmail }}",
            "audienceProfile": "={{ $(\"Build role-based deliveries\").item.json.audienceProfile }}",
            "metadata_object": "={{ JSON.stringify({ executionId: $execution.id, eventId: $(\"Build role-based deliveries\").item.json.eventId, ruleId: $(\"Build role-based deliveries\").item.json.ruleId, agent: $(\"Build role-based deliveries\").item.json.agent }) }}",
            "organizationKey": "={{ $(\"Build role-based deliveries\").item.json.organizationKey }}",
            "providerMessageId": "={{ $json.messageId || $json.response || \"accepted\" }}"
          },
          "schema": [
            {
              "id": "deliveryKey",
              "type": "string",
              "display": true,
              "displayName": "deliveryKey"
            },
            {
              "id": "runId",
              "type": "string",
              "display": true,
              "displayName": "runId"
            },
            {
              "id": "organizationKey",
              "type": "string",
              "display": true,
              "displayName": "organizationKey"
            },
            {
              "id": "recipientEmail",
              "type": "string",
              "display": true,
              "displayName": "recipientEmail"
            },
            {
              "id": "deliveryType",
              "type": "string",
              "display": true,
              "displayName": "deliveryType"
            },
            {
              "id": "audienceProfile",
              "type": "string",
              "display": true,
              "displayName": "audienceProfile"
            },
            {
              "id": "severity",
              "type": "string",
              "display": true,
              "displayName": "severity"
            },
            {
              "id": "periodStart",
              "type": "date",
              "display": true,
              "displayName": "periodStart"
            },
            {
              "id": "periodEnd",
              "type": "date",
              "display": true,
              "displayName": "periodEnd"
            },
            {
              "id": "deliveryStatus",
              "type": "string",
              "display": true,
              "displayName": "deliveryStatus"
            },
            {
              "id": "providerMessageId",
              "type": "string",
              "display": true,
              "displayName": "providerMessageId"
            },
            {
              "id": "errorSummary",
              "type": "string",
              "display": true,
              "displayName": "errorSummary"
            },
            {
              "id": "sentAt",
              "type": "date",
              "display": true,
              "displayName": "sentAt"
            },
            {
              "id": "metadata_object",
              "type": "string",
              "display": true,
              "displayName": "metadata_object"
            }
          ],
          "mappingMode": "defineBelow"
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "eFRljEojwfT812EE",
          "cachedResultName": "wazuh_alert_delivery_log"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ce5ecdaf-899f-4a3c-a617-59b0f06aaa20",
      "name": "Workflow overview and setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -832,
        -48
      ],
      "parameters": {
        "color": "#F3FF4D",
        "width": 680,
        "height": 2676,
        "content": "## Turn Wazuh alerts into AI-enriched notifications and role-based reports\n\n**Wazuh** is an open-source SIEM and XDR platform supporting Threat Detection, File Integrity Monitoring, Security Configuration Assessment, Vulnerability Detection, Authentication Monitoring, operational monitoring, and compliance. Wazuh collects and indexes the evidence; n8n converts it into immediate notifications and audience-specific reports.\n\n### How it works\n\n\u2192 Manual, weekly, monthly, and five-minute monitoring modes share one bounded Wazuh Indexer query. Immediate modes restrict the query to Critical and High rule levels.\n\n\u2192 Severity counts, reporting windows, Wazuh use-case coverage, recipient selection, cadence, and delivery policy are deterministic. Gemini cannot change them.\n\n\u2192 Use-case coverage is **non-exclusive**. One alert can support multiple contexts, so category counts are not expected to sum to the total.\n\n\u2192 MITRE ATT&CK tactics and techniques are calculated only from the **Threat Detection subset** containing a Wazuh MITRE mapping. Other Wazuh use cases remain visible in their own categories.\n\n\u2192 Gemini receives minimized aggregates, rule summaries, use-case coverage, and MITRE context. Asset names and complete events are excluded. Validated JSON adds narrative, technical observations, recommendations, and limitations.\n\n\u2192 Delivery profiles explicitly select the recipient, audience (**Executive**, **Technical**, or **Combined**), cadence, and allowed immediate severities.\n\n\u2192 All report audiences use one email-safe visual system based on responsive presentation tables. This preserves compact severity cards in Gmail and common email clients while adapting content depth to the selected audience.\n\n\u2192 Executive reports emphasize posture, leadership context, coverage, and priority actions. Technical reports add rules, assets, MITRE context, observations, and evidence. Combined reports append the complete technical analysis to the executive view.\n\n\u2192 Immediate notifications are event-driven: each email contains exactly one Critical or High Wazuh event. They do not include total-alert metrics, severity distributions, trends, or reporting-period summaries.\n\n\u2192 Complete HTML email is the default. Optional PDF attachments render the same HTML through Gotenberg only when `generatePdf=true`.\n\n\u2192 Every successful report or immediate alert is recorded in the delivery log Data Table. Before an immediate email is sent, its organization/profile/event delivery key is checked against the same table so a previously delivered Wazuh event is not sent twice.\n\n### Setup\n\n\u2192 Create **wazuh_alert_delivery_profiles** with: `profileKey`, `organizationKey`, `recipientEmail`, `recipientName`, `audienceProfile`, `receiveImmediateAlerts`, `immediateSeverities_object`, `receiveReports`, `reportCadence`, `timezone`, and `enabled`.\n\n\u2192 Store immediate severities as a JSON string, for example `[\"critical\",\"high\"]`.\n\n\u2192 Create **wazuh_alert_delivery_log** with the columns mapped in the logging nodes.\n\n\u2192 Add a read-only Wazuh Indexer Basic Auth credential limited to `wazuh-alerts*`, an SMTP credential, and a Google Gemini API credential.\n\n\u2192 Replace deployment-specific values in **Configure alert intelligence workflow**.\n\n\u2192 Keep **Use manual reporting mode** set to `manual_report` for on-demand validation. Use `immediate_test` temporarily to send only the newest eligible event with a `[TEST]` subject, then restore `manual_report`.\n\n\u2192 Verify Executive, Technical, and Combined HTML emails and the delivery log before activating required schedules.\n\n\u2192 For unattended schedules, connect a published n8n Error Trigger workflow that notifies the workflow owner when a production execution fails.\n\n### Customization\n\n\u2192 Assign different addresses or distribution lists to delivery profiles. The workflow never infers a recipient role.\n\n\u2192 Adjust severity thresholds, cadence, lookback windows, evidence limits, immediate severity lists, and deterministic group-to-use-case mappings.\n\n\u2192 The shared visual shell can be branded centrally in **Build role-based deliveries** without changing audience routing or evidence logic.\n\n\u2192 `generatePdf=false` needs no document service. For `true`, deploy Gotenberg privately, configure `gotenbergUrl`, and verify the attached PDF matches the HTML email.\n\n\u2192 Add Slack, Microsoft Teams, or webhook channels after the delivery builder while preserving explicit routing rules.\n\n### Security\n\n\u2192 Store secrets only in n8n Credentials. Never paste passwords, tokens, or API keys into nodes or Data Tables.\n\n\u2192 Use least-privilege, read-only Wazuh access. Review data residency and external-AI policies before enabling Gemini.\n\n\u2192 Gemini enriches narrative only. Analysts must validate recommendations before containment, suppression, tuning, decoder changes, or automated response.\n\n\u2192 The template does not silently create or select an organization-specific failure channel; configure and test the Error Trigger workflow before production use."
      },
      "typeVersion": 1
    },
    {
      "id": "a0006438-1b56-4f08-aec8-0d964d7b6740",
      "name": "Collection and least privilege",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 1540,
        "height": 1176,
        "content": "### 1 - Trigger, configure, and collect\nManual and scheduled modes share one bounded query. Credentials stay in n8n Credentials, and the Wazuh account has read-only access only to **wazuh-alerts***."
      },
      "typeVersion": 1
    },
    {
      "id": "8940f7a4-ea90-4186-a639-a9382c4e9277",
      "name": "Deterministic analysis and routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 892,
        "height": 1176,
        "content": "### 2 - Classify, enrich, and route\nWazuh evidence is represented through non-exclusive use cases: Threat Detection, File Integrity Monitoring, Security Configuration Assessment, Vulnerability Detection, Authentication Monitoring, System & Operational Monitoring, Compliance Context, and Other. MITRE ATT&CK is calculated only for the Threat Detection subset. Gemini receives minimized aggregates and never selects recipients or changes severity."
      },
      "typeVersion": 1
    },
    {
      "id": "1b99f4b1-ef2c-4576-8da2-b89f5f510742",
      "name": "PDF reporting pipeline",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2480,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 1260,
        "height": 648,
        "content": "### 3 - Deliver HTML and optionally attach PDF\nComplete HTML email is the default and needs no document service. When **generatePdf=true**, a private Gotenberg service renders the same HTML and the PDF is attached."
      },
      "typeVersion": 1
    },
    {
      "id": "170c6ad4-dae2-46e6-b496-2772288cc9b0",
      "name": "Immediate notification pipeline",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2480,
        640
      ],
      "parameters": {
        "color": 7,
        "width": 572,
        "height": 488,
        "content": "### 4 - Immediate operational notification\nOnly enabled profiles receive immediate notifications for their configured severities. Use **immediate_test** manually to validate this branch with a clear TEST subject prefix."
      },
      "typeVersion": 1
    },
    {
      "id": "0e838936-4a39-4850-b457-2ebc2a8d5e81",
      "name": "Apply meaningful PDF filenames",
      "type": "n8n-nodes-base.code",
      "position": [
        3040,
        368
      ],
      "parameters": {
        "jsCode": "const reports = $('Build role-based deliveries').all().filter((item) => item.json.deliveryType === 'report');\nreturn $input.all().map((item, index) => {\n if (item.binary?.reportPdf) item.binary.reportPdf.fileName = reports[index]?.json.fileName || 'wazuh-security-report.pdf';\n return item;\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "cc14b369-12c1-46e6-8176-1e70bd07f58f",
      "name": "Continue only when delivery is needed",
      "type": "n8n-nodes-base.if",
      "position": [
        1632,
        528
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "delivery-needed",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ String($(\"Build deterministic security context\").first().json.shouldContinue) }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "193e666b-3690-4dbd-a6e5-442bc0bb22f4",
      "name": "Generate structured security insight with Gemini",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1840,
        528
      ],
      "parameters": {
        "text": "={{ \"You are a cybersecurity reporting assistant. Analyze only the minimized Wazuh summary supplied below.\\nThe payload contains aggregate metrics, rule summaries, and MITRE context only; asset names and complete event records are excluded.\\nDo not invent incidents, assets, business impact, root causes, or remediation facts.\\nSeverity counts, recipient profiles, cadence, routing, and delivery policy are authoritative and must not be changed.\\nIf evidence is insufficient, state that explicitly. Recommendations require analyst validation before containment, suppression, or rule changes.\\nReturn raw JSON only. No markdown fences or prose outside the JSON.\\n\\nMINIMIZED WAZUH SUMMARY:\\n\" + $(\"Build deterministic security context\").first().json.aiContext.toJsonString() }}",
        "batching": {
          "batchSize": 1,
          "delayBetweenBatches": 0
        },
        "messages": {
          "messageValues": [
            {
              "message": "Use only the minimized Wazuh summary. Never change deterministic severity, recipients, cadence, routing, or delivery policy. Never recommend automatic containment or suppression without analyst validation. Return the exact structured JSON required by the parser."
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.9
    },
    {
      "id": "cd112615-8edb-41fd-9902-0eeb259f33d2",
      "name": "Gemini security analysis model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1776,
        800
      ],
      "parameters": {
        "options": {
          "temperature": 0.2,
          "maxOutputTokens": 2500
        },
        "modelName": "models/gemini-3.1-flash-lite"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ce824ce0-8249-401c-b031-4ff5df91cc4a",
      "name": "Validate Gemini security insight",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        1904,
        800
      ],
      "parameters": {
        "prompt": "Instructions:\n--------------\n{instructions}\n--------------\nCompletion:\n--------------\n{completion}\n--------------\nError:\n--------------\n{error}\n--------------\nRepair the completion so it satisfies the schema exactly. Preserve only claims supported by the supplied evidence. Return raw JSON only. DO NOT wrap the output in a markdown code block.",
        "autoFix": true,
        "schemaType": "manual",
        "inputSchema": "{\"type\":\"object\",\"properties\":{\"executiveSummary\":{\"type\":\"string\"},\"riskNarrative\":{\"type\":\"string\"},\"technicalObservations\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1,\"maxItems\":6},\"recommendedActions\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"priority\":{\"type\":\"string\",\"enum\":[\"Immediate\",\"High\",\"Planned\"]},\"action\":{\"type\":\"string\"},\"rationale\":{\"type\":\"string\"}},\"required\":[\"priority\",\"action\",\"rationale\"]},\"minItems\":1,\"maxItems\":6},\"limitations\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"maxItems\":4}},\"required\":[\"executiveSummary\",\"riskNarrative\",\"technicalObservations\",\"recommendedActions\",\"limitations\"]}",
        "customizeRetryPrompt": true
      },
      "typeVersion": 1.3
    },
    {
      "id": "913230b0-5403-49c4-b951-217b99a19bc0",
      "name": "Repair invalid Gemini output",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1904,
        976
      ],
      "parameters": {
        "options": {
          "temperature": 0,
          "maxOutputTokens": 2500
        },
        "modelName": "models/gemini-3.1-flash-lite"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "a219af3e-09d5-4bdb-9130-d291bcfcd044",
      "name": "Build role-based deliveries",
      "type": "n8n-nodes-base.code",
      "position": [
        2176,
        528
      ],
      "parameters": {
        "jsCode": "const context = $('Build deterministic security context').first().json;\nconst config = $('Configure alert intelligence workflow').first().json;\nconst profiles = $('Load enabled delivery profiles').all().map((item) => item.json);\nconst chainResult = $input.first().json;\nconst ai = chainResult.output?.output || chainResult.output || chainResult;\nconst counts = context.counts;\n\nconst esc = (value) => String(value ?? '').replace(/[&<>\"']/g, (character) => ({\n  '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;',\n})[character]);\nconst pct = (value, total = context.total) => total > 0\n  ? Math.round((Number(value || 0) / total) * 1000) / 10\n  : 0;\nconst rows = (items, renderer, empty = 'No data available', colspan = 5) => items.length\n  ? items.map(renderer).join('')\n  : `<tr><td colspan=\"${colspan}\" class=\"empty\">${empty}</td></tr>`;\nconst list = (items, renderer) => items?.length\n  ? `<ul class=\"clean-list\">${items.map(renderer).join('')}</ul>`\n  : '<p class=\"muted\">No additional observation was produced.</p>';\n\nconst attention = counts.critical > 0\n  ? { label: 'IMMEDIATE ATTENTION', color: '#b42318', background: '#fef3f2', border: '#fecdca' }\n  : counts.high > 0\n    ? { label: 'ELEVATED ATTENTION', color: '#9a6700', background: '#fff8e7', border: '#f4c95d' }\n    : { label: 'STABLE ACTIVITY', color: '#067647', background: '#ecfdf3', border: '#abefc6' };\nconst generatedAt = new Date().toISOString();\n\nconst commonStyle = `<style>\n@page{size:A4;margin:14mm}\nbody{margin:0;padding:0;background:#eef2f6;color:#172033;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:1.55}\ntable{border-spacing:0;border-collapse:collapse}\n.shell{width:100%;max-width:760px;margin:0 auto;background:#fff}\n.section{padding:0 34px 28px}\n.section-title{font-size:19px;line-height:26px;font-weight:700;color:#172033;margin:0}\n.section-rule{width:42px;height:4px;background:#0aa9b8;font-size:0;line-height:0;margin:10px 0 18px}\n.data-table{width:100%;border:1px solid #d8e0ea;margin:0}\n.data-table th{background:#f3f6f9;color:#41516a;font-size:11px;line-height:16px;text-transform:uppercase;letter-spacing:.4px;padding:10px;border:1px solid #d8e0ea;text-align:left}\n.data-table td{color:#25334a;font-size:12px;line-height:17px;padding:10px;border:1px solid #d8e0ea;vertical-align:top}\n.empty{color:#66758a;text-align:center}\n.muted{color:#66758a}\n.clean-list{margin:0;padding-left:20px;color:#25334a}\n.clean-list li{padding:0 0 8px}\n.page{page-break-before:always;padding-top:20px}\n.footer{padding:22px 34px 30px;color:#748197;font-size:10px;line-height:15px;border-top:1px solid #e4e9f0}\n@media only screen and (max-width:620px){.shell{width:100%!important}.section{padding-left:18px!important;padding-right:18px!important}.mobile-block{display:block!important;width:100%!important}.mobile-center{text-align:center!important}.hide-mobile{display:none!important}}\n</style>`;\n\nconst header = (title, subtitle) => `<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">${commonStyle}</head><body><table role=\"presentation\" width=\"100%\" style=\"width:100%;background:#eef2f6\"><tr><td align=\"center\" style=\"padding:24px 10px\"><table role=\"presentation\" class=\"shell\" width=\"760\" style=\"width:760px;max-width:760px;background:#fff;border-top:7px solid #0aa9b8;border-radius:14px 14px 0 0\"><tr><td style=\"background:#0f1d31;padding:32px 34px 30px\"><table role=\"presentation\" width=\"100%\"><tr><td width=\"56\" valign=\"top\"><div style=\"width:46px;height:46px;line-height:46px;background:#0aa9b8;border-radius:12px;color:#fff;text-align:center;font-size:17px;font-weight:700\">WS</div></td><td valign=\"top\"><div style=\"color:#91a2b9;font-size:11px;line-height:16px;letter-spacing:1.3px;text-transform:uppercase\">Wazuh Security Intelligence</div><div style=\"color:#fff;font-size:27px;line-height:34px;font-weight:700;padding-top:4px\">${esc(title)}</div><div style=\"color:#c5cfdd;font-size:12px;line-height:18px;padding-top:8px\">${esc(config.organizationName)} &nbsp;&bull;&nbsp; ${esc(subtitle)}</div></td><td width=\"178\" valign=\"top\" align=\"right\" class=\"hide-mobile\"><span style=\"display:inline-block;color:${attention.color};background:${attention.background};border:1px solid ${attention.border};border-radius:18px;padding:7px 12px;font-size:10px;line-height:14px;font-weight:700\">${attention.label}</span></td></tr></table></td></tr>`;\n\nconst close = (message) => `<tr><td class=\"footer\">${esc(message)}<br>Reporting window: ${esc(context.periodStart)} to ${esc(context.periodEnd)} &nbsp;&bull;&nbsp; Generated ${esc(generatedAt)}</td></tr></table></td></tr></table></body></html>`;\n\nconst activityHero = `<tr><td style=\"padding:26px 34px 20px\"><table role=\"presentation\" width=\"100%\" style=\"background:#f4f7fa;border-radius:14px\"><tr><td style=\"padding:22px 24px\"><div style=\"color:#65758d;font-size:11px;line-height:16px;text-transform:uppercase;letter-spacing:.8px;font-weight:700\">Security activity analyzed</div><div style=\"color:#172033;font-size:38px;line-height:44px;font-weight:700\">${context.total.toLocaleString('en-US')}</div></td><td align=\"right\" style=\"padding:22px 24px;color:#65758d;font-size:12px;line-height:18px\">Wazuh alerts<br>across ${context.useCaseCoverage.length} security contexts</td></tr></table></td></tr>`;\n\nconst severityCell = (label, value, color, border = true) => `<td width=\"25%\" align=\"center\" style=\"width:25%;padding:18px 8px;border-right:${border ? '1px solid #dbe3ec' : '0'}\"><div style=\"color:#65758d;font-size:11px;line-height:16px;font-weight:700;text-transform:uppercase\">${label}</div><div style=\"color:${color};font-size:29px;line-height:36px;font-weight:700\">${Number(value || 0).toLocaleString('en-US')}</div></td>`;\nconst severityStrip = `<tr><td style=\"padding:0 34px 30px\"><table role=\"presentation\" width=\"100%\" style=\"border-top:1px solid #e4e9f0;border-bottom:1px solid #e4e9f0\"><tr>${severityCell('Critical', counts.critical, '#b42318')}${severityCell('High', counts.high, '#d9480f')}${severityCell('Medium', counts.medium, '#c17b00')}${severityCell('Low', counts.low, '#008577', false)}</tr></table></td></tr>`;\n\nconst sectionTitle = (title) => `<div class=\"section-title\">${esc(title)}</div><div class=\"section-rule\">&nbsp;</div>`;\nconst callout = (content, tone = 'amber') => {\n  const colors = tone === 'blue'\n    ? { bg: '#eef4ff', border: '#6172d6', text: '#27364f' }\n    : tone === 'red'\n      ? { bg: '#fef3f2', border: '#d92d20', text: '#5c211d' }\n      : { bg: '#fff8e7', border: '#f2b01e', text: '#543b0a' };\n  return `<div style=\"background:${colors.bg};border-left:5px solid ${colors.border};padding:16px 18px;color:${colors.text};border-radius:0 10px 10px 0\">${content}</div>`;\n};\n\nconst coverageBars = context.useCaseCoverage.slice(0, 8).map((item) => {\n  const width = Math.max(2, Math.min(100, Number(item.percentage || 0)));\n  return `<tr><td width=\"34%\" style=\"padding:7px 10px 7px 0;color:#25334a;font-size:12px\">${esc(item.name)}</td><td width=\"54%\" style=\"padding:7px 10px\"><table role=\"presentation\" width=\"100%\" style=\"background:#e8edf3;border-radius:7px\"><tr><td width=\"${width}%\" style=\"width:${width}%;height:10px;background:#0aa9b8;border-radius:7px;font-size:0;line-height:0\">&nbsp;</td><td style=\"font-size:0;line-height:0\">&nbsp;</td></tr></table></td><td width=\"12%\" align=\"right\" style=\"padding:7px 0;color:#25334a;font-size:12px;font-weight:700\">${item.percentage}%</td></tr>`;\n}).join('');\nconst coverageSection = `<tr><td class=\"section\">${sectionTitle('Wazuh use-case coverage')}<div class=\"muted\" style=\"font-size:11px;margin:-10px 0 10px\">Non-exclusive coverage: one alert may support multiple security contexts.</div><table role=\"presentation\" width=\"100%\">${coverageBars || '<tr><td class=\"empty\">No Wazuh use-case context was identified.</td></tr>'}</table></td></tr>`;\n\nconst actionRows = rows(ai.recommendedActions || [], (action) => `<tr><td style=\"font-weight:700;color:#41516a;white-space:nowrap\">${esc(action.priority)}</td><td>${esc(action.action)}</td><td>${esc(action.rationale)}</td></tr>`, 'No recommended action was produced.', 3);\nconst actionsSection = `<tr><td class=\"section\">${sectionTitle('Priority actions')}<table class=\"data-table\"><tr><th>Priority</th><th>Action</th><th>Rationale</th></tr>${actionRows}</table></td></tr>`;\nconst limitationsSection = `<tr><td class=\"section\">${sectionTitle('Evidence limitations')}${list(ai.limitations || [], (item) => `<li>${esc(item)}</li>`)}</td></tr>`;\n\nconst executiveBody = () => `${header('Executive Security Report', 'Leadership view')}${activityHero}${severityStrip}<tr><td class=\"section\">${sectionTitle('Executive risk snapshot')}${callout(`<strong>${pct(counts.critical + counts.high)}% of observed alerts are Critical or High.</strong> ${esc(ai.executiveSummary)}`)}</td></tr><tr><td class=\"section\">${sectionTitle('Risk narrative')}${callout(esc(ai.riskNarrative), 'blue')}</td></tr>${coverageSection}<tr><td class=\"section\"><table role=\"presentation\" width=\"100%\"><tr><td width=\"50%\" valign=\"top\" style=\"padding-right:8px\"><div style=\"border:1px solid #d8e0ea;background:#f7f9fc;padding:16px;min-height:128px\"><div style=\"font-size:15px;font-weight:700;margin-bottom:10px\">Leadership focus</div>${list((ai.recommendedActions || []).slice(0, 3), (action) => `<li>${esc(action.action)}</li>`)}</div></td><td width=\"50%\" valign=\"top\" style=\"padding-left:8px\"><div style=\"border:1px solid #d8e0ea;background:#f7f9fc;padding:16px;min-height:128px\"><div style=\"font-size:15px;font-weight:700;margin-bottom:8px\">MITRE visibility</div><div style=\"font-size:28px;line-height:34px;font-weight:700;color:#6941c6\">${context.threatMitrePercentage}%</div><div class=\"muted\" style=\"font-size:11px\">${context.threatMitreCount.toLocaleString('en-US')} alerts include mapped Threat Detection context. Other Wazuh use cases remain reported separately.</div></div></td></tr></table></td></tr>${actionsSection}${limitationsSection}${close('Wazuh metrics remain authoritative. Gemini enriches narrative and recommendations only; analyst validation is required before response, suppression, or tuning.')}`;\n\nconst topRulesSection = `<tr><td class=\"section\">${sectionTitle('Top Wazuh rules across all use cases')}<table cl

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow queries Wazuh Indexer on a schedule or manually, summarizes alert activity, uses Google Gemini to generate structured security insights, and sends role-based HTML email reports or immediate alert notifications via SMTP, optionally attaching PDFs rendered by…

Source: https://n8n.io/workflows/18167/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This n8n workflow automates the entire lead nurturing process from initial contact through a 3-email follow-up sequence, with intelligent reply detection and personalized AI-generated content. It's de

Telegram Trigger, Chain Llm, Google Gemini Chat +5
AI & RAG

Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 91 nodes.

Form Trigger, Chain Llm, Output Parser Structured +8
AI & RAG

Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 87 nodes.

Form Trigger, Chain Llm, Output Parser Structured +7
AI & RAG

Host Your Own AI Deep Research Agent with n8n, Apify and OpenAI. Uses outputParserStructured, lmChatOpenAi, formTrigger, chainLlm. Event-driven trigger; 87 nodes.

Output Parser Structured, OpenAI Chat, Form Trigger +7
AI & RAG

This template attempts to replicate OpenAI's DeepResearch feature which, at time of writing, is only available to their pro subscribers.

Output Parser Structured, OpenAI Chat, Form Trigger +8