DEV Community

Alex Kane
Alex Kane

Posted on

n8n for ClimateTech & GreenTech: 5 Automations That Scale Sustainability Ops (Free Workflow JSON)

Whether you're running a climate tech startup, managing clean energy infrastructure, or leading ESG reporting at a sustainability-focused company — you have more manual data work than you should.

Carbon tracking. Renewable output monitoring. ESG filing deadlines. Supplier emissions data. Weekly sustainability KPI reports.

Here are 5 complete n8n workflows — with import-ready JSON — built specifically for ClimateTech and GreenTech operations teams.

All free. All self-hosted. No Zapier bill. No sensor data, carbon accounting data, or supplier emissions data touching third-party cloud servers. Full SEC climate disclosure / EU CSRD-compatible audit trail in git.


1. Carbon Footprint Data Aggregator & Daily Brief

The problem: Carbon accounting data sits in 3 different systems — energy bills, fleet telematics, supplier invoices. Your sustainability manager pulls it manually every week into a spreadsheet.

What this does: Runs daily at 8AM. Pulls scope 1 (direct), scope 2 (purchased energy), and scope 3 (supply chain) data from your sources. Calculates total CO2e, compares to daily target. If over target: fires Slack alert to #sustainability with breakdown. Logs to audit sheet. Emails daily carbon brief to sustainability manager.

{
  "name": "Carbon Footprint Aggregator",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * *" }] } },
      "name": "Daily 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "documentId": "YOUR_SHEET_ID", "sheetName": "daily_carbon", "options": {} },
      "name": "Get Carbon Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [450, 300]
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all();\nconst today = rows[rows.length - 1]?.json || {};\nconst scope1 = parseFloat(today.scope1_kgco2e || 0);\nconst scope2 = parseFloat(today.scope2_kgco2e || 0);\nconst scope3 = parseFloat(today.scope3_kgco2e || 0);\nconst total = scope1 + scope2 + scope3;\nconst target = parseFloat(today.daily_target_kgco2e || 1000);\nconst overTarget = total > target;\nconst html = `<h2>Daily Carbon Brief</h2><table border='1' cellpadding='8' style='border-collapse:collapse'><tr style='background:#2d6a4f;color:#fff'><th>Scope</th><th>kg CO2e</th></tr><tr><td>Scope 1 (Direct)</td><td>${scope1}</td></tr><tr><td>Scope 2 (Energy)</td><td>${scope2}</td></tr><tr><td>Scope 3 (Supply Chain)</td><td>${scope3}</td></tr><tr style='font-weight:bold'><td>Total</td><td>${total}</td></tr><tr><td>Daily Target</td><td>${target}</td></tr></table><p>Status: ${overTarget ? '<strong style=color:red>OVER TARGET</strong>' : '<strong style=color:green>ON TRACK</strong>'}</p>`;\nreturn [{ json: { scope1, scope2, scope3, total, target, overTarget, html } }];"
      },
      "name": "Calculate CO2e",
      "type": "n8n-nodes-base.code",
      "position": [650, 300]
    },
    {
      "parameters": {
        "conditions": { "boolean": [{ "value1": "={{ $json.overTarget }}", "value2": true }] }
      },
      "name": "Over Target?",
      "type": "n8n-nodes-base.if",
      "position": [850, 300]
    },
    {
      "parameters": {
        "channel": "#sustainability",
        "text": "=:warning: *Carbon Target Exceeded*\nTotal: {{ $json.total }} kg CO2e | Target: {{ $json.target }}\nScope 1: {{ $json.scope1 }} | Scope 2: {{ $json.scope2 }} | Scope 3: {{ $json.scope3 }}"
      },
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "position": [1050, 200]
    },
    {
      "parameters": {
        "toEmail": "sustainability@yourcompany.com",
        "subject": "=Daily Carbon Brief — {{ $now.format('MMM DD, YYYY') }}",
        "emailType": "html",
        "message": "={{ $json.html }}"
      },
      "name": "Email Brief",
      "type": "n8n-nodes-base.gmail",
      "position": [1050, 400]
    }
  ],
  "connections": {
    "Daily 8AM": { "main": [[{ "node": "Get Carbon Data", "type": "main", "index": 0 }]] },
    "Get Carbon Data": { "main": [[{ "node": "Calculate CO2e", "type": "main", "index": 0 }]] },
    "Calculate CO2e": { "main": [[{ "node": "Over Target?", "type": "main", "index": 0 }]] },
    "Over Target?": { "main": [[{ "node": "Slack Alert", "type": "main", "index": 0 }], [{ "node": "Email Brief", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Renewable Energy Output Alert

The problem: Your solar array or wind turbines underperform on a cloudy day — but no one knows until the end-of-month energy report shows the shortfall.

What this does: Runs every 15 minutes. Pulls actual energy output from your monitoring API or Sheets. Compares to expected output (based on rated capacity and weather-adjusted capacity factor). If actual output is <70% of expected: fires Slack alert to #grid-ops with plant name, actual vs expected kWh, and deviation %. Logs all readings to audit sheet.

{
  "name": "Renewable Output Alert",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "minutes", "minutesInterval": 15 }] } },
      "name": "Every 15 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "documentId": "YOUR_SHEET_ID", "sheetName": "plant_readings", "options": {} },
      "name": "Get Plant Readings",
      "type": "n8n-nodes-base.googleSheets",
      "position": [450, 300]
    },
    {
      "parameters": {
        "jsCode": "return $input.all()\n  .map(r => {\n    const actual = parseFloat(r.json.actual_kwh || 0);\n    const expected = parseFloat(r.json.expected_kwh || 1);\n    const deviation = ((expected - actual) / expected * 100).toFixed(1);\n    return { json: { ...r.json, actual, expected, deviation, underperforming: actual < expected * 0.7 } };\n  })\n  .filter(r => r.json.underperforming);"
      },
      "name": "Detect Underperformance",
      "type": "n8n-nodes-base.code",
      "position": [650, 300]
    },
    {
      "parameters": {
        "channel": "#grid-ops",
        "text": "=:solar_panel: *Renewable Output Alert*\n*Plant:* {{ $json.plant_name }}\n*Actual:* {{ $json.actual }} kWh | *Expected:* {{ $json.expected }} kWh\n*Deviation:* {{ $json.deviation }}% below expected\n*Type:* {{ $json.energy_type }}"
      },
      "name": "Slack Grid Ops",
      "type": "n8n-nodes-base.slack",
      "position": [900, 300]
    }
  ],
  "connections": {
    "Every 15 Min": { "main": [[{ "node": "Get Plant Readings", "type": "main", "index": 0 }]] },
    "Get Plant Readings": { "main": [[{ "node": "Detect Underperformance", "type": "main", "index": 0 }]] },
    "Detect Underperformance": { "main": [[{ "node": "Slack Grid Ops", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. ESG Regulatory Deadline Tracker

The problem: ESG filing deadlines — SEC climate disclosure, EU CSRD, GHG Protocol audits, CDP submissions — are scattered across emails and calendars. Teams miss them until 2 weeks out.

What this does: Runs daily at 8AM. Reads your ESG deadline sheet (regulation name, jurisdiction, deadline date, owner, status). Calculates days until each deadline. Tiers by urgency: CRITICAL (≤7 days), URGENT (≤21 days), WARNING (≤45 days), NOTICE (≤90 days). Posts to Slack #compliance for CRITICAL and URGENT. Emails each owner directly. Skips completed items.

{
  "name": "ESG Deadline Tracker",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * *" }] } },
      "name": "Daily 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "documentId": "YOUR_SHEET_ID", "sheetName": "esg_deadlines", "options": {} },
      "name": "Get Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "position": [450, 300]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nreturn $input.all()\n  .filter(r => r.json.status !== 'completed')\n  .map(r => {\n    const due = new Date(r.json.deadline_date);\n    const daysLeft = Math.ceil((due - today) / 86400000);\n    const urgency = daysLeft <= 7 ? 'CRITICAL' : daysLeft <= 21 ? 'URGENT' : daysLeft <= 45 ? 'WARNING' : daysLeft <= 90 ? 'NOTICE' : null;\n    return { json: { ...r.json, daysLeft, urgency } };\n  })\n  .filter(r => r.json.urgency !== null);"
      },
      "name": "Calculate Urgency",
      "type": "n8n-nodes-base.code",
      "position": [650, 300]
    },
    {
      "parameters": {
        "conditions": { "string": [{ "value1": "={{ $json.urgency }}", "operation": "contains", "value2": "CRITICAL" }] }
      },
      "name": "Is Critical?",
      "type": "n8n-nodes-base.if",
      "position": [850, 300]
    },
    {
      "parameters": {
        "channel": "#compliance",
        "text": "=:[{{ $json.urgency }}] *ESG Deadline: {{ $json.regulation }}*\n*Jurisdiction:* {{ $json.jurisdiction }}\n*Due:* {{ $json.deadline_date }} ({{ $json.daysLeft }} days)\n*Owner:* {{ $json.owner }}\n*Status:* {{ $json.status }}"
      },
      "name": "Slack Compliance",
      "type": "n8n-nodes-base.slack",
      "position": [1050, 200]
    },
    {
      "parameters": {
        "toEmail": "={{ $json.owner_email }}",
        "subject": "=[{{ $json.urgency }}] ESG Deadline in {{ $json.daysLeft }} days: {{ $json.regulation }}",
        "emailType": "html",
        "message": "=<h2>ESG Filing Reminder</h2><p>Hi {{ $json.owner }},</p><p>You have an upcoming ESG regulatory deadline:</p><ul><li><strong>Regulation:</strong> {{ $json.regulation }}</li><li><strong>Jurisdiction:</strong> {{ $json.jurisdiction }}</li><li><strong>Deadline:</strong> {{ $json.deadline_date }}</li><li><strong>Days remaining:</strong> {{ $json.daysLeft }}</li><li><strong>Status:</strong> {{ $json.status }}</li></ul>"
      },
      "name": "Email Owner",
      "type": "n8n-nodes-base.gmail",
      "position": [1050, 400]
    }
  ],
  "connections": {
    "Daily 8AM": { "main": [[{ "node": "Get Deadlines", "type": "main", "index": 0 }]] },
    "Get Deadlines": { "main": [[{ "node": "Calculate Urgency", "type": "main", "index": 0 }]] },
    "Calculate Urgency": { "main": [[{ "node": "Is Critical?", "type": "main", "index": 0 }]] },
    "Is Critical?": { "main": [[{ "node": "Slack Compliance", "type": "main", "index": 0 }], [{ "node": "Email Owner", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Scope 3 Supply Chain Emissions Monitor

The problem: Scope 3 emissions (supply chain) are the hardest to track and often the largest slice of your carbon footprint. Data arrives from suppliers monthly, manually, in inconsistent formats.

What this does: Runs daily at 7AM. Reads supplier emissions submissions from Sheets (supplier name, reporting period, CO2e tonnes, category, status). Identifies suppliers over their allocated emissions quota. Fires Slack alert to #sustainability with supplier name, actual vs quota, overage %. Emails the supplier relationship manager. Appends all readings to scope3_audit log.

{
  "name": "Scope 3 Emissions Monitor",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 7 * * *" }] } },
      "name": "Daily 7AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "documentId": "YOUR_SHEET_ID", "sheetName": "supplier_emissions", "options": {} },
      "name": "Get Supplier Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [450, 300]
    },
    {
      "parameters": {
        "jsCode": "return $input.all()\n  .map(r => {\n    const actual = parseFloat(r.json.co2e_tonnes || 0);\n    const quota = parseFloat(r.json.annual_quota_tonnes || 1);\n    const overage = ((actual - quota) / quota * 100).toFixed(1);\n    return { json: { ...r.json, actual, quota, overage, overQuota: actual > quota } };\n  })\n  .filter(r => r.json.overQuota);"
      },
      "name": "Find Over-Quota Suppliers",
      "type": "n8n-nodes-base.code",
      "position": [650, 300]
    },
    {
      "parameters": {
        "channel": "#sustainability",
        "text": "=:warning: *Scope 3 Quota Exceeded*\n*Supplier:* {{ $json.supplier_name }}\n*Category:* {{ $json.category }}\n*Actual:* {{ $json.actual }} t CO2e | *Quota:* {{ $json.quota }} t\n*Overage:* {{ $json.overage }}% above annual quota"
      },
      "name": "Slack Sustainability",
      "type": "n8n-nodes-base.slack",
      "position": [900, 200]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "scope3_audit",
        "columns": { "mappingMode": "defineBelow", "value": { "ts": "={{ $now.toISO() }}", "supplier": "={{ $json.supplier_name }}", "actual": "={{ $json.actual }}", "quota": "={{ $json.quota }}", "overage_pct": "={{ $json.overage }}" } }
      },
      "name": "Log to Audit Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [900, 400]
    }
  ],
  "connections": {
    "Daily 7AM": { "main": [[{ "node": "Get Supplier Data", "type": "main", "index": 0 }]] },
    "Get Supplier Data": { "main": [[{ "node": "Find Over-Quota Suppliers", "type": "main", "index": 0 }]] },
    "Find Over-Quota Suppliers": { "main": [[{ "node": "Slack Sustainability", "type": "main", "index": 0 }], [{ "node": "Log to Audit Sheet", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Weekly Sustainability KPI Report

The problem: Your sustainability KPI report for the board takes half a day every Monday — pulling CO2 data, calculating renewable %, converting to 'trees equivalent', formatting into a slide deck.

What this does: Runs every Monday at 8AM. Pulls weekly sustainability metrics from Sheets. Calculates: total CO2e reduced vs baseline, renewable energy %, grid contribution MWh, scope 1+2+3 totals, WoW changes. Builds clean HTML report. Emails sustainability director and CSO with full table. Posts one-line Slack summary to #sustainability.

{
  "name": "Weekly Sustainability KPI Report",
  "nodes": [
    {
      "parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * 1" }] } },
      "name": "Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [250, 300]
    },
    {
      "parameters": { "documentId": "YOUR_SHEET_ID", "sheetName": "weekly_sustainability", "options": {} },
      "name": "Get Sustainability Data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [450, 300]
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(r => r.json);\nconst tw = rows[rows.length - 1] || {};\nconst lw = rows[rows.length - 2] || tw;\nconst co2Reduced = parseFloat(tw.co2e_reduced_kg || 0);\nconst renewablePct = parseFloat(tw.renewable_energy_pct || 0);\nconst gridMwh = parseFloat(tw.grid_contribution_mwh || 0);\nconst scope1 = parseFloat(tw.scope1_kgco2e || 0);\nconst scope2 = parseFloat(tw.scope2_kgco2e || 0);\nconst scope3 = parseFloat(tw.scope3_kgco2e || 0);\nconst treesEq = (co2Reduced / 21).toFixed(0);\nconst renewWoW = lw.renewable_energy_pct ? ((renewablePct - parseFloat(lw.renewable_energy_pct)).toFixed(1)) : '0';\nconst html = `<h2>Weekly Sustainability Report</h2><table border='1' cellpadding='8' style='border-collapse:collapse;font-family:sans-serif'><tr style='background:#2d6a4f;color:#fff'><th>Metric</th><th>This Week</th><th>WoW Change</th></tr><tr><td>CO2e Reduced</td><td>${co2Reduced} kg</td><td>—</td></tr><tr><td>Trees Equivalent</td><td>${treesEq} trees</td><td>—</td></tr><tr><td>Renewable Energy %</td><td>${renewablePct}%</td><td>${renewWoW}%</td></tr><tr><td>Grid Contribution</td><td>${gridMwh} MWh</td><td>—</td></tr><tr><td>Scope 1</td><td>${scope1} kg CO2e</td><td>—</td></tr><tr><td>Scope 2</td><td>${scope2} kg CO2e</td><td>—</td></tr><tr><td>Scope 3</td><td>${scope3} kg CO2e</td><td>—</td></tr></table>`;\nreturn [{ json: { html, co2Reduced, renewablePct, gridMwh, treesEq, renewWoW } }];"
      },
      "name": "Calculate KPIs",
      "type": "n8n-nodes-base.code",
      "position": [650, 300]
    },
    {
      "parameters": {
        "toEmail": "cso@yourcompany.com",
        "subject": "=Weekly Sustainability Report — {{ $now.format('MMM DD, YYYY') }}",
        "emailType": "html",
        "message": "={{ $json.html }}"
      },
      "name": "Email CSO",
      "type": "n8n-nodes-base.gmail",
      "position": [900, 250]
    },
    {
      "parameters": {
        "channel": "#sustainability",
        "text": "=Weekly Sustainability: CO2e Reduced {{ $json.co2Reduced }} kg (={{ $json.treesEq }} trees) | Renewable {{ $json.renewablePct }}% ({{ $json.renewWoW }}% WoW) | Grid {{ $json.gridMwh }} MWh"
      },
      "name": "Slack Summary",
      "type": "n8n-nodes-base.slack",
      "position": [900, 400]
    }
  ],
  "connections": {
    "Monday 8AM": { "main": [[{ "node": "Get Sustainability Data", "type": "main", "index": 0 }]] },
    "Get Sustainability Data": { "main": [[{ "node": "Calculate KPIs", "type": "main", "index": 0 }]] },
    "Calculate KPIs": { "main": [[{ "node": "Email CSO", "type": "main", "index": 0 }], [{ "node": "Slack Summary", "type": "main", "index": 0 }]] }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why n8n for ClimateTech & GreenTech?

Feature n8n Zapier Make.com
Self-hosted (data stays on-prem)
SEC climate disclosure audit trail ✅ (git JSON)
EU CSRD / GHG Protocol compatible
Custom code for emissions math Limited
Sensor/API data integration Limited Limited
Cost at 50k ops/month $0 (self-hosted) $49/mo $29/mo

For climate tech operations, self-hosted n8n is often a compliance requirement — not just a cost play.

Carbon accounting data, supplier emissions figures, energy consumption patterns, and ESG filing timelines are commercially sensitive and potentially regulated:

  • SEC climate disclosure rules (US public companies) require defensible audit trails for reported emissions data
  • EU CSRD (Corporate Sustainability Reporting Directive) mandates third-party assurance on sustainability data — your data pipeline needs to be auditable
  • GHG Protocol and CDP submissions involve supplier and operational data that shouldn't transit third-party cloud automation platforms

n8n's JSON workflows are git-versionable — every workflow change is auditable, every data transformation is inspectable, with no data leaving your infrastructure.


Get ready-to-import n8n workflow templates

If you want these workflows ready to import (not manually rebuild from scratch), I've packaged them at FlowKit — n8n Automation Templates.

Individual templates: $12–$29. Full bundle (15 templates): $97.

Includes the Daily Report Generator, Email Auto-Responder, AI Customer Support Bot, Lead Capture to CRM, and 11 more — all pre-built, documented, and ready to customize.


Which of these would save your sustainability team the most time? Drop it in the comments.

Top comments (0)