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 }]] }
}
}
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 }]] }
}
}
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 }]] }
}
}
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 }]] }
}
}
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 }]] }
}
}
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)