DEV Community

Alex Kane
Alex Kane

Posted on

n8n for CleanTech/Energy SaaS Vendors: 5 Automations for NERC CIP, FERC, EPA RCRA, and SEC Climate Compliance (Free Workflow JSON)

If you run a SaaS platform for clean energy, grid management, EV charging, or sustainability reporting, your customers operate under some of the most complex regulatory frameworks in any industry.

NERC CIP critical infrastructure protection. FERC order compliance. EPA RCRA hazardous waste rules for battery storage. The SEC's new climate disclosure rule. EU CSRD Article 19a. And ERCOT settlement reporting for Texas grid participants — all on top of standard SOC2/ISO27001.

Many CleanTech SaaS vendors use Zapier or Make to glue customer success, compliance alerts, and ops reporting together. That creates a NERC CIP-011 problem.

NERC CIP-011 governs BES Cyber System Information (BCSI) protection. The moment operational data from a grid-connected system flows through a commercial cloud iPaaS — API keys, topology diagrams, real-time generation setpoints, asset configurations — you may have expanded your customers' Electronic Security Perimeter in ways neither party intended. Self-hosted n8n in an on-prem or private-cloud environment keeps BCSI inside the authorized boundary.

Here are 5 n8n automation workflows for CleanTech and Energy SaaS vendors — all with import-ready JSON.


1. Energy SaaS Customer Onboarding Drip

The problem: A utility-scale renewable customer and an EV charging network customer have completely different compliance requirements. A generic onboarding sequence fails both.

The workflow:

  • Trigger: Google Sheets new customer row (or CRM webhook)
  • Detect customer tier: UTILITY_SCALE_RENEWABLE / INDEPENDENT_POWER_PRODUCER / ELECTRIC_COOPERATIVE / TRANSMISSION_SYSTEM_OPERATOR / EV_CHARGING_NETWORK / ENERGY_STORAGE_OPERATOR
  • Detect compliance flags: NERC_CIP_APPLICABLE / FERC_ORDER_APPLICABLE / EPA_RCRA_APPLICABLE / DOE_CYBERSECURITY_REQUIRED / SEC_CLIMATE_DISCLOSURE / EU_CSRD_APPLICABLE / ERCOT_SETTLEMENT_REQUIRED
  • Day 0: Tier- and flag-specific welcome — CIP-007 system security config guide for NERC customers; FERC Form 1 timeline for FERC customers; EPA RCRA waste manifest setup for battery storage; SEC Scope 1/2 emissions pipeline for public companies
  • Day 3: Compliance configuration tips — immutable audit logging (CIP-007 R5 log retention 90d minimum); FERC Form 1 annual reminders; SEC Climate Rule Scope 1/2 data pipeline
  • Day 7: Advanced features by tier — grid anomaly detection, automated compliance report export, one-click CSRD data export

Import-ready workflow JSON:

{
  "nodes": [
    {
      "id": "1",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "2",
      "name": "Get Customer List",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "customers"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "3",
      "name": "Set Onboarding Variables",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const customers = $input.all();\nconst today = new Date();\nconst results = [];\nfor (const item of customers) {\n  const c = item.json;\n  const signupDate = new Date(c.signup_date);\n  const daysSinceSignup = Math.floor((today - signupDate) / 86400000);\n  const tier = c.tier || 'UTILITY_SCALE_RENEWABLE';\n  const flags = { nerc_cip: c.nerc_cip_applicable === 'true', ferc_order: c.ferc_order_applicable === 'true', epa_rcra: c.epa_rcra_applicable === 'true', doe_cyber: c.doe_cybersecurity_required === 'true', sec_climate: c.sec_climate_disclosure === 'true', eu_csrd: c.eu_csrd_applicable === 'true', ercot: c.ercot_settlement === 'true' };\n  let emailType = null;\n  if (daysSinceSignup === 0) emailType = 'DAY0_WELCOME';\n  else if (daysSinceSignup === 3) emailType = 'DAY3_COMPLIANCE';\n  else if (daysSinceSignup === 7) emailType = 'DAY7_ADVANCED';\n  if (emailType) results.push({ json: { ...c, emailType, tier, flags, daysSinceSignup } });\n}\nreturn results;"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "4",
      "name": "Route by Email Type",
      "type": "n8n-nodes-base.switch",
      "parameters": {
        "dataType": "string",
        "value1": "={{ $json.emailType }}",
        "rules": {
          "rules": [
            {
              "value2": "DAY0_WELCOME",
              "output": 0
            },
            {
              "value2": "DAY3_COMPLIANCE",
              "output": 1
            },
            {
              "value2": "DAY7_ADVANCED",
              "output": 2
            }
          ]
        }
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "5",
      "name": "Send Day 0 Welcome",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.email }}",
        "subject": "Welcome \u2014 your NERC CIP & FERC compliance setup guide",
        "message": "={{ 'Hi ' + $json.name + ', welcome to [Platform]! Your Day 0 compliance setup guide is ready based on your tier: ' + $json.tier }}"
      },
      "position": [
        1050,
        200
      ]
    },
    {
      "id": "6",
      "name": "Send Day 3 Tips",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.email }}",
        "subject": "Day 3: NERC CIP audit trail and FERC reporting tips",
        "message": "={{ 'Hi ' + $json.name + ', Day 3 compliance configuration tips for your environment.' }}"
      },
      "position": [
        1050,
        350
      ]
    },
    {
      "id": "7",
      "name": "Send Day 7 Advanced",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.email }}",
        "subject": "Day 7: Advanced features \u2014 grid anomaly detection and compliance reports",
        "message": "={{ 'Hi ' + $json.name + ', you are 1 week in. Here are advanced features for tier: ' + $json.tier }}"
      },
      "position": [
        1050,
        500
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Get Customer List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Customer List": {
      "main": [
        [
          {
            "node": "Set Onboarding Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Onboarding Variables": {
      "main": [
        [
          {
            "node": "Route by Email Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Email Type": {
      "main": [
        [
          {
            "node": "Send Day 0 Welcome",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Day 3 Tips",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Day 7 Advanced",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Grid & Energy API Health Monitor with NERC CIP-008 Annotation

The problem: When your grid monitoring API or FERC reporting endpoint goes down, it's not just an SLA issue. For customers subject to NERC CIP-008, an extended outage affecting cyber assets may trigger incident reporting obligations. Your ops team needs to know the regulatory risk of every downtime event.

The workflow:

  • Trigger: Every 5 minutes
  • Pull endpoint list from Google Sheets (name, URL, regulation, criticality tier)
  • HTTP check each endpoint — flag non-200 or response time > 3s
  • Use $getWorkflowStaticData for UP→DOWN state transitions (alert once, not every 5 minutes)
  • If DOWN and criticality = NERC_CIP_HIGH: include CIP-008 R1 incident reporting criteria in alert
  • Route: Slack #grid-ops + Google Sheets SLA log

Sample endpoint table:
| Endpoint | Regulation | Criticality |
|---|---|---|
| grid_monitoring_api | NERC CIP-007 | NERC_CIP_HIGH |
| ferc_reporting_api | FERC Order 848 | FERC_CRITICAL |
| emissions_data_api | SEC Climate Rule | SEC_MATERIAL |
| epa_rcra_tracking_api | EPA RCRA §263.30 | EPA_REGULATED |
| ercot_settlement_api | ERCOT Protocols | ERCOT_SETTLEMENT |

Import-ready workflow JSON:

{
  "nodes": [
    {
      "id": "1",
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "2",
      "name": "Get Endpoints",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "endpoints"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "3",
      "name": "HTTP Check",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "={{ $json.url }}",
        "method": "GET",
        "timeout": 10000,
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "4",
      "name": "Evaluate Status",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const results = [];\nfor (const item of $input.all()) {\n  const ep = item.json;\n  const status = ep.$response?.statusCode || 0;\n  const latency = ep.$response?.responseTime || 9999;\n  const prevState = $getWorkflowStaticData('node')[ep.name] || 'UP';\n  let newState = 'UP';\n  if (status !== 200 || latency > 5000) newState = 'DOWN';\n  else if (latency > 3000) newState = 'DEGRADED';\n  const stateChanged = prevState !== newState;\n  $getWorkflowStaticData('node')[ep.name] = newState;\n  if (stateChanged || newState !== 'UP') {\n    results.push({ json: { name: ep.name, url: ep.url, status, latency, newState, prevState, stateChanged, regulation: ep.regulation, criticality: ep.criticality } });\n  }\n}\nreturn results.length > 0 ? results : [{ json: { skip: true } }];"
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "5",
      "name": "Skip if OK",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.skip }}",
              "value2": true
            }
          ]
        }
      },
      "position": [
        1050,
        300
      ]
    },
    {
      "id": "6",
      "name": "Alert Slack",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#grid-ops",
        "text": "={{ '[' + $json.newState + '] ' + $json.name + ' \u2014 ' + $json.latency + 'ms | Regulation: ' + $json.regulation + ($json.criticality === 'NERC_CIP_HIGH' && $json.newState === 'DOWN' ? ' | NERC CIP-008 R1 incident criteria \u2014 check if reporting required' : '') }}"
      },
      "position": [
        1250,
        200
      ]
    },
    {
      "id": "7",
      "name": "Log SLA",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendRow",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "sla_log",
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "timestamp": "={{ new Date().toISOString() }}",
            "endpoint": "={{ $json.name }}",
            "state": "={{ $json.newState }}",
            "latency_ms": "={{ $json.latency }}",
            "regulation": "={{ $json.regulation }}"
          }
        }
      },
      "position": [
        1250,
        400
      ]
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Get Endpoints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Endpoints": {
      "main": [
        [
          {
            "node": "HTTP Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Check": {
      "main": [
        [
          {
            "node": "Evaluate Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Status": {
      "main": [
        [
          {
            "node": "Skip if OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip if OK": {
      "main": [
        [],
        [
          {
            "node": "Alert Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log SLA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. NERC CIP / FERC / EPA RCRA Compliance Deadline Tracker

The problem: CleanTech SaaS vendors need to track customers' compliance deadlines to drive retention and renewal conversations. Missing a NERC CIP annual assessment or an EPA RCRA hazardous waste manifest deadline is a churnable event.

The workflow:

  • Trigger: Daily at 8 AM weekdays
  • Pull compliance deadlines sheet — 12 deadline types:
    • NERC_CIP_007_ANNUAL_ASSESSMENT — System Security Management review
    • NERC_CIP_014_PHYSICAL_SECURITY_REVIEW — Annual
    • NERC_CIP_008_INCIDENT_RESPONSE_TEST — Annual IR exercise (CIP-008 R2)
    • FERC_FORM_1_ANNUAL_FILING — Major utilities
    • FERC_FORM_714_ANNUAL — Transmission planning
    • EPA_RCRA_HAZARDOUS_WASTE_MANIFEST — Large quantity generator 90-day storage limit
    • EPA_RCRA_BIENNIAL_REPORT — Even calendar years
    • DOE_C2M2_SELF_EVALUATION_ANNUAL — DOE Cybersecurity Maturity Model
    • SEC_CLIMATE_DISCLOSURE_10K — Annual Scope 1/2 emissions
    • EU_CSRD_ART19A_ANNUAL — CSRD sustainability report
    • SOC2_TYPE2_RENEWAL — Annual
    • ANNUAL_PENETRATION_TEST — Annual
  • Classify: OVERDUE / CRITICAL (≤14d) / URGENT (≤30d) / WARNING (≤60d) / NOTICE (≤90d)
  • Dedup on alert_sent_date — one alert per deadline per day
  • Route: OVERDUE → Slack #compliance-critical + email; CRITICAL → owner email; URGENT/WARNING → Slack #compliance

Import-ready workflow JSON:

{
  "nodes": [
    {
      "id": "1",
      "name": "Daily 8AM Weekdays",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "2",
      "name": "Get Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "readRows",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "compliance_deadlines"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const today = new Date();\nconst results = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  if (!d.due_date || !d.owner_email) continue;\n  const due = new Date(d.due_date);\n  const daysLeft = Math.floor((due - today) / 86400000);\n  let urgency = null;\n  if (daysLeft < 0) urgency = 'OVERDUE';\n  else if (daysLeft <= 14) urgency = 'CRITICAL';\n  else if (daysLeft <= 30) urgency = 'URGENT';\n  else if (daysLeft <= 60) urgency = 'WARNING';\n  else if (daysLeft <= 90) urgency = 'NOTICE';\n  const lastAlert = d.alert_sent_date ? new Date(d.alert_sent_date) : null;\n  const alertedToday = lastAlert && (today - lastAlert) < 86400000;\n  if (urgency && !alertedToday) {\n    results.push({ json: { ...d, urgency, daysLeft } });\n  }\n}\nreturn results.length > 0 ? results : [{ json: { skip: true } }];"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "4",
      "name": "Route by Urgency",
      "type": "n8n-nodes-base.switch",
      "parameters": {
        "dataType": "string",
        "value1": "={{ $json.urgency }}",
        "rules": {
          "rules": [
            {
              "value2": "OVERDUE",
              "output": 0
            },
            {
              "value2": "CRITICAL",
              "output": 1
            },
            {
              "value2": "URGENT",
              "output": 2
            },
            {
              "value2": "WARNING",
              "output": 3
            }
          ]
        }
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "5",
      "name": "Slack OVERDUE",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-critical",
        "text": "={{ '[OVERDUE] ' + $json.deadline_type + ' \u2014 Regulation: ' + $json.regulation + ' | Owner: ' + $json.owner_name + ' | Penalty risk: ' + ($json.penalty_risk || 'Significant') }}"
      },
      "position": [
        1050,
        150
      ]
    },
    {
      "id": "6",
      "name": "Email CRITICAL",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.owner_email }}",
        "subject": "={{ 'CRITICAL \u2014 ' + $json.deadline_type + ' due in ' + $json.daysLeft + ' days (' + $json.regulation + ')' }}",
        "message": "={{ 'CRITICAL compliance deadline: ' + $json.deadline_type + '\\nDue: ' + $json.due_date + '\\nDays remaining: ' + $json.daysLeft + '\\nRegulation: ' + $json.regulation }}"
      },
      "position": [
        1050,
        300
      ]
    },
    {
      "id": "7",
      "name": "Slack URGENT",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance",
        "text": "={{ '[URGENT] ' + $json.deadline_type + ' \u2014 ' + $json.daysLeft + ' days | ' + $json.regulation + ' | Owner: ' + $json.owner_name }}"
      },
      "position": [
        1050,
        450
      ]
    },
    {
      "id": "8",
      "name": "Slack WARNING",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance",
        "text": "={{ '[WARNING] ' + $json.deadline_type + ' \u2014 ' + $json.daysLeft + ' days | Owner: ' + $json.owner_name }}"
      },
      "position": [
        1050,
        600
      ]
    }
  ],
  "connections": {
    "Daily 8AM Weekdays": {
      "main": [
        [
          {
            "node": "Get Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Deadlines": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "Route by Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Urgency": {
      "main": [
        [
          {
            "node": "Slack OVERDUE",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email CRITICAL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack URGENT",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack WARNING",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Energy Security Incident Pipeline with NERC CIP-008 & FERC Clocks

The problem: When a grid cyber incident, EPA release, or DOE cybersecurity event hits your customer's environment, multiple regulatory clocks start simultaneously. NERC CIP-008 R1 requires reporting to E-ISAC and NERC within 1 hour. FERC Order 848 has its own notification path. Your incident pipeline needs to apply the right clock to the right customer.

The workflow:

  • Trigger: Webhook (POST /energy-incident from SIEM, monitoring platform, or manual entry)
  • Immediate 200 ACK with incident ID, regulation, and response deadline hours
  • Classify by 8 incident types with specific regulatory clocks:
    • NERC_CIP_008_CYBER_INCIDENT — 1 hour (E-ISAC + NERC notification, CIP-008 R1)
    • FERC_CRITICAL_INFRASTRUCTURE_BREACH — 1 hour (NERC OE-417 electric emergency report)
    • NERC_EOP_005_VIOLATION — 4 hours (NERC compliance notification)
    • EPA_RCRA_RELEASE — 24 hours (§263.30 containment + EPA notification for battery storage)
    • DOE_CYBER_INCIDENT — 1 hour (DOE CESER reporting, C2M2 obligation)
    • GRID_DISTURBANCE_REPORTABLE — 1 hour (NERC EOP-004 + DOE OE-417 form)
    • SEC_CLIMATE_MATERIAL_RISK — 96 hours (SEC 8-K or 10-K material climate risk)
    • EU_CSRD_BREACH — 72 hours (Auditor notification, CSRD Art.19a)
  • Alert Slack channel + log to Postgres energy_incidents table

Import-ready workflow JSON:

{
  "nodes": [
    {
      "id": "1",
      "name": "Webhook Intake",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "path": "energy-incident",
        "responseMode": "responseNode"
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "2",
      "name": "ACK 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ received: true, incident_id: $json.incident_id }) }}"
      },
      "position": [
        450,
        200
      ]
    },
    {
      "id": "3",
      "name": "Classify Incident",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const inc = $input.first().json;\nconst clockMap = {\n  'NERC_CIP_008_CYBER_INCIDENT': { hours: 1, regulation: 'NERC CIP-008 R1 \u2014 E-ISAC + NERC within 1h', channel: '#sec-critical' },\n  'FERC_CRITICAL_INFRASTRUCTURE_BREACH': { hours: 1, regulation: 'FERC Order 848 \u2014 NERC OE-417 report', channel: '#compliance-critical' },\n  'NERC_EOP_005_VIOLATION': { hours: 4, regulation: 'NERC EOP-005 \u2014 compliance notification', channel: '#compliance' },\n  'EPA_RCRA_RELEASE': { hours: 24, regulation: 'EPA RCRA \u00a7263.30 \u2014 containment + EPA notification (battery storage)', channel: '#safety' },\n  'DOE_CYBER_INCIDENT': { hours: 1, regulation: 'DOE C2M2 \u2014 CESER report within 1h', channel: '#sec-critical' },\n  'GRID_DISTURBANCE_REPORTABLE': { hours: 1, regulation: 'NERC EOP-004 + DOE OE-417', channel: '#grid-ops' },\n  'SEC_CLIMATE_MATERIAL_RISK': { hours: 96, regulation: 'SEC 17 CFR Part 210 \u2014 8-K or 10-K material climate risk', channel: '#compliance' },\n  'EU_CSRD_BREACH': { hours: 72, regulation: 'EU CSRD Art.19a \u2014 auditor notification', channel: '#compliance' }\n};\nconst meta = clockMap[inc.incident_type] || { hours: 72, regulation: 'See compliance calendar', channel: '#compliance' };\nconst deadline = new Date(Date.now() + meta.hours * 3600000).toISOString();\nreturn [{ json: { ...inc, ...meta, response_deadline: deadline } }];"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "4",
      "name": "Alert Slack",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "={{ $json.channel }}",
        "text": "={{ '[ENERGY INCIDENT] ' + $json.incident_type + ' \u2014 Customer: ' + $json.customer_name + '\\nRegulation + clock: ' + $json.regulation + '\\nResponse deadline: ' + $json.response_deadline }}"
      },
      "position": [
        850,
        200
      ]
    },
    {
      "id": "5",
      "name": "Log Incident",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "insert",
        "table": "energy_incidents",
        "columns": "incident_id,incident_type,customer_name,regulation,response_deadline,received_at"
      },
      "position": [
        850,
        400
      ]
    }
  ],
  "connections": {
    "Webhook Intake": {
      "main": [
        [
          {
            "node": "ACK 200",
            "type": "main",
            "index": 0
          },
          {
            "node": "Classify Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident": {
      "main": [
        [
          {
            "node": "Alert Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Weekly CleanTech SaaS KPI Dashboard

The problem: Your CEO wants MRR by customer tier. Your CISO wants to know about NERC CIP incidents this week. Your CFO wants to see open compliance deadlines that could affect renewals. One Monday morning email, three stakeholders covered.

The workflow:

  • Trigger: Monday at 8 AM
  • Query 1: Total MRR by tier (UTILITY_SCALE_RENEWABLE / IPP / EV_CHARGING_NETWORK), new ARR WoW, total customers
  • Query 2: NERC CIP incidents this week, FERC incidents, open critical deadlines, EPA releases
  • Build HTML table with $getWorkflowStaticData for WoW % comparison
  • Subject-line compliance flags (if any events occurred):
    • [NERC CIP INCIDENT THIS WEEK: N]
    • [FERC INCIDENT THIS WEEK: N]
    • [CRITICAL DEADLINE OPEN: N]
    • [EPA RCRA RELEASE THIS WEEK: N]
  • Email: CEO → BCC CISO + Compliance
  • Slack: One-liner to #go-to-market

Import-ready workflow JSON:

{
  "nodes": [
    {
      "id": "1",
      "name": "Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "2",
      "name": "Get Platform Metrics",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(*) as total_customers, SUM(CASE WHEN tier = 'UTILITY_SCALE_RENEWABLE' THEN mrr_usd ELSE 0 END) as mrr_utility, SUM(CASE WHEN tier = 'INDEPENDENT_POWER_PRODUCER' THEN mrr_usd ELSE 0 END) as mrr_ipp, SUM(CASE WHEN tier = 'EV_CHARGING_NETWORK' THEN mrr_usd ELSE 0 END) as mrr_ev, SUM(mrr_usd) as total_mrr FROM customers WHERE status = 'active'"
      },
      "position": [
        450,
        200
      ]
    },
    {
      "id": "3",
      "name": "Get Compliance Events",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(CASE WHEN incident_type LIKE 'NERC%' AND created_at >= NOW() - INTERVAL '7 days' THEN 1 END) as nerc_incidents_week, COUNT(CASE WHEN incident_type LIKE 'FERC%' AND created_at >= NOW() - INTERVAL '7 days' THEN 1 END) as ferc_incidents_week, COUNT(CASE WHEN urgency IN ('OVERDUE','CRITICAL') AND resolved_at IS NULL THEN 1 END) as open_critical_deadlines, COUNT(CASE WHEN incident_type = 'EPA_RCRA_RELEASE' AND created_at >= NOW() - INTERVAL '7 days' THEN 1 END) as epa_releases_week FROM energy_incidents"
      },
      "position": [
        450,
        400
      ]
    },
    {
      "id": "4",
      "name": "Build HTML Report",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const metrics = $('Get Platform Metrics').first().json;\nconst compliance = $('Get Compliance Events').first().json;\nconst prev = $getWorkflowStaticData('node');\nconst mrrWoW = prev.last_mrr ? (((metrics.total_mrr - prev.last_mrr) / prev.last_mrr) * 100).toFixed(1) + '%' : 'N/A';\nprev.last_mrr = metrics.total_mrr;\nconst flagList = [compliance.nerc_incidents_week > 0 ? '[NERC CIP INCIDENT: ' + compliance.nerc_incidents_week + ']' : '', compliance.open_critical_deadlines > 0 ? '[CRITICAL DEADLINE OPEN: ' + compliance.open_critical_deadlines + ']' : '', compliance.epa_releases_week > 0 ? '[EPA RCRA RELEASE: ' + compliance.epa_releases_week + ']' : ''].filter(Boolean);\nconst subject = '[CLEANTECH KPI ' + new Date().toISOString().split('T')[0] + ']' + (flagList.length > 0 ? ' ' + flagList.join(' ') : '');\nconst html = '<h2>CleanTech KPI</h2><table border=\"1\"><tr><th>Metric</th><th>Value</th></tr><tr><td>Total MRR</td><td>$' + metrics.total_mrr + '</td></tr><tr><td>MRR WoW</td><td>' + mrrWoW + '</td></tr><tr><td>NERC CIP Incidents</td><td>' + compliance.nerc_incidents_week + '</td></tr><tr><td>Open Critical Deadlines</td><td>' + compliance.open_critical_deadlines + '</td></tr></table>';\nreturn [{ json: { subject, html } }];"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "5",
      "name": "Email CEO",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "ceo@company.com",
        "bcc": "ciso@company.com",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.html }}"
      },
      "position": [
        850,
        300
      ]
    }
  ],
  "connections": {
    "Monday 8AM": {
      "main": [
        [
          {
            "node": "Get Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Compliance Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Platform Metrics": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Compliance Events": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HTML Report": {
      "main": [
        [
          {
            "node": "Email CEO",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why self-hosted n8n for CleanTech/Energy SaaS

Regulation Cloud iPaaS risk Self-hosted n8n advantage
NERC CIP-011 (BCSI protection) Grid operational data in commercial cloud = potential Electronic Security Perimeter expansion Self-hosted in on-prem/private cloud keeps BCSI inside authorized ESP
NERC CIP-007 (System Security Mgmt) API credentials and system configs transiting third-party cloud = CIP-007 R2 access management finding Access credentials stay within the CIP environment
FERC Order 848 (Infrastructure protection) Grid topology data in third-party hands = additional critical infrastructure exposure Data remains within the utility's own environment
EPA RCRA (Hazardous waste tracking) Waste manifest and facility data in third-party SaaS = chain-of-custody questions Complete audit trail within operator's own systems
EU CSRD Art.19a (Sustainability reporting) Emissions data accuracy attestation requires auditable pipeline Git-versioned n8n workflows are auditable by sustainability auditors

Ready to implement?

All 5 workflows are available as import-ready n8n JSON in the FlowKit CleanTech & Energy SaaS Bundle — pre-built templates for regulated verticals, documentation, and setup guides.

Individual templates and the full compliance bundle: stripeai.gumroad.com


Built with n8n — open-source workflow automation. Self-host for full data control.

Top comments (0)