DEV Community

Alex Kane
Alex Kane

Posted on

n8n for CleanTech & Energy SaaS: 5 Automations That Scale Renewable Ops and Keep Energy Data Compliant (Free Workflow JSON)

If you're building software for the energy transition — utility analytics platforms, solar/wind project management SaaS, EV charging network software, carbon accounting tools, or energy trading platforms — you already know that your data is subject to compliance requirements most cloud automation vendors have never heard of.

NERC CIP. FERC confidentiality orders. ISO-RTO settlement data agreements. Carbon registry integrity requirements. The moment a utility or grid operator discovers that their BES (Bulk Electric System) data is transiting a commercial cloud automation platform, you have a NERC CIP-014 problem — or worse, a CIP-011 violation.

n8n's self-hosted architecture eliminates this entirely. Energy workflows run inside the operator's network perimeter — never touching Zapier or Make's multi-tenant cloud. And at the telemetry volumes that energy platforms generate (thousands of meter readings per hour), the economics of per-task pricing make Zapier prohibitively expensive.

Here are 5 real automations CleanTech and Energy SaaS teams build with n8n — with full import-ready workflow JSON for each.


Why Energy SaaS Teams Are Moving to n8n

Challenge Zapier / Make n8n (self-hosted)
NERC CIP-011 BES data Off-network routing Runs inside operator VPC
FERC confidentiality orders Third-party disclosure risk Never leaves authorized network
ISO-RTO settlement data Data sovereignty violations On-premises or VPC deployment
Carbon registry integrity Third-party attestation gaps Immutable git-versioned audit trail
IoT telemetry volume Per-task cost: $3K+/mo at scale Flat VPS cost: $30/mo
Workflow audit trail 30-day log limit Permanent, versioned execution log

1. New Energy Project Client Onboarding Drip

Use case: When a new utility, solar developer, or EV fleet operator signs up for your platform, automatically tier them, send the right API credentials and setup materials, assign a CSM, and run a multi-day onboarding sequence.

{
  "name": "Energy SaaS Client Onboarding Drip",
  "nodes": [
    {"name": "New Client Trigger", "type": "n8n-nodes-base.googleSheetsTrigger", "parameters": {"sheetId": "YOUR_SHEET_ID", "range": "Clients!A:H", "event": "rowAdded"}},
    {"name": "Classify Client Tier", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const c = $json; const type = (c.client_type || '').toUpperCase(); let tier = 'RESIDENTIAL_INSTALLER'; if (type.includes('UTILITY') || type.includes('ISO') || type.includes('RTO')) tier = 'UTILITY_ENTERPRISE'; else if (type.includes('COMMERCIAL') || type.includes('C&I') || type.includes('DEVELOPER')) tier = 'COMMERCIAL_CI'; else if (type.includes('EV') || type.includes('FLEET') || type.includes('CHARGING')) tier = 'EV_FLEET'; return [{json: {...c, tier, onboarding_started: new Date().toISOString()}}];"}},
    {"name": "Send Day 0 Welcome", "type": "n8n-nodes-base.gmail", "parameters": {"toList": "={{$json.contact_email}}", "subject": "Welcome to the platform — your API credentials inside", "message": "<p>Hi {{$json.contact_name}},</p><p>Your account is live. API key: <b>{{$json.api_key}}</b>. Docs: <a href='https://docs.yourplatform.com'>docs.yourplatform.com</a></p><p>Your CSM will be in touch within 1 business day.</p>"}},
    {"name": "Slack CSM DM", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#cs-onboarding", "text": "New {{$json.tier}} client: {{$json.company_name}} ({{$json.contact_email}}). CSM: {{$json.csm_owner}}. Sites: {{$json.site_count}}."}},
    {"name": "Log Onboarding", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "appendOrUpdate", "sheetId": "YOUR_SHEET_ID", "range": "Onboarding!A:D", "values": [{"row": ["={{$json.client_id}}", "={{$json.tier}}", "DAY_0_SENT", "={{$json.onboarding_started}}"]}]}},
    {"name": "Wait 3 Days", "type": "n8n-nodes-base.wait", "parameters": {"amount": 3, "unit": "days"}},
    {"name": "Day 3 Check-In", "type": "n8n-nodes-base.gmail", "parameters": {"toList": "={{$json.contact_email}}", "subject": "Day 3 check-in — have you connected your first site?", "message": "<p>Hi {{$json.contact_name}},</p><p>Quick check — have you connected your first monitored site? If you hit any issues with the API integration, reply here and your CSM will jump in.</p>"}},
    {"name": "Wait 4 More Days", "type": "n8n-nodes-base.wait", "parameters": {"amount": 4, "unit": "days"}},
    {"name": "Day 7 Feature Guide", "type": "n8n-nodes-base.gmail", "parameters": {"toList": "={{$json.contact_email}}", "subject": "Your first weekly report is ready", "message": "<p>Hi {{$json.contact_name}},</p><p>You've completed your first week on the platform. Here's how to set up your automated weekly performance report: <a href='https://docs.yourplatform.com/reports'>docs.yourplatform.com/reports</a></p>"}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

What it does: SheetsTrigger detects new client row → classifies tier (UTILITY_ENTERPRISE/COMMERCIAL_CI/EV_FLEET/RESIDENTIAL_INSTALLER) → sends API credentials and welcome email → DMs CSM on Slack → logs to onboarding tracker → runs Day 3 check-in and Day 7 feature guide via Wait nodes.


2. Renewable Asset Health Monitor

Use case: Your platform monitors solar arrays, wind turbines, or EV charging stations. Poll each asset's monitoring endpoint every few minutes and alert ops the moment one goes down or degrades.

{
  "name": "Renewable Asset Health Monitor",
  "nodes": [
    {"name": "Every 5 Minutes", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "*/5 * * * *"}]}}},
    {"name": "Load Asset List", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "read", "spreadsheetId": "YOUR_SHEET_ID", "range": "Assets!A:D"}},
    {"name": "Ping Asset Endpoints", "type": "n8n-nodes-base.httpRequest", "parameters": {"url": "={{$json.monitoring_url}}", "method": "GET", "timeout": 5000, "continueOnFail": true}},
    {"name": "Classify Status", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "return $input.all().map(item => { const r = item.json; const isDown = !!r.error; const isStale = !isDown && (Date.now() - new Date(r.last_reading_at || 0).getTime() > 15 * 60 * 1000); const isDegraded = !isDown && !isStale && r.output_kw < r.expected_kw * 0.7; let status = 'OK'; if (isDown) status = 'DOWN'; else if (isStale) status = 'STALE_DATA'; else if (isDegraded) status = 'DEGRADED'; return {...item, json: {...r, asset_status: status, checked_at: new Date().toISOString()}}; }).filter(i => i.json.asset_status !== 'OK');"}},
    {"name": "Dedup Alerts", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const store = $getWorkflowStaticData('global'); const now = Date.now(); return $input.all().filter(item => { const key = item.json.asset_id; const last = store[key] || 0; if (now - last > 30 * 60 * 1000) { store[key] = now; return true; } return false; });"}},
    {"name": "Slack Alert", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#asset-ops", "text": "={{$json.asset_status}} — {{$json.asset_name}} ({{$json.site_location}}) | Output: {{$json.output_kw}}kW vs {{$json.expected_kw}}kW expected | {{$json.checked_at}}"}},
    {"name": "Log Incident", "type": "n8n-nodes-base.postgres", "parameters": {"operation": "insert", "table": "asset_incidents", "columns": "asset_id,asset_name,site_location,asset_status,output_kw,expected_kw,checked_at", "values": "={{$json.asset_id}},={{$json.asset_name}},={{$json.site_location}},={{$json.asset_status}},={{$json.output_kw}},={{$json.expected_kw}},={{$json.checked_at}}"}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

What it does: Every 5 minutes → loads asset list from Sheets → pings each monitoring endpoint → classifies DOWN/STALE_DATA/DEGRADED/OK → deduplicates alerts per asset using $getWorkflowStaticData (30-minute window) → Slack alert to #asset-ops → logs all incidents to Postgres for SLA and warranty tracking.


3. FERC & NERC Regulatory Compliance Deadline Tracker

Use case: Track all regulatory filing deadlines — FERC quarterly reports, NERC CIP certification cycles, ISO-RTO settlement windows, REC attestation deadlines, SOC 2 evidence collection — and escalate as each approaches.

{
  "name": "Energy Regulatory Compliance Tracker",
  "nodes": [
    {"name": "Weekdays 8 AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 8 * * 1-5"}]}}},
    {"name": "Load Compliance Calendar", "type": "n8n-nodes-base.googleSheets", "parameters": {"operation": "read", "spreadsheetId": "YOUR_SHEET_ID", "range": "Compliance!A:F"}},
    {"name": "Classify Urgency", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const actionMap = { 'FERC_FORM_1': 'File FERC Form 1 (Annual Report)', 'FERC_EQR': 'Submit Electric Quarterly Report', 'NERC_CIP_002': 'Complete NERC CIP-002 BES Classification', 'NERC_CIP_014': 'NERC CIP-014 Physical Security Assessment', 'ISO_RTO_SETTLEMENT': 'Close ISO/RTO settlement window', 'REC_ATTESTATION': 'Submit REC attestation to registry', 'CARBON_REGISTRY': 'File carbon credit issuance report', 'SOC2_EVIDENCE': 'Collect SOC 2 audit evidence', 'GDPR_DPA': 'Renew GDPR Data Processing Agreement' }; return $input.all().map(item => { const d = item.json; const days = Math.floor((new Date(d.deadline_date) - Date.now()) / 86400000); let urgency = 'NOTICE'; if (days < 0) urgency = 'OVERDUE'; else if (days <= 3) urgency = 'CRITICAL'; else if (days <= 7) urgency = 'URGENT'; else if (days <= 14) urgency = 'WARNING'; else if (days <= 30) urgency = 'NOTICE'; else return null; const action = actionMap[d.requirement_code] || d.requirement_code; return {...item, json: {...d, days_remaining: days, urgency, action_required: action}}; }).filter(Boolean).filter(i => i.json.urgency !== 'NOTICE');"}},
    {"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}]}, "fallbackOutput": 3}},
    {"name": "Slack Alert", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#compliance-ops", "text": "={{$json.urgency}} — {{$json.requirement_code}} ({{$json.action_required}}): {{$json.days_remaining}} days remaining. Owner: {{$json.compliance_owner}}"}},
    {"name": "Email Compliance Owner", "type": "n8n-nodes-base.gmail", "parameters": {"toList": "={{$json.owner_email}}", "subject": "{{$json.urgency}}: {{$json.requirement_code}} deadline in {{$json.days_remaining}} days", "message": "<p>Regulatory deadline approaching: <b>{{$json.action_required}}</b></p><p>Deadline: {{$json.deadline_date}} ({{$json.days_remaining}} days).</p><p>Jurisdiction: {{$json.jurisdiction}}. Please confirm completion status.</p>"}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

What it does: Runs weekday mornings → loads compliance calendar from Sheets → classifies days remaining into OVERDUE/CRITICAL/URGENT/WARNING tiers → routes by urgency → Slack alert to #compliance-ops → email to compliance owner with action required.


4. Carbon Credit & REC Reporting Pipeline

Use case: Aggregate meter data from your platform, calculate MWh generated and CO2e avoided, compare against committed targets, and generate audit-ready reports for carbon registries or REC tracking systems.

{
  "name": "Carbon Credit & REC Reporting Pipeline",
  "nodes": [
    {"name": "Daily 6 AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 6 * * *"}]}}},
    {"name": "Query Yesterday Meter Readings", "type": "n8n-nodes-base.postgres", "parameters": {"operation": "executeQuery", "query": "SELECT site_id, site_name, asset_type, SUM(energy_kwh) AS total_kwh, SUM(energy_kwh) / 1000 AS total_mwh FROM meter_readings WHERE reading_ts >= NOW() - INTERVAL '1 day' AND reading_ts < NOW() GROUP BY site_id, site_name, asset_type;"}},
    {"name": "Calculate CO2e and RECs", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const CO2E_FACTOR = 0.386; return $input.all().map(item => { const d = item.json; const mwh = parseFloat(d.total_mwh); const co2e_avoided_kg = mwh * CO2E_FACTOR * 1000; const recs_generated = Math.floor(mwh); return {...item, json: {...d, co2e_avoided_kg: co2e_avoided_kg.toFixed(2), recs_generated, report_date: new Date().toISOString().split('T')[0]}}; });"}},
    {"name": "Upsert Carbon Ledger", "type": "n8n-nodes-base.postgres", "parameters": {"operation": "executeQuery", "query": "INSERT INTO carbon_ledger (site_id, report_date, total_mwh, co2e_avoided_kg, recs_generated, asset_type) VALUES ('={{$json.site_id}}', '={{$json.report_date}}', ={{$json.total_mwh}}, ={{$json.co2e_avoided_kg}}, ={{$json.recs_generated}}, '={{$json.asset_type}}') ON CONFLICT (site_id, report_date) DO UPDATE SET total_mwh = EXCLUDED.total_mwh, co2e_avoided_kg = EXCLUDED.co2e_avoided_kg, recs_generated = EXCLUDED.recs_generated;"}},
    {"name": "Check Against Targets", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const totals = $input.all().reduce((acc, i) => { acc.mwh += parseFloat(i.json.total_mwh); acc.co2e += parseFloat(i.json.co2e_avoided_kg); acc.recs += i.json.recs_generated; return acc; }, {mwh: 0, co2e: 0, recs: 0}); const dailyMwhTarget = parseFloat(process.env.DAILY_MWH_TARGET || '1000'); const pct = totals.mwh / dailyMwhTarget * 100; return [{json: {...totals, pct_of_target: pct.toFixed(1), below_target: pct < 80, report_date: new Date().toISOString().split('T')[0]}}];"}},
    {"name": "Alert if Below Target", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#sustainability", "text": "={{$json.below_target ? 'BELOW TARGET' : 'On track'}} — Yesterday: {{$json.mwh.toFixed(1)}} MWh ({{$json.pct_of_target}}% of target) | CO2e avoided: {{$json.co2e.toFixed(0)}} kg | RECs: {{$json.recs}}"}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

What it does: Daily 6 AM → queries yesterday's meter readings from Postgres → calculates CO2e avoided (EPA eGRID emission factor) and RECs generated (1 REC = 1 MWh) → upserts to carbon ledger with ON CONFLICT for idempotency → compares against daily MWh target → Slack alert if below 80% of target.


5. Weekly CleanTech Platform KPI Dashboard

Use case: Every Monday morning, your leadership team receives an HTML email with platform KPIs — active sites, MWh generated, carbon avoided, fleet uptime, new clients — with week-over-week comparisons.

{
  "name": "Weekly CleanTech KPI Dashboard",
  "nodes": [
    {"name": "Monday 8 AM", "type": "n8n-nodes-base.scheduleTrigger", "parameters": {"rule": {"interval": [{"field": "cronExpression", "expression": "0 8 * * 1"}]}}},
    {"name": "Query Platform Metrics", "type": "n8n-nodes-base.postgres", "parameters": {"operation": "executeQuery", "query": "SELECT COUNT(DISTINCT site_id) AS active_sites, SUM(total_mwh) AS total_mwh, SUM(co2e_avoided_kg) AS co2e_kg, SUM(recs_generated) AS recs FROM carbon_ledger WHERE report_date >= NOW() - INTERVAL '7 days';"}},
    {"name": "Query Account Health", "type": "n8n-nodes-base.postgres", "parameters": {"operation": "executeQuery", "query": "SELECT COUNT(*) AS total_clients, COUNT(*) FILTER (WHERE status = 'ACTIVE') AS active_clients, COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '7 days') AS new_clients, COUNT(*) FILTER (WHERE last_api_call < NOW() - INTERVAL '7 days') AS at_risk_clients FROM clients;"}},
    {"name": "Build HTML Report", "type": "n8n-nodes-base.code", "parameters": {"jsCode": "const store = $getWorkflowStaticData('global'); const p = $('Query Platform Metrics').first().json; const a = $('Query Account Health').first().json; const prevMwh = store.prev_mwh || 0; const wowMwh = prevMwh > 0 ? ((p.total_mwh - prevMwh) / prevMwh * 100).toFixed(1) : 'N/A'; store.prev_mwh = parseFloat(p.total_mwh); const co2Tonnes = (parseFloat(p.co2e_kg) / 1000).toFixed(1); const html = `<h2>CleanTech Platform — Weekly KPIs</h2><table border='1' cellpadding='6'><tr><th>Metric</th><th>This Week</th><th>WoW</th></tr><tr><td>Active Sites</td><td>${p.active_sites}</td><td></td></tr><tr><td>MWh Generated</td><td>${parseFloat(p.total_mwh).toFixed(1)}</td><td>${wowMwh}%</td></tr><tr><td>CO2e Avoided</td><td>${co2Tonnes} tonnes</td><td></td></tr><tr><td>RECs Generated</td><td>${p.recs}</td><td></td></tr><tr><td>Active Clients</td><td>${a.active_clients}</td><td></td></tr><tr><td>New Clients</td><td>${a.new_clients}</td><td></td></tr><tr><td>At-Risk Clients</td><td style='color:${a.at_risk_clients > 0 ? 'red' : 'green'}'>${a.at_risk_clients}</td><td></td></tr></table>`; return [{json: {html, total_mwh: p.total_mwh, at_risk: a.at_risk_clients}}];"}},
    {"name": "Email Leadership", "type": "n8n-nodes-base.gmail", "parameters": {"toList": "ceo@yourcleantech.com", "bccList": "cto@yourcleantech.com,cfo@yourcleantech.com,cpo@yourcleantech.com", "subject": "CleanTech Weekly KPIs — {{$json.total_mwh}} MWh | At-risk: {{$json.at_risk}}", "message": "={{$json.html}}"}},
    {"name": "Slack Summary", "type": "n8n-nodes-base.slack", "parameters": {"channel": "#exec-kpis", "text": "Weekly KPIs: {{$json.total_mwh}} MWh generated this week | At-risk clients: {{$json.at_risk}} | See email for full report."}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

What it does: Monday 8 AM → parallel Postgres queries (platform metrics + account health) → builds HTML email with WoW MWh comparison using $getWorkflowStaticData for baseline tracking → emails leadership with BCC → posts summary to #exec-kpis.


The Energy & CleanTech Compliance Case for Self-Hosting

Energy SaaS companies face data classification requirements that commercial cloud automation platforms fundamentally cannot address:

NERC CIP-011 BES Cyber System Information: Any data that could be used to compromise Bulk Electric System assets is BES Cyber System Information (BCSI). Routing operational data through Zapier or Make constitutes off-premise storage of BCSI — a NERC CIP compliance violation. n8n runs inside the operator's secure perimeter.

NERC CIP-014 Physical Security: Transmission station data and security plans are protected under CIP-014. Cloud automation platforms that receive this data create unauthorized access paths. Self-hosted n8n keeps this data entirely within the utility's access control boundary.

FERC Confidentiality Orders: FERC routinely issues protective orders covering ISO/RTO settlement data, interconnection agreements, and market-sensitive operational data. Commercial SaaS platforms are typically not authorized recipients under these orders. n8n running inside the authorized environment is.

Carbon Registry Integrity: Voluntary carbon markets and compliance markets (RGGI, WCI, EU ETS) require tamper-evident audit trails for generation data and REC attestations. n8n's JSON workflows stored in git provide an immutable record of every transformation applied to meter data — Zapier's 30-day log cannot satisfy this requirement.

IoT Telemetry Economics: A platform monitoring 1,000 solar sites at 15-minute intervals generates 96 readings/site/day — 96,000 tasks/day, 2.88M tasks/month. At Zapier's Professional tier ($73.50/month for 2K tasks), this overflows into Enterprise pricing ($600+/month or more). n8n on a $30/month VPS handles the same load at 1/20th the cost.


Deploy These Workflows

All 5 workflows are available as import-ready JSON at stripeai.gumroad.com — or grab the complete CleanTech SaaS automation bundle.

Built one of these for your energy platform? Drop a comment — always interested in how teams handle the NERC/FERC compliance layer.


FlowKit builds ready-to-use n8n automation templates for SaaS teams. These workflows are starting points — customize asset schemas, Postgres table names, and Slack channels for your stack.

Top comments (0)