Smart building and BuildingTech SaaS companies sit at the intersection of IoT infrastructure, commercial real estate operations, and an increasingly dense energy-code compliance calendar.
If your company builds building management system (BMS) software, HVAC control SaaS, energy management platforms, occupancy analytics tools, or facility intelligence systems for commercial property operators — this post is for you.
NYC Local Law 97 (Int'l §28-320.3.1) started levying carbon emission fines in 2024 at $268/ton CO2e over limit. California Title 24 Part 6 mandates energy code compliance on every permit. EU Energy Efficiency Directive 2023/1791 Article 8 requires large enterprises to conduct energy audits every four years. ASHRAE 90.1-2019 is the baseline standard embedded in most state building codes. LEED v4.1 certification renewals require ongoing performance documentation.
These five n8n automations cover the highest-value workflows for BuildingTech SaaS operators — without routing occupancy patterns, HVAC telemetry, or tenant energy data through a third-party cloud that enterprise tenants' security and compliance teams will flag.
Why Self-Hosted n8n for BuildingTech SaaS?
Three reasons enterprise building operators will ask before signing:
- Occupancy data is personal data — under GDPR Art. 9 and CJEU precedent, occupancy patterns derived from WiFi sensors, badge readers, or CO2 sensors can infer biometric presence data. Enterprise tenants' DPOs block cloud iPaaS processing.
- Tenant energy data has NDA protection — commercial lease agreements routinely contain confidentiality clauses on energy consumption, HVAC setpoints, and occupancy schedules. Routing this data through Zapier or Make violates those agreements.
- NYC LL97 audit trail requirements — Local Law 97 compliance requires documented measurement methodologies and data lineage from sensor to annual building emissions report. Git-versioned n8n workflow JSON + Postgres execution logs creates that chain of custody.
Workflow 1: New Building Operator Customer Onboarding Drip
Office REITs, university facilities teams, hospital facilities departments, and municipal building operators all need different onboarding tracks — different BMS integrations, different compliance priorities, different stakeholder structures.
{
"name": "Building Operator Customer Onboarding Drip",
"nodes": [
{
"name": "Webhook - New Customer",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "building-onboarding",
"httpMethod": "POST"
}
},
{
"name": "Code - Classify Account",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const d = $input.first().json;\nconst buildingType = d.building_type || 'COMMERCIAL_OFFICE';\nconst jurisdiction = d.jurisdiction || 'US_GENERAL';\nconst complianceFlags = [];\nif (buildingType === 'OFFICE_REIT' || buildingType === 'COMMERCIAL_OFFICE') complianceFlags.push('ASHRAE_90_1_2019', 'EPA_ENERGY_STAR_BENCHMARKING');\nif (jurisdiction === 'NYC') complianceFlags.push('NYC_LOCAL_LAW_97', 'NYC_LL87_ENERGY_AUDIT', 'NYC_LL33_ENERGY_GRADE');\nif (jurisdiction === 'CA') complianceFlags.push('CA_TITLE_24_PART_6', 'CA_AB_802_BENCHMARKING');\nif (jurisdiction === 'EU') complianceFlags.push('EU_EED_2023_1791_ART8', 'GDPR_ART9_OCCUPANCY_DATA');\nif (d.leed_certified) complianceFlags.push('LEED_V4_1_CERTIFICATION', 'LEED_PERFORMANCE_SCORE');\nif (buildingType === 'HEALTHCARE') complianceFlags.push('JOINT_COMMISSION_ENVIRONMENT_OF_CARE', 'ASHRAE_170_2021_HVAC');\nif (buildingType === 'UNIVERSITY') complianceFlags.push('APPA_FACILITIES_STANDARDS', 'ASHRAE_62_1_IAQ');\nconst tier = complianceFlags.length >= 4 ? 'ENTERPRISE_REGULATED' : complianceFlags.length >= 2 ? 'STANDARD' : 'BASIC';\nreturn [{ json: { ...d, buildingType, jurisdiction, complianceFlags, tier, onboardedAt: new Date().toISOString() } }];"
}
},
{
"name": "Gmail - Day 0 Welcome",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "={{ $json.contact_email }}",
"subject": "Welcome to {{ $json.company_name }} \u2014 Your Building Intelligence Platform Is Live",
"message": "=Hi {{ $json.contact_name }},\n\nWelcome! Your onboarding tier: {{ $json.tier }}.\n\nCompliance flags detected for your portfolio: {{ $json.complianceFlags.join(', ') }}.\n\nDay 3: BMS/HVAC integration setup guide for {{ $json.bms_system || 'your building system' }}.\nDay 7: Energy benchmarking and LL97 carbon trajectory dashboard setup.\nDay 14: Tenant comfort and IAQ alert configuration.\n\nYour dedicated CSM will reach out within 24h.\n\nBest,\nThe Team"
}
},
{
"name": "Google Sheets - Log",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "append",
"sheetId": "YOUR_SHEET_ID",
"range": "Customers!A:I",
"values": [
[
"={{ $json.company_name }}",
"={{ $json.contact_email }}",
"={{ $json.buildingType }}",
"={{ $json.jurisdiction }}",
"={{ $json.tier }}",
"={{ $json.complianceFlags.join('|') }}",
"={{ $json.square_footage || '' }}",
"={{ $json.onboardedAt }}",
"D0_SENT"
]
]
}
},
{
"name": "Wait - 3 Days",
"type": "n8n-nodes-base.wait",
"parameters": {
"amount": 3,
"unit": "days"
}
},
{
"name": "Gmail - Day 3 BMS Integration",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "={{ $json.contact_email }}",
"subject": "Day 3: Connect Your BMS \u2014 Integration Setup Guide",
"message": "=Hi {{ $json.contact_name }},\n\nTime to connect your building data sources.\n\n{{ $json.complianceFlags.includes('NYC_LOCAL_LAW_97') ? 'Your NYC LL97 setup: BACnet/Modbus meter integration, carbon emissions calculation configuration, and annual building emissions report automation.' : $json.complianceFlags.includes('CA_TITLE_24_PART_6') ? 'Your CA Title 24 setup: utility API connection (PG&E/SCE), ENERGY STAR Portfolio Manager sync, and benchmarking report automation.' : 'Your BMS integration guide: BACnet/Modbus/OPC-UA connection, point mapping, and dashboard configuration.' }}\n\nOpen your integration dashboard: [LINK]\n\nBest,\nThe Team"
}
},
{
"name": "Wait - 4 Days",
"type": "n8n-nodes-base.wait",
"parameters": {
"amount": 4,
"unit": "days"
}
},
{
"name": "Gmail - Day 7 Energy Dashboard",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "={{ $json.contact_email }}",
"subject": "Day 7: Set Up Your Energy & Carbon Compliance Dashboard",
"message": "=Hi {{ $json.contact_name }},\n\nYour energy dashboards are ready to configure.\n\n{{ $json.complianceFlags.includes('NYC_LOCAL_LAW_97') ? 'LL97 carbon trajectory tool: track your building\\'s annual CO2e vs the 2030 limit and calculate projected fines ($268/ton over limit). Setup guide: [LINK]' : 'Energy benchmarking setup: ENERGY STAR score, EUI (kBtu/sf/yr), and month-over-month comparison. Setup guide: [LINK]' }}\n\nBest,\nThe Team"
}
}
],
"connections": {
"Webhook - New Customer": {
"main": [
[
{
"node": "Code - Classify Account",
"type": "main",
"index": 0
}
]
]
},
"Code - Classify Account": {
"main": [
[
{
"node": "Gmail - Day 0 Welcome",
"type": "main",
"index": 0
}
]
]
},
"Gmail - Day 0 Welcome": {
"main": [
[
{
"node": "Google Sheets - Log",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Log": {
"main": [
[
{
"node": "Wait - 3 Days",
"type": "main",
"index": 0
}
]
]
},
"Wait - 3 Days": {
"main": [
[
{
"node": "Gmail - Day 3 BMS Integration",
"type": "main",
"index": 0
}
]
]
},
"Gmail - Day 3 BMS Integration": {
"main": [
[
{
"node": "Wait - 4 Days",
"type": "main",
"index": 0
}
]
]
},
"Wait - 4 Days": {
"main": [
[
{
"node": "Gmail - Day 7 Energy Dashboard",
"type": "main",
"index": 0
}
]
]
}
}
}
What it does: Classifies new customers by building type and jurisdiction, attaches relevant compliance flags (NYC LL97, CA Title 24, EU EED, LEED, ASHRAE), and routes Day 0/3/7 emails with content tailored to their specific regulatory profile and BMS system. An NYC office REIT gets LL97 carbon trajectory setup. A California campus gets AB 802 benchmarking integration. A healthcare facility gets ASHRAE 170-2021 HVAC compliance configuration.
Workflow 2: BMS / IoT Sensor & Integration Health Monitor
BuildingTech SaaS platforms integrate with BACnet/Modbus controllers, smart meter APIs, occupancy sensors, IAQ monitors, and utility data platforms. When these integrations degrade, the downstream consequences range from inaccurate tenant billing to compliance exposure — a CEMS data gap under NYC LL97 can invalidate an annual emissions report.
{
"name": "BMS and IoT Integration Health Monitor",
"nodes": [
{
"name": "Schedule - Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/5 * * * *"
}
]
}
}
},
{
"name": "Google Sheets - Integration List",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "getAll",
"sheetId": "YOUR_SHEET_ID",
"range": "Integrations!A:G"
}
},
{
"name": "HTTP - Health Check Each",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "GET",
"url": "={{ $json.health_endpoint }}",
"timeout": 10000,
"continueOnFail": true
}
},
{
"name": "Code - Evaluate Status",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const results = [];\nfor (const item of $input.all()) {\n const d = item.json;\n const statusCode = d.statusCode || d.status || 0;\n const latencyMs = d.headers ? parseInt(d.headers['x-response-time'] || '0') : 0;\n const lastReading = new Date(d.last_data_ts || 0);\n const staleMinutes = (Date.now() - lastReading.getTime()) / 60000;\n let status = 'OK';\n let complianceRisk = null;\n let alertChannel = '#building-ops';\n if (statusCode === 0 || statusCode >= 500) {\n status = 'DOWN';\n if (d.integration_type === 'ENERGY_METER') { complianceRisk = 'NYC_LL97_DATA_GAP_RISK'; alertChannel = '#compliance-critical'; }\n else if (d.integration_type === 'HVAC_BMS') { complianceRisk = 'ASHRAE_62_1_IAQ_MONITORING_GAP'; alertChannel = '#building-critical'; }\n else { alertChannel = '#building-ops'; }\n } else if (staleMinutes > 60 && d.integration_type === 'ENERGY_METER') {\n status = 'STALE_DATA'; complianceRisk = 'LL97_HOURLY_INTERVAL_DATA_GAP'; alertChannel = '#compliance-critical';\n } else if (staleMinutes > 30 && d.integration_type === 'IAQ_SENSOR') {\n status = 'STALE_DATA'; complianceRisk = 'ASHRAE_62_1_MONITORING_GAP'; alertChannel = '#building-ops';\n } else if (latencyMs > 500) {\n status = 'DEGRADED'; alertChannel = '#building-ops';\n }\n if (status !== 'OK') results.push({ json: { customer_id: d.customer_id, customer_name: d.customer_name, integration_type: d.integration_type, endpoint: d.health_endpoint, status, complianceRisk, alertChannel, latencyMs, staleMinutes: Math.round(staleMinutes), detectedAt: new Date().toISOString() } });\n}\nreturn results.length ? results : [{ json: { allOk: true } }];"
}
},
{
"name": "IF - Has Alerts",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"conditions": [
{
"value1": "={{ $json.allOk }}",
"operation": "notEqual",
"value2": true
}
]
}
}
},
{
"name": "Slack - Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "={{ $json.alertChannel }}",
"text": "={{ $json.status }}: {{ $json.customer_name }} \u2014 {{ $json.integration_type }} integration {{ $json.status === 'DOWN' ? 'is DOWN' : 'has STALE DATA (' + $json.staleMinutes + 'min)' }}. {{ $json.complianceRisk ? 'Compliance risk: ' + $json.complianceRisk : '' }} Detected: {{ $json.detectedAt }}"
}
},
{
"name": "Google Sheets - Log Alert",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "append",
"sheetId": "YOUR_SHEET_ID",
"range": "AlertLog!A:H",
"values": [
[
"={{ $json.detectedAt }}",
"={{ $json.customer_id }}",
"={{ $json.customer_name }}",
"={{ $json.integration_type }}",
"={{ $json.status }}",
"={{ $json.complianceRisk || '' }}",
"={{ $json.latencyMs }}",
"={{ $json.staleMinutes }}"
]
]
}
}
],
"connections": {
"Schedule - Every 5 Minutes": {
"main": [
[
{
"node": "Google Sheets - Integration List",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Integration List": {
"main": [
[
{
"node": "HTTP - Health Check Each",
"type": "main",
"index": 0
}
]
]
},
"HTTP - Health Check Each": {
"main": [
[
{
"node": "Code - Evaluate Status",
"type": "main",
"index": 0
}
]
]
},
"Code - Evaluate Status": {
"main": [
[
{
"node": "IF - Has Alerts",
"type": "main",
"index": 0
}
]
]
},
"IF - Has Alerts": {
"main": [
[
{
"node": "Slack - Alert",
"type": "main",
"index": 0
}
],
[]
]
},
"Slack - Alert": {
"main": [
[
{
"node": "Google Sheets - Log Alert",
"type": "main",
"index": 0
}
]
]
}
}
}
What it monitors: BMS/HVAC controllers (BACnet, Modbus, OPC-UA), smart energy meters (interval data), IAQ sensors (CO2, TVOC, PM2.5), utility API connections, and occupancy sensor feeds. DOWN status on an energy meter integration flags as NYC LL97 data gap risk — LL97 requires hourly interval data for annual emissions calculations. STALE_DATA on an IAQ sensor flags ASHRAE 62.1 monitoring gap. All alerts log to Google Sheets for customer-facing SLA reporting.
Workflow 3: Building Energy Code & Sustainability Compliance Deadline Tracker
NYC Local Law 97 annual compliance filings. CA Title 24 permit-triggered energy code attestations. ASHRAE 90.1 baseline compliance documentation for LEED submissions. EU EED Article 8 energy audits every four years. LEED v4.1 certification renewal windows. EPA ENERGY STAR annual recertification.
{
"name": "Building Compliance Deadline Tracker",
"nodes": [
{
"name": "Schedule - Weekdays 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1-5"
}
]
}
}
},
{
"name": "Google Sheets - Compliance Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "getAll",
"sheetId": "YOUR_DEADLINES_SHEET_ID",
"range": "Deadlines!A:H"
}
},
{
"name": "Code - Classify Urgency",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const today = new Date();\nconst results = [];\nfor (const row of $input.all()) {\n const d = row.json;\n const deadline = new Date(d.deadline_date);\n const daysLeft = Math.floor((deadline - today) / 86400000);\n const DEADLINE_TYPES = {\n 'NYC_LL97_ANNUAL_REPORT': { reg: 'NYC Local Law 97 of 2019 (Int\\'l \u00a728-320.3.1)', consequence: 'Fine $268/ton CO2e over limit', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 14 ? 'CRITICAL' : daysLeft <= 30 ? 'URGENT' : daysLeft <= 90 ? 'WARNING' : 'NOTICE' },\n 'NYC_LL87_ENERGY_AUDIT': { reg: 'NYC Local Law 87 (\u00a728-308.1)', consequence: 'Civil penalty $5,000 + $500/month', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 21 ? 'CRITICAL' : daysLeft <= 60 ? 'URGENT' : daysLeft <= 90 ? 'WARNING' : 'NOTICE' },\n 'NYC_LL33_ENERGY_GRADE': { reg: 'NYC Local Law 33 (\u00a728-309.12)', consequence: 'Grade D posting required', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 14 ? 'CRITICAL' : daysLeft <= 30 ? 'URGENT' : daysLeft <= 60 ? 'WARNING' : 'NOTICE' },\n 'CA_TITLE_24_COMPLIANCE': { reg: 'CA Title 24 Part 6 \u2014 CA Building Energy Code', consequence: 'Permit denial or correction notice', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 7 ? 'CRITICAL' : daysLeft <= 21 ? 'URGENT' : daysLeft <= 60 ? 'WARNING' : 'NOTICE' },\n 'EU_EED_ENERGY_AUDIT': { reg: 'EU EED 2023/1791 Art.8 \u2014 Every 4 years', consequence: 'Member state penalties (varies)', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 30 ? 'CRITICAL' : daysLeft <= 60 ? 'URGENT' : daysLeft <= 120 ? 'WARNING' : 'NOTICE' },\n 'LEED_CERTIFICATION_RENEWAL': { reg: 'LEED v4.1 GBCI Arc Platform', consequence: 'Certification lapse', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 30 ? 'CRITICAL' : daysLeft <= 60 ? 'URGENT' : daysLeft <= 90 ? 'WARNING' : 'NOTICE' },\n 'EPA_ENERGY_STAR_RECERT': { reg: 'EPA ENERGY STAR \u2014 Annual recertification', consequence: 'Score revocation, tenant/investor disclosure', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 21 ? 'CRITICAL' : daysLeft <= 45 ? 'URGENT' : daysLeft <= 90 ? 'WARNING' : 'NOTICE' },\n 'ASHRAE_90_1_COMPLIANCE_DOC': { reg: 'ASHRAE 90.1-2019 \u2014 Energy efficiency standard', consequence: 'LEED submission failure', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 14 ? 'CRITICAL' : daysLeft <= 30 ? 'URGENT' : daysLeft <= 60 ? 'WARNING' : 'NOTICE' },\n 'BENCHMARKING_DISCLOSURE': { reg: 'CA AB 802 / NYC LL84 / WA HB 1257', consequence: 'Fine + public non-compliance listing', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 21 ? 'CRITICAL' : daysLeft <= 45 ? 'URGENT' : daysLeft <= 90 ? 'WARNING' : 'NOTICE' }\n };\n const info = DEADLINE_TYPES[d.deadline_type] || { reg: d.deadline_type, consequence: 'Review required', tier: daysLeft <= 0 ? 'OVERDUE' : daysLeft <= 14 ? 'CRITICAL' : daysLeft <= 30 ? 'URGENT' : daysLeft <= 60 ? 'WARNING' : 'NOTICE' };\n if (info.tier !== 'NOTICE') results.push({ json: { ...d, daysLeft, tier: info.tier, regulatory_citation: info.reg, consequence: info.consequence } });\n}\nreturn results;"
}
},
{
"name": "Switch - Route by Tier",
"type": "n8n-nodes-base.switch",
"parameters": {
"rules": {
"rules": [
{
"value1": "={{ $json.tier }}",
"operation": "equal",
"value2": "OVERDUE"
},
{
"value1": "={{ $json.tier }}",
"operation": "equal",
"value2": "CRITICAL"
},
{
"value1": "={{ $json.tier }}",
"operation": "equal",
"value2": "URGENT"
}
]
}
}
},
{
"name": "Slack - OVERDUE",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#compliance-critical",
"text": "=:rotating_light: OVERDUE: {{ $json.customer_name }} \u2014 {{ $json.deadline_type }} overdue by {{ Math.abs($json.daysLeft) }} days. Consequence: {{ $json.consequence }}. Reg: {{ $json.regulatory_citation }}"
}
},
{
"name": "Slack - CRITICAL",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#compliance-ops",
"text": "=:red_circle: CRITICAL: {{ $json.customer_name }} \u2014 {{ $json.deadline_type }} due in {{ $json.daysLeft }} days. {{ $json.regulatory_citation }}"
}
},
{
"name": "Gmail - Owner Notify",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "={{ $json.owner_email }}",
"subject": "={{ $json.tier }}: {{ $json.deadline_type }} \u2014 {{ $json.daysLeft }} days",
"message": "=Building compliance alert.\n\nCustomer: {{ $json.customer_name }}\nBuilding: {{ $json.building_address }}\nDeadline: {{ $json.deadline_date }} ({{ $json.daysLeft }} days)\nType: {{ $json.deadline_type }}\nReg: {{ $json.regulatory_citation }}\nConsequence: {{ $json.consequence }}\n\nAction required in your compliance portal."
}
}
],
"connections": {
"Schedule - Weekdays 7AM": {
"main": [
[
{
"node": "Google Sheets - Compliance Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets - Compliance Deadlines": {
"main": [
[
{
"node": "Code - Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Code - Classify Urgency": {
"main": [
[
{
"node": "Switch - Route by Tier",
"type": "main",
"index": 0
}
]
]
},
"Switch - Route by Tier": {
"main": [
[
{
"node": "Slack - OVERDUE",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack - CRITICAL",
"type": "main",
"index": 0
}
],
[
{
"node": "Gmail - Owner Notify",
"type": "main",
"index": 0
}
]
]
}
}
}
Deadline types tracked (9 types): NYC Local Law 97 annual carbon reports (fines $268/ton CO2e over limit), NYC LL87 energy audits ($5,000 civil penalty + $500/month), NYC LL33 energy grade postings, CA Title 24 compliance documentation, EU EED Article 8 energy audits (every 4 years), LEED v4.1 certification renewals, EPA ENERGY STAR annual recertification, ASHRAE 90.1 compliance documentation, and state/city benchmarking disclosure laws (CA AB 802, NYC LL84, WA HB 1257). NYC LL97 is flagged as highest consequence — $268/ton fines scale to millions for large portfolios.
Workflow 4: Energy Anomaly & Tenant Comfort Alert Pipeline
The most valuable real-time signal for a BuildingTech SaaS platform isn't that the API went down — it's that the energy or comfort data stopped making physical sense. This pipeline catches anomalies on the data stream before they become tenant complaints or compliance findings.
{
"name": "Energy Anomaly and Tenant Comfort Alert Pipeline",
"nodes": [
{
"name": "Webhook - Sensor Event",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "building-sensor-event",
"httpMethod": "POST"
}
},
{
"name": "Code - Classify Event",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const ev = $input.first().json;\nconst EVENT_MAP = {\n 'ENERGY_SPIKE': { channel: '#building-ops', severity: 'HIGH', ref: 'ASHRAE 90.1-2019 \u00a7 \u2014 Anomalous consumption', sla_hours: 4, action: 'Investigate HVAC fault or equipment malfunction' },\n 'LL97_CARBON_THRESHOLD_RISK': { channel: '#compliance-critical', severity: 'CRITICAL', ref: 'NYC LL97 \u00a728-320.3.1 \u2014 Annual carbon limit', sla_hours: 1, action: 'Review carbon trajectory and HVAC scheduling immediately' },\n 'HVAC_FAILURE': { channel: '#building-critical', severity: 'CRITICAL', ref: 'ASHRAE 62.1-2019 \u2014 Ventilation requirement', sla_hours: 1, action: 'Dispatch HVAC technician \u2014 IAQ and comfort SLA at risk' },\n 'TEMPERATURE_EXCURSION': { channel: '#building-ops', severity: 'HIGH', ref: 'Tenant lease comfort SLA (\u00b12\u00b0F setpoint)', sla_hours: 2, action: 'Check HVAC zone setpoints and override if needed' },\n 'IAQ_EXCEEDANCE': { channel: '#building-ops', severity: 'HIGH', ref: 'ASHRAE 62.1-2019 CO2 < 1100ppm / TVOC < 500ppb', sla_hours: 2, action: 'Increase fresh air intake \u2014 CO2 or TVOC above threshold' },\n 'OCCUPANCY_ANOMALY': { channel: '#building-ops', severity: 'MEDIUM', ref: 'GDPR Art.9 \u2014 Occupancy data handling', sla_hours: 8, action: 'Verify occupancy sensor calibration \u2014 unusual pattern detected' },\n 'METER_DATA_GAP': { channel: '#compliance-critical', severity: 'CRITICAL', ref: 'NYC LL97 \u00a728-320.3.1 \u2014 Hourly interval data required', sla_hours: 1, action: 'Restore meter connectivity \u2014 LL97 annual report requires complete hourly data' }\n};\nconst info = EVENT_MAP[ev.event_type] || { channel: '#building-ops', severity: 'MEDIUM', ref: ev.event_type, sla_hours: 8, action: 'Investigate' };\nconst sla_deadline = new Date(Date.now() + info.sla_hours * 3600000).toISOString();\nreturn [{ json: { ...ev, ...info, sla_deadline, event_id: `BLD-${Date.now()}-${Math.random().toString(36).substr(2,6).toUpperCase()}`, detected_at: new Date().toISOString() } }];"
}
},
{
"name": "Slack - Route Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "={{ $json.channel }}",
"text": "={{ $json.severity }}: {{ $json.event_type }}\nBuilding: {{ $json.building_address }} | Customer: {{ $json.customer_name }}\nRef: {{ $json.ref }}\nSLA: {{ $json.sla_deadline }} ({{ $json.sla_hours }}h)\nAction: {{ $json.action }}\nEvent ID: {{ $json.event_id }}"
}
},
{
"name": "Gmail - Facility Manager",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "={{ $json.facility_contact }}",
"subject": "=[{{ $json.severity }}] {{ $json.event_type }} \u2014 {{ $json.building_address }}",
"message": "=Building Alert\n\nEvent ID: {{ $json.event_id }}\nType: {{ $json.event_type }}\nSeverity: {{ $json.severity }}\nBuilding: {{ $json.building_address }}\nFloor/Zone: {{ $json.zone || 'N/A' }}\nValue: {{ $json.measured_value }} {{ $json.unit || '' }}\nThreshold: {{ $json.threshold || 'N/A' }}\nRef: {{ $json.ref }}\nRequired Action: {{ $json.action }}\nSLA Deadline: {{ $json.sla_deadline }}\n\nLog in to your building dashboard to respond."
}
},
{
"name": "Postgres - Audit Log",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO building_events (event_id, customer_id, building_id, event_type, severity, measured_value, threshold, ref, sla_deadline, detected_at) VALUES ('{{ $json.event_id }}', '{{ $json.customer_id }}', '{{ $json.building_id }}', '{{ $json.event_type }}', '{{ $json.severity }}', '{{ $json.measured_value }}', '{{ $json.threshold }}', '{{ $json.ref }}', '{{ $json.sla_deadline }}', NOW()) ON CONFLICT (event_id) DO NOTHING"
}
}
],
"connections": {
"Webhook - Sensor Event": {
"main": [
[
{
"node": "Code - Classify Event",
"type": "main",
"index": 0
}
]
]
},
"Code - Classify Event": {
"main": [
[
{
"node": "Slack - Route Alert",
"type": "main",
"index": 0
}
]
]
},
"Slack - Route Alert": {
"main": [
[
{
"node": "Gmail - Facility Manager",
"type": "main",
"index": 0
}
]
]
},
"Gmail - Facility Manager": {
"main": [
[
{
"node": "Postgres - Audit Log",
"type": "main",
"index": 0
}
]
]
}
}
}
Event types handled (7): Energy spikes (ASHRAE 90.1 anomalous consumption), NYC LL97 carbon threshold risk (triggers when projected annual carbon exceeds the legal limit), HVAC failures (ASHRAE 62.1 IAQ SLA), temperature excursions (tenant lease comfort SLA ±2°F), IAQ exceedances (CO2 >1100ppm, TVOC >500ppb per ASHRAE 62.1), occupancy anomalies (GDPR Art.9 flagged), and meter data gaps (NYC LL97 hourly interval data requirement). Every event writes an immutable Postgres audit log — creating the data lineage chain from sensor to LL97 annual report.
Workflow 5: Weekly Building Performance KPI Dashboard
Your building operators, sustainability managers, and compliance leads all need different views of the same platform data. This workflow pulls from two Postgres views and sends a formatted HTML digest every Monday morning — with compliance flags in the subject line when action is needed.
{
"name": "BuildingTech Weekly KPI Dashboard",
"nodes": [
{
"name": "Schedule - Monday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1"
}
]
}
}
},
{
"name": "Postgres - Platform Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) as total_buildings, SUM(CASE WHEN status='ACTIVE' THEN 1 ELSE 0 END) as active_buildings, SUM(CASE WHEN created_at >= NOW() - INTERVAL '7 days' THEN 1 ELSE 0 END) as new_this_week, SUM(total_sqft) as total_sqft_managed, AVG(eui_kbtu_sf_yr) as avg_eui, AVG(energy_star_score) as avg_energy_star_score, SUM(monthly_arr_usd) as total_arr FROM customer_buildings WHERE status='ACTIVE'"
}
},
{
"name": "Postgres - Compliance Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) as total_open_deadlines, SUM(CASE WHEN tier='CRITICAL' THEN 1 ELSE 0 END) as critical_deadlines, SUM(CASE WHEN tier='OVERDUE' THEN 1 ELSE 0 END) as overdue_items, SUM(CASE WHEN deadline_type='NYC_LL97_ANNUAL_REPORT' AND deadline_date <= NOW() + INTERVAL '60 days' THEN 1 ELSE 0 END) as ll97_filings_60d, SUM(CASE WHEN deadline_type LIKE '%LEED%' AND deadline_date <= NOW() + INTERVAL '90 days' THEN 1 ELSE 0 END) as leed_renewals_90d FROM compliance_deadlines WHERE status='OPEN'"
}
},
{
"name": "Merge",
"type": "n8n-nodes-base.merge",
"parameters": {
"mode": "mergeByPosition"
}
},
{
"name": "Code - Build HTML Report",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const p = $input.all()[0].json;\nconst c = $input.all()[1].json;\nconst complianceAlert = c.overdue_items > 0 || c.critical_deadlines > 2;\nconst euiRating = parseFloat(p.avg_eui || 0) < 50 ? 'EXCELLENT' : parseFloat(p.avg_eui || 0) < 80 ? 'GOOD' : parseFloat(p.avg_eui || 0) < 120 ? 'AVERAGE' : 'POOR';\nconst html = `<h2>${complianceAlert ? '\u26a0\ufe0f COMPLIANCE ALERT \u2014 ' : ''}BuildingTech Weekly KPI</h2><p>Week of ${new Date().toISOString().split('T')[0]}</p><h3>Portfolio</h3><table border=1><tr><th>Metric</th><th>Value</th></tr><tr><td>Active Buildings</td><td>${p.active_buildings}</td></tr><tr><td>New This Week</td><td>${p.new_this_week}</td></tr><tr><td>Total sf Managed</td><td>${parseInt(p.total_sqft_managed || 0).toLocaleString()} sf</td></tr><tr><td>Avg EUI (kBtu/sf/yr)</td><td>${parseFloat(p.avg_eui || 0).toFixed(1)} \u2014 ${euiRating}</td></tr><tr><td>Avg ENERGY STAR Score</td><td>${parseFloat(p.avg_energy_star_score || 0).toFixed(0)}/100</td></tr><tr><td>Total ARR</td><td>$${parseInt(p.total_arr || 0).toLocaleString()}</td></tr></table><h3>Compliance Dashboard</h3><table border=1><tr><th>Metric</th><th>Value</th></tr><tr><td>Overdue Items</td><td><b style='color:${c.overdue_items > 0 ? 'red' : 'green'}'>${c.overdue_items}</b></td></tr><tr><td>Critical Deadlines</td><td><b style='color:${c.critical_deadlines > 0 ? 'red' : 'green'}'>${c.critical_deadlines}</b></td></tr><tr><td>NYC LL97 Filings (60 days)</td><td>${c.ll97_filings_60d}</td></tr><tr><td>LEED Renewals (90 days)</td><td>${c.leed_renewals_90d}</td></tr></table>`;\nreturn [{ json: { html, complianceAlert, p, c } }];"
}
},
{
"name": "Gmail - Weekly Report",
"type": "n8n-nodes-base.gmail",
"parameters": {
"operation": "send",
"toList": "ceo@yourcompany.com",
"ccList": "sustainability@yourcompany.com,compliance@yourcompany.com",
"subject": "=BuildingTech Weekly KPI \u2014 {{ new Date().toISOString().split('T')[0] }}{{ $json.complianceAlert ? ' [COMPLIANCE ALERT]' : '' }}",
"message": "={{ $json.html }}"
}
}
],
"connections": {
"Schedule - Monday 7AM": {
"main": [
[
{
"node": "Postgres - Platform Metrics",
"type": "main",
"index": 0
}
],
[
{
"node": "Postgres - Compliance Metrics",
"type": "main",
"index": 0
}
]
]
},
"Postgres - Platform Metrics": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Postgres - Compliance Metrics": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Code - Build HTML Report",
"type": "main",
"index": 0
}
]
]
},
"Code - Build HTML Report": {
"main": [
[
{
"node": "Gmail - Weekly Report",
"type": "main",
"index": 0
}
]
]
}
}
}
Data sources: Pulls from two Postgres views — portfolio metrics (buildings managed, total sf, average EUI in kBtu/sf/yr, average ENERGY STAR score, ARR) and compliance metrics (open deadlines by tier, NYC LL97 filings due within 60 days, LEED renewals due within 90 days). EUI is color-coded: <50 EXCELLENT, 50-80 GOOD, 80-120 AVERAGE, >120 POOR. Compliance alert in subject line fires when overdue items exist or critical deadlines exceed 2.
n8n vs Zapier/Make.com for BuildingTech SaaS
| Factor | n8n (self-hosted) | Zapier/Make.com |
|---|---|---|
| Occupancy data (GDPR Art.9) | On your VPC — biometric inference data never leaves | US cloud — GDPR Art.9 transfer concern |
| NYC LL97 audit trail | Git-versioned workflow + Postgres log = chain of custody | No equivalent documented process |
| Tenant NDA compliance | Data stays in operator's infrastructure | Tenant confidentiality clauses block cloud routing |
| BACnet/Modbus integration | Direct TCP connection from on-prem n8n | Requires cloud connector — latency + security gap |
| Custom energy logic | Code node — ASHRAE formulas, EUI calculations | Limited to pre-built steps |
| Cost at scale | Fixed server cost | Per-task pricing at 5-min BMS polling = expensive |
When an enterprise REIT's legal team asks 'does tenant occupancy data leave our network?' — self-hosted n8n lets you answer no.
Get These Workflows
All five workflows above — plus templates for tenant billing automation, LEED documentation pipelines, and portfolio benchmarking reports — are in the FlowKit n8n Template Collection at stripeai.gumroad.com.
Templates include:
- Import-ready JSON (paste directly into n8n)
- Setup guide (Postgres schema, Sheets column definitions, BMS connection notes)
- Compliance deadline calendar template (NYC LL97, EU EED, LEED, ENERGY STAR)
If you found this useful, follow @flowkithq — I publish vertical-specific n8n automation guides every week.
Tags: n8n, automation, webdev, tutorial
Top comments (0)