DEV Community

Alex Kane
Alex Kane

Posted on

n8n for GovTech & Civic Engagement SaaS Vendors: 5 Automations for FOIA Response, Election Compliance, and ADA Title II Accessibility

n8n for GovTech & Civic Engagement SaaS Vendors: 5 Automations for FOIA Response, Election Compliance, and ADA Title II

If your SaaS platform serves government agencies, election officials, or public sector clients, you already know the stakes:

  • A FOIA request that misses the 20-working-day federal clock (or 10-day California CPRA, 5-day New York FOIL acknowledgment) exposes your government customer to DOJ referral — and your platform to contract termination
  • An election management system that goes down on election day is a HAVA §21083 compliance event and a CISA critical infrastructure reportable incident
  • Failing the DOJ's ADA Title II WCAG 2.1 AA deadline (April 24, 2026 for state agencies) creates private right of action exposure for every government portal your SaaS powers
  • Routing voter PII, FOIA request data, or election configuration through Zapier or Make creates a data egress problem that breaks your government customer's data sovereignty agreements

Here are 5 production-ready n8n workflows — with full import-ready JSON — that GovTech and civic engagement SaaS vendors use to manage these compliance obligations on autopilot.


Who This Is For

These workflows target SaaS vendors selling into government — engineering, ops, and compliance teams at:

  • ELECTION_MANAGEMENT_SAAS: Statewide election management platforms (ballot design, polling place management, results reporting) — HAVA 52 USC §21083 EAC certification, VRA §2 disparate impact, ADA Title II WCAG 2.1 AA April 2026
  • VOTER_REGISTRATION_SAAS: Voter registration and roll maintenance systems — NVRA 52 USC §20501, HAVA §21083 security, State voter data retention laws
  • FOIA_REQUEST_MANAGEMENT_SAAS: FOIA/open records request tracking and response platforms — 5 USC §552 20-day, CA CPRA 10-day, NY FOIL 5-day ACK/20-day, IL FOIA 5-day
  • CIVIC_ENGAGEMENT_PLATFORM_SAAS: Public comment portals, participatory budgeting, constituent engagement tools — ADA Title II WCAG 2.1 AA, eGov Act 44 USC §3501, CCPA/state privacy
  • GOVERNMENT_CONTENT_MANAGEMENT_SAAS: Government websites, portals, and digital content platforms — Section 508 29 USC §794d, ADA Title II WCAG 2.1 AA, FedRAMP (if federal)
  • DIGITAL_GOVERNMENT_SERVICES_SAAS: Permitting, licensing, payment, and service delivery platforms for government — ADA Title II WCAG 2.1 AA, PCI DSS v4.0 (payments), eGov Act digital-first mandate
  • GOVTECH_CIVIC_STARTUP_SAAS: Early-stage GovTech/civic tech startups pre-Series A — ADA Title II WCAG 2.1 AA, FOIA compliance by product type, State open records acts

Why Self-Hosted n8n vs. Zapier / Make for GovTech

Capability n8n (self-hosted) Zapier / Make
FOIA SLA multi-jurisdiction clock ✅ Custom JS per state law ❌ No built-in deadline engine
Election-day zero-downtime monitoring ✅ 5-min health checks + CISA alerts ❌ No HAVA-aware routing
ADA Title II deadline tracking ✅ 8 deadline types, DOJ rule dates ❌ No government-specific nodes
VRA §2 audit trail (git-versioned JSON) ✅ Version control = forensic audit log ❌ SaaS audit log, not court-ready
Voter PII / FOIA request data egress ✅ Stays in your VPC/network ❌ Routes through cloud vendor servers
Multi-state open records jurisdiction ✅ Switch node per state law ❌ Single-logic only
CISA election infrastructure reporting ✅ Webhook to CISA ISAC endpoint ❌ No CISA-specific integrations

The core problem: voter PII, FOIA request content, election configuration data, and open records documents are subject to strict data sovereignty requirements — routing them through a cloud automation vendor creates a data egress audit finding. Self-hosted n8n keeps every byte in your controlled environment.


Workflow 1: FOIA / Open Records Request Triage & Multi-Jurisdiction Response Clock

The compliance trap: federal FOIA gives 20 working days. California CPRA gives 10. New York FOIL requires a 5-business-day acknowledgment followed by a 20-business-day response. Illinois FOIA gives 5 business days. Miss any of these and your government customer faces DOJ referral, statutory damages, and fee-shifting attorney awards — and your platform gets blamed.

This workflow runs every weekday morning and calculates each open request's SLA status under the correct jurisdiction's statute:

{
  "name": "FOIA / Open Records Request Triage & Multi-Jurisdiction Response Clock",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "name": "Weekdays 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "id": "wf1-n1"
    },
    {
      "parameters": {
        "operation": "getAll",
        "documentId": {
          "__rl": true,
          "value": "={{$vars.FOIA_SHEET_ID}}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Requests",
          "mode": "name"
        },
        "options": {
          "where": {
            "values": [
              {
                "lookupColumn": "status",
                "lookupValue": "OPEN"
              }
            ]
          }
        }
      },
      "name": "Get Open Requests",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        460,
        300
      ],
      "id": "wf1-n2"
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst items = [];\nfor (const row of $input.all()) {\n  const r = row.json;\n  const received = new Date(r.received_date);\n  const jurisdiction = r.jurisdiction || 'FEDERAL';\n  let deadline_days = 20;\n  let ack_days = null;\n  let law_cite = '5 USC \u00a7552(a)(6)(A)(i) 20 working days';\n  if (jurisdiction === 'CA_CPRA') { deadline_days = 10; law_cite = 'Cal. Gov. Code \u00a77922.535 10 working days'; }\n  else if (jurisdiction === 'NY_FOIL') { deadline_days = 20; ack_days = 5; law_cite = 'NY PO \u00a789(3) 5-day ACK + 20-day response'; }\n  else if (jurisdiction === 'TX_PIA') { deadline_days = 10; law_cite = 'Tex. Gov. Code \u00a7552.301 10 business days'; }\n  else if (jurisdiction === 'IL_FOIA') { deadline_days = 5; law_cite = 'Illinois FOIA 5 ILCS 140/3 5 business days'; }\n  let business_days = 0;\n  let d = new Date(received);\n  while (d < today) {\n    d.setDate(d.getDate() + 1);\n    if (d.getDay() !== 0 && d.getDay() !== 6) business_days++;\n  }\n  const days_remaining = deadline_days - business_days;\n  let urgency = 'NOTICE';\n  if (days_remaining < 0) urgency = 'OVERDUE';\n  else if (days_remaining <= 2) urgency = 'CRITICAL';\n  else if (days_remaining <= 5) urgency = 'URGENT';\n  else if (days_remaining <= 10) urgency = 'WARNING';\n  if (ack_days && !r.ack_sent && business_days >= ack_days) {\n    items.push({json: {...r, urgency: 'ACK_OVERDUE', days_remaining, business_days, deadline_days, law_cite, note: 'ACK not sent \u2014 ' + ack_days + '-day ACK window breached'}});\n  } else if (days_remaining <= 10 || urgency === 'OVERDUE') {\n    items.push({json: {...r, urgency, days_remaining, business_days, deadline_days, law_cite}});\n  }\n}\nreturn items.length ? items : [{json:{skip:true}}];"
      },
      "name": "Calculate SLA Status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "id": "wf1-n3"
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.skip}}",
              "value2": true
            }
          ]
        }
      },
      "name": "Skip if None",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "id": "wf1-n4"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{$vars.SLACK_FOIA_CHANNEL}}",
          "mode": "id"
        },
        "text": "={{$json.urgency === 'OVERDUE' ? '\ud83d\udea8' : $json.urgency === 'CRITICAL' ? '\ud83d\udd34' : '\ud83d\udfe1'}} *FOIA/Open Records {{$json.urgency}}* \u2014 Request #{{$json.request_id}}\\nRequester: {{$json.requester_name}} | Jurisdiction: {{$json.jurisdiction}}\\nReceived: {{$json.received_date}} | Days elapsed: {{$json.business_days}} / {{$json.deadline_days}}\\nDays remaining: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) : $json.days_remaining}}\\nLaw: {{$json.law_cite}}\\nAssigned: {{$json.assigned_staff}} | Topic: {{$json.request_topic}}"
      },
      "name": "Slack #foia-compliance",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        1120,
        200
      ],
      "id": "wf1-n5"
    },
    {
      "parameters": {
        "sendTo": "={{$json.assigned_staff_email}}",
        "subject": "={{$json.urgency}}: FOIA Request #{{$json.request_id}} \u2014 {{$json.days_remaining < 0 ? 'OVERDUE' : $json.days_remaining + ' days remaining'}} ({{$json.law_cite}})",
        "message": "={{$json.urgency}} \u2014 FOIA/Open Records request #{{$json.request_id}} requires immediate attention.\\n\\nRequester: {{$json.requester_name}}\\nJurisdiction: {{$json.jurisdiction}}\\nLaw: {{$json.law_cite}}\\nReceived: {{$json.received_date}}\\nDays elapsed: {{$json.business_days}} of {{$json.deadline_days}} working days\\nStatus: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) + ' working days' : $json.days_remaining + ' working days remaining'}}\\n\\nNote: {{$json.note || 'Respond or issue extension via 5 USC \u00a7552(a)(6)(B) before deadline.'}}"
      },
      "name": "Gmail assigned staff",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1120,
        400
      ],
      "id": "wf1-n6"
    }
  ],
  "connections": {
    "Weekdays 8 AM": {
      "main": [
        [
          {
            "node": "Get Open Requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Open Requests": {
      "main": [
        [
          {
            "node": "Calculate SLA Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate SLA Status": {
      "main": [
        [
          {
            "node": "Skip if None",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip if None": {
      "main": [
        [],
        [
          {
            "node": "Slack #foia-compliance",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail assigned staff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

How it works:

  • Pulls all open FOIA/open records requests from Google Sheets
  • Counts working days elapsed since receipt, per-jurisdiction business day rules
  • Maps jurisdiction code to statute: FEDERAL → 5 USC §552(a)(6)(A)(i) 20 working days, CA_CPRA → Cal. Gov. Code §7922.535 10 working days, NY_FOIL → 5-day ACK + 20-day response, TX_PIA → 10 business days, IL_FOIA → 5 business days
  • Flags ACK_OVERDUE separately for NY FOIL (acknowledgment window different from response window)
  • Routes OVERDUE/CRITICAL/URGENT/WARNING to Slack #foia-compliance + Gmail to assigned staff member

Workflow 2: Election Platform Uptime & HAVA §21083 Security Compliance Monitor

The compliance trap: election management systems, voter registration databases, and results reporting platforms are CISA-designated critical infrastructure. Under HAVA 52 USC §21083, state election systems must meet EAC security certification standards. A breach or extended outage during an election window is a CISA Election ISAC reportable event — and your SaaS contract likely includes 99.9% uptime SLAs with liquidated damages.

This workflow runs every 5 minutes and monitors all registered election infrastructure endpoints:

{
  "name": "Election Platform Uptime & HAVA \u00a721083 Security Compliance Monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/5 * * * *"
            }
          ]
        }
      },
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "id": "wf2-n1"
    },
    {
      "parameters": {
        "jsCode": "const endpoints = JSON.parse(process.env.ELECTION_ENDPOINTS || '[]');\nreturn endpoints.map(ep => ({json: ep}));"
      },
      "name": "Load Endpoints",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "id": "wf2-n2"
    },
    {
      "parameters": {
        "url": "={{$json.health_url}}",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "name": "HTTP Health Check",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        300
      ],
      "id": "wf2-n3"
    },
    {
      "parameters": {
        "jsCode": "const endpoint = $('Load Endpoints').item.json;\nconst resp = $input.item.json;\nconst status = resp.$response?.status || 0;\nconst latency_ms = resp.$response?.headers?.['x-response-time'] || null;\nconst is_election_day = new Date().toISOString().substring(0,10) === process.env.ELECTION_DATE;\nlet severity = 'OK';\nif (status === 0 || status >= 500) severity = is_election_day ? 'ELECTION_DAY_CRITICAL' : 'CRITICAL';\nelse if (status >= 400) severity = 'DEGRADED';\nelse if (latency_ms && parseInt(latency_ms) > 3000) severity = is_election_day ? 'ELECTION_DAY_SLA_BREACH' : 'HIGH_LATENCY';\nconst hava_risk = ['VOTER_REGISTRATION_DB','ELECTION_MANAGEMENT_SYSTEM','RESULTS_REPORTING'].includes(endpoint.system_type);\nreturn [{json: {...endpoint, severity, status, latency_ms, hava_risk, is_election_day,\n  law_cite: hava_risk ? 'HAVA 52 USC \u00a721083 \u2014 election system security + CISA election infrastructure' : 'Platform SLA',\n  cisa_note: hava_risk ? 'CISA Election Infrastructure ISAC reportable event if unresolved >15min' : null}}];"
      },
      "name": "Evaluate Severity",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "id": "wf2-n4"
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json.severity}}",
              "operation": "notEqual",
              "value2": "OK"
            }
          ]
        }
      },
      "name": "Filter Issues",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "id": "wf2-n5"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{$json.is_election_day ? $vars.SLACK_ELECTION_OPS : $vars.SLACK_PLATFORM_OPS}}",
          "mode": "id"
        },
        "text": "={{$json.severity.includes('ELECTION_DAY') ? '\ud83d\udea8 ELECTION DAY INCIDENT' : '\ud83d\udd34 PLATFORM ALERT'}} \u2014 *{{$json.severity}}*\\nSystem: {{$json.system_name}} ({{$json.system_type}})\\nHTTP: {{$json.status}} | Latency: {{$json.latency_ms || 'N/A'}}ms\\nHAVA Risk: {{$json.hava_risk ? 'YES \u2014 ' + $json.law_cite : 'No'}}\\n{{$json.cisa_note ? 'CISA: ' + $json.cisa_note : ''}}"
      },
      "name": "Slack alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        1340,
        300
      ],
      "id": "wf2-n6"
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Load Endpoints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Endpoints": {
      "main": [
        [
          {
            "node": "HTTP Health Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Health Check": {
      "main": [
        [
          {
            "node": "Evaluate Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Severity": {
      "main": [
        [
          {
            "node": "Filter Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Issues": {
      "main": [
        [
          {
            "node": "Slack alert",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

How it works:

  • Loads endpoint list from environment config (voter registration DB, election management system, results reporting portal)
  • HTTP health checks with 10-second timeout
  • Detects ELECTION_DAY_CRITICAL vs. CRITICAL based on ELECTION_DATE environment variable — escalation path changes on election day
  • HAVA-risk flag: VOTER_REGISTRATION_DB, ELECTION_MANAGEMENT_SYSTEM, RESULTS_REPORTING system types trigger CISA note
  • Routes alerts to #election-ops on election day vs. #platform-ops otherwise

Workflow 3: ADA Title II WCAG 2.1 AA & VRA §2 Accessibility Compliance Deadline Tracker

The compliance trap: the DOJ finalized its ADA Title II WCAG 2.1 AA rule in April 2024 — state agencies with populations over 50,000 had until April 24, 2026 to comply; smaller localities have until April 26, 2027. Every government portal your SaaS powers is subject. VRA §2 algorithmic disparate impact audits are required any time you deploy a change that affects voter registration or ballot access eligibility logic.

This workflow tracks 8 compliance deadline types specific to civic tech:

{
  "name": "ADA Title II WCAG 2.1 AA & VRA \u00a72 Accessibility Compliance Deadline Tracker",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "name": "Weekdays 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "id": "wf3-n1"
    },
    {
      "parameters": {
        "operation": "getAll",
        "documentId": {
          "__rl": true,
          "value": "={{$vars.GOVTECH_COMPLIANCE_SHEET_ID}}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "AccessibilityDeadlines",
          "mode": "name"
        }
      },
      "name": "Get Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        460,
        300
      ],
      "id": "wf3-n2"
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst results = [];\nconst DEADLINE_TYPES = {\n  ADA_TITLE_II_WCAG_STATE: {cite: 'DOJ 28 CFR \u00a735.200 ADA Title II WCAG 2.1 AA \u2014 State agencies >50K pop: April 24 2026', risk: 'DOJ enforcement + private right of action'},\n  ADA_TITLE_II_WCAG_LOCAL: {cite: 'DOJ 28 CFR \u00a735.200 ADA Title II WCAG 2.1 AA \u2014 Local govts <50K pop: April 26 2027', risk: 'DOJ enforcement + private right of action'},\n  VRA_SECTION_2_AUDIT: {cite: 'VRA 52 USC \u00a710301 \u2014 algorithmic disparate impact audit on any system change', risk: 'DOJ/private litigation disparate impact claim'},\n  HAVA_SYSTEM_CERTIFICATION: {cite: 'HAVA 52 USC \u00a721083 \u2014 EAC certification renewal on upgrade cycle', risk: 'State decertification, election integrity audit'},\n  CISA_ELECTION_SECURITY_ASSESSMENT: {cite: 'CISA Election Security \u2014 annual risk assessment recommended', risk: 'Critical infrastructure incident response gap'},\n  SECTION_508_VPAT_RENEWAL: {cite: 'Section 508 29 USC \u00a7794d \u2014 VPAT attestation on federal contract renewal', risk: 'Federal contract non-renewal'},\n  FOIA_ANNUAL_REPORT: {cite: '5 USC \u00a7552(e) DOJ annual FOIA report \u2014 Feb 1 deadline', risk: 'DOJ compliance referral'},\n  STATE_PRIVACY_LAW_MAPPING: {cite: 'State open records modernization \u2014 varies by state', risk: 'Litigation exposure on novel state statutes'}\n};\nfor (const row of $input.all()) {\n  const r = row.json;\n  const deadline = new Date(r.deadline_date);\n  const days_remaining = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));\n  const meta = DEADLINE_TYPES[r.deadline_type] || {cite: r.deadline_type, risk: 'Review required'};\n  let urgency = 'NOTICE';\n  if (days_remaining < 0) urgency = 'OVERDUE';\n  else if (days_remaining <= 14) urgency = 'CRITICAL';\n  else if (days_remaining <= 30) urgency = 'URGENT';\n  else if (days_remaining <= 60) urgency = 'WARNING';\n  else if (days_remaining <= 90) urgency = 'NOTICE';\n  else continue;\n  results.push({json: {...r, urgency, days_remaining, ...meta}});\n}\nreturn results.length ? results : [{json:{skip:true}}];"
      },
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "id": "wf3-n3"
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.skip}}",
              "value2": true
            }
          ]
        }
      },
      "name": "Skip if None",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "id": "wf3-n4"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{$vars.SLACK_COMPLIANCE_CHANNEL}}",
          "mode": "id"
        },
        "text": "={{$json.urgency === 'OVERDUE' ? '\ud83d\udea8' : $json.urgency === 'CRITICAL' ? '\ud83d\udd34' : $json.urgency === 'URGENT' ? '\ud83d\udfe0' : '\ud83d\udfe1'}} *{{$json.urgency}}: {{$json.deadline_name}}*\\nDeadline: {{$json.deadline_date}} ({{$json.days_remaining < 0 ? 'OVERDUE ' + Math.abs($json.days_remaining) + 'd' : $json.days_remaining + 'd remaining'}})\\nLaw: {{$json.cite}}\\nRisk: {{$json.risk}}\\nOwner: {{$json.owner}}"
      },
      "name": "Slack #accessibility-compliance",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        1120,
        200
      ],
      "id": "wf3-n5"
    },
    {
      "parameters": {
        "sendTo": "={{$json.owner_email}}",
        "subject": "={{$json.urgency}}: {{$json.deadline_name}} \u2014 {{$json.days_remaining < 0 ? 'OVERDUE' : $json.days_remaining + ' days'}} ({{$json.cite}})",
        "message": "={{$json.urgency}} compliance deadline: {{$json.deadline_name}}\\n\\nDeadline: {{$json.deadline_date}}\\nStatus: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) + ' days' : $json.days_remaining + ' days remaining'}}\\nLaw: {{$json.cite}}\\nRisk if missed: {{$json.risk}}\\nOwner: {{$json.owner}}"
      },
      "name": "Gmail owner",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1120,
        400
      ],
      "id": "wf3-n6"
    }
  ],
  "connections": {
    "Weekdays 8 AM": {
      "main": [
        [
          {
            "node": "Get Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Deadlines": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "Skip if None",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip if None": {
      "main": [
        [],
        [
          {
            "node": "Slack #accessibility-compliance",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Deadline types covered:

  • ADA_TITLE_II_WCAG_STATE — DOJ 28 CFR §35.200, state agencies >50K: April 24 2026
  • ADA_TITLE_II_WCAG_LOCAL — local govts <50K: April 26 2027
  • VRA_SECTION_2_AUDIT — 52 USC §10301 disparate impact audit on system changes
  • HAVA_SYSTEM_CERTIFICATION — EAC certification renewal on upgrade cycles
  • CISA_ELECTION_SECURITY_ASSESSMENT — annual election security assessment
  • SECTION_508_VPAT_RENEWAL — federal contract renewal VPAT attestation
  • FOIA_ANNUAL_REPORT — 5 USC §552(e) DOJ annual report, Feb 1 deadline
  • STATE_PRIVACY_LAW_MAPPING — state open records modernization tracking

Workflow 4: Civic Platform Data Breach & Election Infrastructure Security Alert Pipeline

The compliance trap: a voter PII breach triggers state breach notification laws (most: 72 hours), HAVA §21083 security requirements, and CISA Election ISAC reporting. An election system compromise must be treated as a critical infrastructure incident with immediate response. FOIA system breaches expose request content that may be legally protected under exemptions — a double liability.

{
  "name": "Civic Platform Data Breach & Election Infrastructure Security Alert Pipeline",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "civic-security-alert",
        "options": {}
      },
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "id": "wf4-n1"
    },
    {
      "parameters": {
        "jsCode": "const event = $input.item.json;\nconst INCIDENT_TYPES = {\n  VOTER_PII_BREACH: {severity: 'CRITICAL', notify_72h: true, cite: 'State notification laws + HAVA \u00a721083 + CISA election infrastructure', cisa_reportable: true, response_window_h: 24},\n  ELECTION_SYSTEM_COMPROMISE: {severity: 'CRITICAL', notify_72h: true, cite: 'CISA Election ISAC + HAVA \u00a721083 + state election code', cisa_reportable: true, response_window_h: 1},\n  FOIA_SYSTEM_BREACH: {severity: 'HIGH', notify_72h: true, cite: '5 USC \u00a7552 + state open records act + state breach notification', cisa_reportable: false, response_window_h: 72},\n  GOVERNMENT_PORTAL_DEFACEMENT: {severity: 'HIGH', notify_72h: false, cite: 'CISA web defacement \u2014 election integrity visual impact', cisa_reportable: true, response_window_h: 4},\n  CIVIC_DATA_UNAUTHORIZED_ACCESS: {severity: 'HIGH', notify_72h: true, cite: 'State breach notification + ADA audit trail integrity', cisa_reportable: false, response_window_h: 72},\n  API_CREDENTIAL_EXPOSURE: {severity: 'CRITICAL', notify_72h: true, cite: 'HAVA \u00a721083 security requirement + CIS Controls v8', cisa_reportable: true, response_window_h: 2},\n  ACCESSIBILITY_AUDIT_LOG_DELETION: {severity: 'MEDIUM', notify_72h: false, cite: 'ADA Title II evidence preservation + VRA \u00a72 audit trail', cisa_reportable: false, response_window_h: 48}\n};\nconst meta = INCIDENT_TYPES[event.incident_type] || {severity: 'HIGH', notify_72h: false, cite: 'Review required', cisa_reportable: false, response_window_h: 72};\nconst deadline = new Date(Date.now() + meta.response_window_h * 3600000).toISOString();\nreturn [{json: {...event, ...meta, response_deadline: deadline}}];"
      },
      "name": "Classify Incident",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "id": "wf4-n2"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{$vars.SLACK_SECURITY_CHANNEL}}",
          "mode": "id"
        },
        "text": "\ud83d\udea8 *CIVIC SECURITY INCIDENT: {{$json.severity}}*\\nType: {{$json.incident_type}}\\nPlatform: {{$json.platform_name}}\\nDetected: {{new Date().toISOString()}}\\nResponse deadline: {{$json.response_deadline}} ({{$json.response_window_h}}h window)\\nLaw: {{$json.cite}}\\nCISA Reportable: {{$json.cisa_reportable ? 'YES \u2014 report to CISA Election ISAC' : 'No'}}\\nNotify-72h Required: {{$json.notify_72h ? 'YES \u2014 state breach notification clock running' : 'No'}}"
      },
      "name": "Slack #security-incident",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        680,
        300
      ],
      "id": "wf4-n3"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{$vars.INCIDENT_LOG_SHEET_ID}}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "IncidentLog",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "name": "Log to Postgres Audit Trail",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        680,
        500
      ],
      "id": "wf4-n4"
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Classify Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident": {
      "main": [
        [
          {
            "node": "Slack #security-incident",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log to Postgres Audit Trail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Incident types and clocks:

  • VOTER_PII_BREACH — 24h response window, state notification + CISA Election ISAC reportable
  • ELECTION_SYSTEM_COMPROMISE — 1h response, CISA + HAVA §21083 + state election code
  • FOIA_SYSTEM_BREACH — 72h window, 5 USC §552 + state breach notification
  • GOVERNMENT_PORTAL_DEFACEMENT — 4h, CISA web defacement — election integrity visual impact
  • CIVIC_DATA_UNAUTHORIZED_ACCESS — 72h, state breach notification + ADA audit trail integrity
  • API_CREDENTIAL_EXPOSURE — 2h, HAVA §21083 + CIS Controls v8
  • ACCESSIBILITY_AUDIT_LOG_DELETION — 48h, ADA Title II evidence preservation + VRA §2 audit trail

Workflow 5: Weekly GovTech Platform KPI Dashboard

Every Monday morning, your CEO and compliance officer get a single HTML email covering the metrics that matter for GovTech SaaS: FOIA SLA compliance rate, average response days, election system uptime, accessibility issues open, and MRR trends — with automatic flags when FOIA SLA drops below 90% (DOJ referral risk) or election uptime drops below 99.9% (HAVA risk).

{
  "name": "Weekly GovTech Platform KPI Dashboard",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "name": "Monday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        300
      ],
      "id": "wf5-n1"
    },
    {
      "parameters": {
        "operation": "getAll",
        "documentId": {
          "__rl": true,
          "value": "={{$vars.GOVTECH_METRICS_SHEET_ID}}",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "WeeklyMetrics",
          "mode": "name"
        }
      },
      "name": "Get Platform Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        460,
        300
      ],
      "id": "wf5-n2"
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(r => r.json);\nif (!rows.length) return [{json:{skip:true}}];\nconst cur = rows[rows.length - 1];\nconst prev = rows.length > 1 ? rows[rows.length - 2] : null;\nconst pct = (a, b) => b && b != 0 ? ((a - b) / b * 100).toFixed(1) + '%' : 'N/A';\nconst arrow = v => parseFloat(v) > 0 ? '\u25b2' : parseFloat(v) < 0 ? '\u25bc' : '\u2014';\nconst metrics = {\n  active_customers: cur.active_customers,\n  foia_requests_processed: cur.foia_requests_processed,\n  foia_sla_compliance_pct: cur.foia_sla_compliance_pct,\n  avg_response_days: cur.avg_response_days,\n  accessibility_issues_open: cur.accessibility_issues_open,\n  election_system_uptime_pct: cur.election_system_uptime_pct,\n  new_trials: cur.new_trials,\n  mrr_usd: cur.mrr_usd,\n  week: cur.week\n};\nconst trends = prev ? {\n  foia_sla_trend: pct(cur.foia_sla_compliance_pct, prev.foia_sla_compliance_pct),\n  mrr_trend: pct(cur.mrr_usd, prev.mrr_usd),\n  accessibility_trend: pct(cur.accessibility_issues_open, prev.accessibility_issues_open)\n} : {};\nconst sla_flag = parseFloat(cur.foia_sla_compliance_pct) < 90 ? '\u26a0\ufe0f FOIA SLA <90% \u2014 DOJ referral risk' : '';\nconst uptime_flag = parseFloat(cur.election_system_uptime_pct) < 99.9 ? '\ud83d\udea8 Uptime <99.9% \u2014 HAVA \u00a721083 risk' : '';\nreturn [{json: {...metrics, ...trends, sla_flag, uptime_flag}}];"
      },
      "name": "Compute KPIs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "id": "wf5-n3"
    },
    {
      "parameters": {
        "sendTo": "={{$vars.CEO_EMAIL}}",
        "subject": "GovTech Weekly KPIs \u2014 Week of {{$json.week}}",
        "message": "=<h2>GovTech Platform Weekly Dashboard \u2014 {{$json.week}}</h2>{{$json.sla_flag ? '<p style=\"color:orange\"><strong>' + $json.sla_flag + '</strong></p>' : ''}}{{$json.uptime_flag ? '<p style=\"color:red\"><strong>' + $json.uptime_flag + '</strong></p>' : ''}}<table border='1' cellpadding='6'><tr><th>Metric</th><th>This Week</th><th>Trend</th></tr><tr><td>Active Customers</td><td>{{$json.active_customers}}</td><td>\u2014</td></tr><tr><td>FOIA Requests Processed</td><td>{{$json.foia_requests_processed}}</td><td>\u2014</td></tr><tr><td>FOIA SLA Compliance</td><td>{{$json.foia_sla_compliance_pct}}%</td><td>{{$json.foia_sla_trend || 'N/A'}}</td></tr><tr><td>Avg Response Days</td><td>{{$json.avg_response_days}}</td><td>\u2014</td></tr><tr><td>Accessibility Issues Open</td><td>{{$json.accessibility_issues_open}}</td><td>{{$json.accessibility_trend || 'N/A'}}</td></tr><tr><td>Election System Uptime</td><td>{{$json.election_system_uptime_pct}}%</td><td>\u2014</td></tr><tr><td>New Trials</td><td>{{$json.new_trials}}</td><td>\u2014</td></tr><tr><td>MRR</td><td>${{$json.mrr_usd}}</td><td>{{$json.mrr_trend || 'N/A'}}</td></tr></table>",
        "options": {
          "appendAttribution": false
        }
      },
      "name": "Gmail CEO Report",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        900,
        300
      ],
      "id": "wf5-n4"
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{$vars.SLACK_LEADERSHIP_CHANNEL}}",
          "mode": "id"
        },
        "text": "=\ud83d\udcca *GovTech Weekly KPIs \u2014 {{$json.week}}*\\nFOIA SLA: {{$json.foia_sla_compliance_pct}}% {{$json.foia_sla_trend || ''}} | Avg Response: {{$json.avg_response_days}}d | Election Uptime: {{$json.election_system_uptime_pct}}%\\nActive Customers: {{$json.active_customers}} | MRR: ${{$json.mrr_usd}} {{$json.mrr_trend || ''}}\\n{{$json.sla_flag}} {{$json.uptime_flag}}"
      },
      "name": "Slack one-liner",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.1,
      "position": [
        900,
        500
      ],
      "id": "wf5-n5"
    }
  ],
  "connections": {
    "Monday 8 AM": {
      "main": [
        [
          {
            "node": "Get Platform Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Platform Metrics": {
      "main": [
        [
          {
            "node": "Compute KPIs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute KPIs": {
      "main": [
        [
          {
            "node": "Gmail CEO Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack one-liner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Get the Full FlowKit Bundle

All 5 workflows above, plus 10 more production-ready n8n templates covering CRM automation, lead capture, invoice generation, AI customer support, and more — available at FlowKit on Gumroad.

Individual templates start at $12. The full bundle is $97 at https://stripeai.gumroad.com.


Built with n8n. Self-hosted. All JSON is import-ready — paste into Settings → Import Workflow.

Top comments (0)