If you're building software for government agencies — federal, state, or local — you already know that the compliance overhead is brutal.
FedRAMP authorization. FISMA quarterly reporting. CJIS audit logs. StateRAMP assessments. POA&M tracking that never ends.
Every week, your compliance and ops teams are manually chasing evidence, checking deadlines, and building status reports in spreadsheets.
This post shows 5 production-ready n8n workflows that automate the most time-consuming parts of GovTech SaaS compliance operations — with import-ready JSON you can use today.
Why self-hosted n8n specifically for GovTech?
The FedRAMP boundary question is real: if your SaaS is FedRAMP Authorized and you route compliance workflow data through Zapier or Make.com, you've just sent government data outside your authorization boundary.
- Zapier is not FedRAMP Authorized for SaaS customer data
- Make.com is not FedRAMP Authorized
- Your ATO boundary doesn't extend to their servers
Self-hosted n8n running inside your FedRAMP boundary — AWS GovCloud, Azure Government, or on-prem — keeps all workflow execution data inside the authorization boundary. Git-versioned workflow JSON = FISMA NIST SP 800-53 CM-2 baseline configuration evidence. Execution logs in your Postgres = FISMA AU-2/AU-3 audit log requirements met natively.
| Requirement | Zapier / Make | Self-hosted n8n |
|---|---|---|
| FedRAMP boundary | Outside — ATO violation risk | Inside — stays in-boundary |
| FISMA AU-2 audit log retention | 30-day Zapier limit | Postgres retention = NIST-defined period |
| CJIS Security Policy §5.4.7 | Prohibited — CJIS data cannot leave approved systems | Self-hosted in CJIS-approved CSP = compliant |
| NIST SP 800-53 CM-2 baseline | No config management record | Git-versioned JSON = CM-2 baseline |
| StateRAMP boundary | Outside state boundary | Inside state-managed infrastructure |
| FedRAMP ConMon evidence | No workflow execution logs | Postgres logs = ConMon artifact |
Workflow 1: FedRAMP POA&M & ATO Expiry Alert Pipeline
What it does: Monitors your FedRAMP Plan of Action & Milestones (POA&M) register and ATO expiry dates. Routes alerts to the right team based on urgency tier — from 120-day advance notice to day-of-expiry escalation to the JAB/Agency AO.
Why it matters: An expired ATO means your government customers must stop using your platform. The JAB reauthorization clock runs against your renewal submission. POA&M items past scheduled completion date are a FISMA finding.
{
"name": "FedRAMP POA&M & ATO Expiry Alert Pipeline",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1-5"
}
]
}
},
"name": "Weekday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
260,
300
]
},
{
"parameters": {
"operation": "read",
"documentId": "{{$env.FEDRAMP_POAM_SHEET_ID}}",
"sheetName": "POA&M",
"options": {}
},
"name": "Read POA&M Register",
"type": "n8n-nodes-base.googleSheets",
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst results = [];\nfor (const item of $input.all()) {\n const d = item.json;\n const scheduledCompletion = d.scheduled_completion_date ? new Date(d.scheduled_completion_date) : null;\n const atoExpiry = d.ato_expiry_date ? new Date(d.ato_expiry_date) : null;\n const daysToCompletion = scheduledCompletion ? Math.ceil((scheduledCompletion - today) / 86400000) : null;\n const daysToExpiry = atoExpiry ? Math.ceil((atoExpiry - today) / 86400000) : null;\n let urgency = null;\n let type = null;\n if (daysToCompletion !== null && d.status !== 'CLOSED') {\n if (daysToCompletion < 0) { urgency = 'OVERDUE'; type = 'POAM_ITEM'; }\n else if (daysToCompletion <= 7) { urgency = 'CRITICAL'; type = 'POAM_ITEM'; }\n else if (daysToCompletion <= 14) { urgency = 'URGENT'; type = 'POAM_ITEM'; }\n else if (daysToCompletion <= 30) { urgency = 'WARNING'; type = 'POAM_ITEM'; }\n }\n if (daysToExpiry !== null && d.ato_type) {\n if (daysToExpiry < 0) { urgency = 'EXPIRED'; type = 'ATO_EXPIRY'; }\n else if (daysToExpiry <= 30) { urgency = 'CRITICAL'; type = 'ATO_EXPIRY'; }\n else if (daysToExpiry <= 60) { urgency = 'URGENT'; type = 'ATO_EXPIRY'; }\n else if (daysToExpiry <= 120) { urgency = 'WARNING'; type = 'ATO_EXPIRY'; }\n }\n const alreadySent = d.alert_sent_date === today.toISOString().split('T')[0];\n if (urgency && !alreadySent) {\n results.push({ json: { ...d, urgency, type, daysToCompletion, daysToExpiry } });\n }\n}\nreturn results;"
},
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"position": [
660,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{$json.urgency}}",
"rightValue": "OVERDUE",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"leftValue": "={{$json.urgency}}",
"rightValue": "CRITICAL",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"leftValue": "={{$json.urgency}}",
"rightValue": "EXPIRED",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
}
},
"name": "Filter Critical+",
"type": "n8n-nodes-base.filter",
"position": [
860,
300
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "#fedramp-compliance",
"mode": "name"
},
"text": "=*FedRAMP Alert \u2014 {{$json.urgency}}*\nType: {{$json.type}}\nItem: {{$json.poam_id || $json.system_name}}\nDays remaining: {{$json.daysToCompletion ?? $json.daysToExpiry}}\nOwner: {{$json.responsible_party}}\nATO Type: {{$json.ato_type || 'N/A'}}\nAction required: Review POA&M tracker and update status."
},
"name": "Slack #fedramp-compliance",
"type": "n8n-nodes-base.slack",
"position": [
1060,
240
]
},
{
"parameters": {
"fromEmail": "compliance@yourplatform.com",
"toEmail": "={{$json.responsible_party_email}}",
"subject": "=[{{$json.urgency}}] FedRAMP {{$json.type}} \u2014 {{$json.poam_id || $json.system_name}} requires action",
"emailType": "text",
"message": "=FedRAMP Alert\n\nUrgency: {{$json.urgency}}\nType: {{$json.type}}\nItem: {{$json.poam_id || $json.system_name}}\nDays remaining: {{$json.daysToCompletion ?? $json.daysToExpiry}}\nATO Expiry: {{$json.ato_expiry_date || 'N/A'}}\nScheduled Completion: {{$json.scheduled_completion_date || 'N/A'}}\n\nPlease update the POA&M register and notify the compliance team.\n\nFedRAMP PMO: marketplace.fedramp.gov\nSecurity Team: security@yourplatform.com"
},
"name": "Email Responsible Party",
"type": "n8n-nodes-base.emailSend",
"position": [
1060,
360
]
}
],
"connections": {
"Weekday 7AM": {
"main": [
[
{
"node": "Read POA&M Register",
"type": "main",
"index": 0
}
]
]
},
"Read POA&M Register": {
"main": [
[
{
"node": "Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify Urgency": {
"main": [
[
{
"node": "Filter Critical+",
"type": "main",
"index": 0
}
]
]
},
"Filter Critical+": {
"main": [
[
{
"node": "Slack #fedramp-compliance",
"type": "main",
"index": 0
},
{
"node": "Email Responsible Party",
"type": "main",
"index": 0
}
]
]
}
}
}
Setup: Add a Google Sheet with columns: poam_id, system_name, scheduled_completion_date, ato_expiry_date, ato_type (P-ATO/Agency-ATO/StateRAMP), status, responsible_party, responsible_party_email, alert_sent_date. Set FEDRAMP_POAM_SHEET_ID environment variable.
Workflow 2: FISMA / NIST SP 800-53 Control Assessment Tracker
What it does: Monitors your FISMA control assessment schedule across all NIST SP 800-53 control families (AC, AU, CM, IA, IR, MP, PE, PL, PS, RA, SA, SC, SI). Routes overdue and upcoming assessments to control owners with NIST control family context.
Why it matters: FISMA requires annual assessments (OMB A-130) and quarterly reporting (FISMA metrics). Controls past their assessment date are a finding in the annual FISMA report. Late POA&M entries from missed assessments affect your agency customer's CIO scorecard.
{
"name": "FISMA NIST SP 800-53 Control Assessment Tracker",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"name": "Weekday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
260,
300
]
},
{
"parameters": {
"operation": "read",
"documentId": "{{$env.FISMA_CONTROLS_SHEET_ID}}",
"sheetName": "Controls",
"options": {}
},
"name": "Read Control Inventory",
"type": "n8n-nodes-base.googleSheets",
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst controlFamilyMap = {\n 'AC': 'Access Control', 'AU': 'Audit & Accountability', 'CM': 'Configuration Management',\n 'IA': 'Identification & Authentication', 'IR': 'Incident Response', 'MP': 'Media Protection',\n 'PE': 'Physical & Environmental', 'PL': 'Planning', 'PS': 'Personnel Security',\n 'RA': 'Risk Assessment', 'SA': 'System & Services Acquisition', 'SC': 'System & Communications',\n 'SI': 'System & Information Integrity', 'CA': 'Assessment, Authorization & Monitoring',\n 'CP': 'Contingency Planning', 'MA': 'Maintenance'\n};\nconst results = [];\nfor (const item of $input.all()) {\n const d = item.json;\n const nextAssessment = d.next_assessment_date ? new Date(d.next_assessment_date) : null;\n if (!nextAssessment) continue;\n const daysLeft = Math.ceil((nextAssessment - today) / 86400000);\n let urgency = null;\n if (daysLeft < 0) urgency = 'OVERDUE';\n else if (daysLeft <= 14) urgency = 'CRITICAL';\n else if (daysLeft <= 30) urgency = 'URGENT';\n else if (daysLeft <= 60) urgency = 'WARNING';\n const alreadySent = d.alert_sent_date === today.toISOString().split('T')[0];\n if (urgency && !alreadySent) {\n const prefix = d.control_id ? d.control_id.split('-')[0] : '';\n const family = controlFamilyMap[prefix] || prefix;\n results.push({ json: { ...d, urgency, daysLeft, control_family: family } });\n }\n}\nreturn results;"
},
"name": "Classify Controls",
"type": "n8n-nodes-base.code",
"position": [
660,
300
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "#fisma-compliance",
"mode": "name"
},
"text": "=*FISMA Control Alert \u2014 {{$json.urgency}}*\nControl: {{$json.control_id}} ({{$json.control_family}})\nImpact Baseline: {{$json.impact_baseline}}\nDays to assessment: {{$json.daysLeft}}\nOwner: {{$json.control_owner}}\nLast assessed: {{$json.last_assessment_date}}\nFISMA metric: Review in next quarterly report."
},
"name": "Slack #fisma-compliance",
"type": "n8n-nodes-base.slack",
"position": [
860,
240
]
},
{
"parameters": {
"fromEmail": "compliance@yourplatform.com",
"toEmail": "={{$json.control_owner_email}}",
"subject": "=[{{$json.urgency}}] FISMA Control Assessment Due \u2014 {{$json.control_id}} {{$json.control_family}}",
"emailType": "text",
"message": "=FISMA Control Assessment Required\n\nControl ID: {{$json.control_id}}\nFamily: {{$json.control_family}}\nImpact Baseline: {{$json.impact_baseline}}\nUrgency: {{$json.urgency}}\nDays remaining: {{$json.daysLeft}}\nNext assessment: {{$json.next_assessment_date}}\nLast assessed: {{$json.last_assessment_date}}\n\nPlease complete the assessment and update the control inventory.\n\nNIST SP 800-53A Rev 5 assessment guidance: csrc.nist.gov\nFISMA metrics portal: fisma.io"
},
"name": "Email Control Owner",
"type": "n8n-nodes-base.emailSend",
"position": [
860,
360
]
}
],
"connections": {
"Weekday 8AM": {
"main": [
[
{
"node": "Read Control Inventory",
"type": "main",
"index": 0
}
]
]
},
"Read Control Inventory": {
"main": [
[
{
"node": "Classify Controls",
"type": "main",
"index": 0
}
]
]
},
"Classify Controls": {
"main": [
[
{
"node": "Slack #fisma-compliance",
"type": "main",
"index": 0
},
{
"node": "Email Control Owner",
"type": "main",
"index": 0
}
]
]
}
}
}
Setup: Google Sheet columns: control_id (e.g. AC-2), impact_baseline (LOW/MODERATE/HIGH), next_assessment_date, last_assessment_date, control_owner, control_owner_email, alert_sent_date.
Workflow 3: CJIS Security Policy Compliance Monitor
What it does: If your platform handles Criminal Justice Information (CJI) — arrest records, NCIC data, biometric data, court records — CJIS Security Policy §5.4.7 requires audit log review. This workflow automates the weekly audit log review check, CI/ORI access credential expiry monitoring, and CJIS policy version change detection.
Why it matters: CJIS non-compliance = terminated agency agreement. FBI CJIS Division audits state agencies annually. CI/ORI credentials that expire without renewal = loss of NCIC access for your government customer's officers.
{
"name": "CJIS Security Policy Compliance Monitor",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 6 * * 1"
}
]
}
},
"name": "Monday 6AM (Weekly)",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
260,
300
]
},
{
"parameters": {
"operation": "read",
"documentId": "{{$env.CJIS_CREDENTIALS_SHEET_ID}}",
"sheetName": "CI_ORI_Credentials",
"options": {}
},
"name": "Read CI/ORI Credentials",
"type": "n8n-nodes-base.googleSheets",
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst results = [];\nfor (const item of $input.all()) {\n const d = item.json;\n const expiryDate = d.credential_expiry_date ? new Date(d.credential_expiry_date) : null;\n if (!expiryDate) continue;\n const daysLeft = Math.ceil((expiryDate - today) / 86400000);\n let urgency = null;\n const cjisPolicy = d.cjis_policy_version || '5.9.3';\n if (daysLeft < 0) urgency = 'EXPIRED';\n else if (daysLeft <= 7) urgency = 'CRITICAL';\n else if (daysLeft <= 30) urgency = 'URGENT';\n else if (daysLeft <= 60) urgency = 'WARNING';\n if (urgency) {\n results.push({ json: {\n ...d,\n urgency,\n daysLeft,\n cjis_reference: 'CJIS Security Policy \u00a75.4.7 \u2014 Audit Log Review',\n ncic_impact: daysLeft < 0 ? 'NCIC ACCESS LIKELY SUSPENDED' : 'Risk of NCIC suspension',\n cjisPolicy\n }});\n }\n}\nreturn results;"
},
"name": "Classify CJIS Alerts",
"type": "n8n-nodes-base.code",
"position": [
660,
300
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "#cjis-compliance",
"mode": "name"
},
"text": "=*CJIS Security Alert \u2014 {{$json.urgency}}*\nAgency: {{$json.agency_name}}\nCI/ORI: {{$json.ori_number}}\nCredential Type: {{$json.credential_type}}\nExpiry: {{$json.credential_expiry_date}}\nDays remaining: {{$json.daysLeft}}\nImpact: {{$json.ncic_impact}}\nCJIS reference: {{$json.cjis_reference}}\nCJIS Policy version: {{$json.cjisPolicy}}"
},
"name": "Slack #cjis-compliance",
"type": "n8n-nodes-base.slack",
"position": [
860,
240
]
},
{
"parameters": {
"fromEmail": "compliance@yourplatform.com",
"toEmail": "={{$json.agency_contact_email}}",
"subject": "=[{{$json.urgency}}] CJIS CI/ORI Credential Expiry \u2014 {{$json.agency_name}} ({{$json.ori_number}})",
"emailType": "text",
"message": "=CJIS Compliance Alert\n\nAgency: {{$json.agency_name}}\nORI: {{$json.ori_number}}\nCredential type: {{$json.credential_type}}\nUrgency: {{$json.urgency}}\nExpiry date: {{$json.credential_expiry_date}}\nDays remaining: {{$json.daysLeft}}\nNCIC impact: {{$json.ncic_impact}}\n\nCJIS Policy reference: \u00a75.4.7 Audit Log Review Requirements\nCJIS Policy version: {{$json.cjisPolicy}}\n\nPlease coordinate with your State CSA (Criminal Justice Information Services Agency) to renew credentials.\n\nFBI CJIS Division: www.fbi.gov/services/cjis"
},
"name": "Email Agency Contact",
"type": "n8n-nodes-base.emailSend",
"position": [
860,
360
]
}
],
"connections": {
"Monday 6AM (Weekly)": {
"main": [
[
{
"node": "Read CI/ORI Credentials",
"type": "main",
"index": 0
}
]
]
},
"Read CI/ORI Credentials": {
"main": [
[
{
"node": "Classify CJIS Alerts",
"type": "main",
"index": 0
}
]
]
},
"Classify CJIS Alerts": {
"main": [
[
{
"node": "Slack #cjis-compliance",
"type": "main",
"index": 0
},
{
"node": "Email Agency Contact",
"type": "main",
"index": 0
}
]
]
}
}
}
CJIS note: CJIS Security Policy §5.4.7 requires that audit logs containing CJI be reviewed at least once per week. Self-hosted n8n in a CJIS-approved CSP keeps all credential management data inside your authorized boundary — Zapier/Make cannot be used to process CJI.
Workflow 4: FedRAMP / StateRAMP Authorization Deadline Tracker (12 Deadline Types)
What it does: Tracks 12 categories of FedRAMP and StateRAMP authorization deadlines — from annual ConMon reports to penetration test expiry to SAAR form renewals — and routes escalating alerts based on urgency.
{
"name": "FedRAMP StateRAMP Authorization Deadline Tracker",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1-5"
}
]
}
},
"name": "Weekday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
260,
300
]
},
{
"parameters": {
"operation": "read",
"documentId": "{{$env.FEDRAMP_DEADLINES_SHEET_ID}}",
"sheetName": "Deadlines",
"options": {}
},
"name": "Read Deadline Register",
"type": "n8n-nodes-base.googleSheets",
"position": [
460,
300
]
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst actionMap = {\n 'FEDRAMP_ATO_RENEWAL': { action: 'Submit P-ATO renewal package to JAB', portal: 'marketplace.fedramp.gov' },\n 'FEDRAMP_CONMON_MONTHLY': { action: 'Submit monthly ConMon deliverables to FedRAMP PMO', portal: 'portal.fedramp.gov' },\n 'FEDRAMP_CONMON_ANNUAL': { action: 'Submit annual ConMon report', portal: 'portal.fedramp.gov' },\n 'FEDRAMP_PENETRATION_TEST': { action: 'Schedule annual penetration test with accredited 3PAO', portal: '3pao.fedramp.gov' },\n 'FEDRAMP_SAR_UPDATE': { action: 'Update Security Assessment Report with 3PAO', portal: 'marketplace.fedramp.gov' },\n 'FEDRAMP_SAAR_RENEWAL': { action: 'Renew System Authorization Access Request', portal: 'portal.fedramp.gov' },\n 'FEDRAMP_INCIDENT_REPORT': { action: 'Submit incident report to US-CERT within 1h/72h per severity', portal: 'us-cert.gov' },\n 'FEDRAMP_POAM_SUBMISSION': { action: 'Submit POA&M update to authorizing agency', portal: 'portal.fedramp.gov' },\n 'STATERAMP_ATO_RENEWAL': { action: 'Submit StateRAMP ATO renewal to state PMO', portal: 'stateramp.org' },\n 'STATERAMP_CONMON_QUARTERLY': { action: 'Submit quarterly StateRAMP ConMon report', portal: 'stateramp.org' },\n 'FISMA_QUARTERLY_METRICS': { action: 'Submit FISMA quarterly metrics to OMB via CyberScope', portal: 'cyberscope.gov' },\n 'CISA_VULNERABILITY_REPORT': { action: 'Report known exploited vulnerability remediation to CISA KEV', portal: 'cisa.gov/kev' }\n};\nconst results = [];\nfor (const item of $input.all()) {\n const d = item.json;\n const deadlineDate = d.deadline_date ? new Date(d.deadline_date) : null;\n if (!deadlineDate) continue;\n const daysLeft = Math.ceil((deadlineDate - today) / 86400000);\n let urgency = null;\n if (daysLeft < 0) urgency = 'OVERDUE';\n else if (daysLeft <= 14) urgency = 'CRITICAL';\n else if (daysLeft <= 30) urgency = 'URGENT';\n else if (daysLeft <= 60) urgency = 'WARNING';\n else if (daysLeft <= 90) urgency = 'NOTICE';\n const alreadySent = d.alert_sent_date === today.toISOString().split('T')[0];\n if (urgency && !alreadySent) {\n const info = actionMap[d.deadline_type] || { action: 'Review deadline requirements', portal: 'fedramp.gov' };\n results.push({ json: { ...d, urgency, daysLeft, ...info } });\n }\n}\nreturn results;"
},
"name": "Classify Deadlines",
"type": "n8n-nodes-base.code",
"position": [
660,
300
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{$json.urgency}}",
"rightValue": "OVERDUE",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"renameOutput": true,
"outputKey": "OVERDUE"
},
{
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{$json.urgency}}",
"rightValue": "CRITICAL",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"renameOutput": true,
"outputKey": "CRITICAL"
},
{
"conditions": {
"options": {
"caseSensitive": true
},
"conditions": [
{
"leftValue": "={{$json.urgency}}",
"rightValue": "URGENT",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"renameOutput": true,
"outputKey": "URGENT"
}
]
}
},
"name": "Route by Urgency",
"type": "n8n-nodes-base.switch",
"position": [
860,
300
]
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "#fedramp-compliance",
"mode": "name"
},
"text": "=*FedRAMP/StateRAMP OVERDUE* \u2014 {{$json.deadline_type}}\nSystem: {{$json.system_name}}\nDays overdue: {{Math.abs($json.daysLeft)}}\nDeadline was: {{$json.deadline_date}}\nRequired action: {{$json.action}}\nPortal: {{$json.portal}}\n@channel \u2014 Immediate action required."
},
"name": "Slack Overdue Alert",
"type": "n8n-nodes-base.slack",
"position": [
1060,
200
]
},
{
"parameters": {
"fromEmail": "compliance@yourplatform.com",
"toEmail": "={{$json.compliance_lead_email}}",
"subject": "=[{{$json.urgency}}] FedRAMP Deadline \u2014 {{$json.deadline_type}} ({{$json.daysLeft}} days)",
"emailType": "text",
"message": "=FedRAMP/StateRAMP Deadline Alert\n\nSystem: {{$json.system_name}}\nDeadline type: {{$json.deadline_type}}\nUrgency: {{$json.urgency}}\nDeadline date: {{$json.deadline_date}}\nDays remaining: {{$json.daysLeft}}\nRequired action: {{$json.action}}\nPortal: {{$json.portal}}\n\nPlease coordinate with your 3PAO and authorizing official."
},
"name": "Email Compliance Lead",
"type": "n8n-nodes-base.emailSend",
"position": [
1060,
380
]
}
],
"connections": {
"Weekday 7AM": {
"main": [
[
{
"node": "Read Deadline Register",
"type": "main",
"index": 0
}
]
]
},
"Read Deadline Register": {
"main": [
[
{
"node": "Classify Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Classify Deadlines": {
"main": [
[
{
"node": "Route by Urgency",
"type": "main",
"index": 0
}
]
]
},
"Route by Urgency": {
"main": [
[
{
"node": "Slack Overdue Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Email Compliance Lead",
"type": "main",
"index": 0
}
],
[
{
"node": "Email Compliance Lead",
"type": "main",
"index": 0
}
]
]
}
}
}
12 deadline types supported: FEDRAMP_ATO_RENEWAL, FEDRAMP_CONMON_MONTHLY, FEDRAMP_CONMON_ANNUAL, FEDRAMP_PENETRATION_TEST, FEDRAMP_SAR_UPDATE, FEDRAMP_SAAR_RENEWAL, FEDRAMP_INCIDENT_REPORT, FEDRAMP_POAM_SUBMISSION, STATERAMP_ATO_RENEWAL, STATERAMP_CONMON_QUARTERLY, FISMA_QUARTERLY_METRICS, CISA_VULNERABILITY_REPORT.
Workflow 5: Weekly GovTech Platform KPI Dashboard
What it does: Monday 8AM email to your leadership team with a color-coded GovTech platform health report — ATO status across your customer base, open POA&M count, FISMA score trend, active CJIS agencies, pending ConMon deliverables.
{
"name": "Weekly GovTech Platform KPI Dashboard",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
},
"name": "Monday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
260,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT\n COUNT(*) FILTER (WHERE ato_status = 'AUTHORIZED') AS ato_authorized,\n COUNT(*) FILTER (WHERE ato_status = 'IN_PROGRESS') AS ato_in_progress,\n COUNT(*) FILTER (WHERE ato_status = 'EXPIRED') AS ato_expired,\n COUNT(*) FILTER (WHERE ato_type = 'FEDRAMP_PATO') AS fedramp_pato_count,\n COUNT(*) FILTER (WHERE ato_type = 'STATERAMP') AS stateramp_count,\n COUNT(*) FILTER (WHERE cjis_enabled = TRUE) AS cjis_active_agencies\nFROM govtech_customers WHERE active = TRUE",
"options": {}
},
"name": "Query Customer ATO Status",
"type": "n8n-nodes-base.postgres",
"position": [
460,
240
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT\n COUNT(*) FILTER (WHERE status != 'CLOSED' AND scheduled_completion_date < CURRENT_DATE) AS poam_overdue,\n COUNT(*) FILTER (WHERE status != 'CLOSED' AND scheduled_completion_date BETWEEN CURRENT_DATE AND CURRENT_DATE + 30) AS poam_due_30d,\n COUNT(*) FILTER (WHERE status != 'CLOSED') AS poam_open_total,\n COUNT(*) FILTER (WHERE conmon_submitted_date >= DATE_TRUNC('month', CURRENT_DATE)) AS conmon_submitted_this_month,\n AVG(fisma_score) AS avg_fisma_score\nFROM govtech_compliance_events WHERE created_at >= NOW() - INTERVAL '90 days'",
"options": {}
},
"name": "Query Compliance Metrics",
"type": "n8n-nodes-base.postgres",
"position": [
460,
420
]
},
{
"parameters": {
"jsCode": "const customers = $('Query Customer ATO Status').first().json;\nconst compliance = $('Query Compliance Metrics').first().json;\nconst poamOverdue = parseInt(customers.poam_overdue ?? compliance.poam_overdue ?? 0);\nconst atoExpired = parseInt(customers.ato_expired ?? 0);\nconst avgFisma = parseFloat(compliance.avg_fisma_score ?? 0).toFixed(1);\nconst subjectFlags = [];\nif (poamOverdue > 0) subjectFlags.push(`[POA&M: ${poamOverdue} OVERDUE]`);\nif (atoExpired > 0) subjectFlags.push(`[ATO EXPIRED: ${atoExpired}]`);\nconst subject = subjectFlags.length > 0\n ? `${subjectFlags.join(' ')} Weekly GovTech KPI \u2014 ${new Date().toISOString().split('T')[0]}`\n : `Weekly GovTech KPI \u2014 ${new Date().toISOString().split('T')[0]}`;\nconst html = `\n<h2>GovTech Platform Weekly KPI</h2>\n<p>Week ending ${new Date().toISOString().split('T')[0]}</p>\n<table border=\"1\" cellpadding=\"6\" cellspacing=\"0\">\n <tr><th>Metric</th><th>Value</th><th>Status</th></tr>\n <tr><td>ATO Authorized</td><td>${customers.ato_authorized || 0}</td><td style=\"color:green\">OK</td></tr>\n <tr><td>ATO In Progress</td><td>${customers.ato_in_progress || 0}</td><td style=\"color:orange\">MONITOR</td></tr>\n <tr style=\"background:${atoExpired > 0 ? '#ffe0e0' : '#fff'}\">\n <td>ATO Expired</td><td>${atoExpired}</td><td style=\"color:${atoExpired > 0 ? 'red' : 'green'}\">${atoExpired > 0 ? 'ACTION REQUIRED' : 'OK'}</td></tr>\n <tr><td>FedRAMP P-ATO Customers</td><td>${customers.fedramp_pato_count || 0}</td><td>-</td></tr>\n <tr><td>StateRAMP Customers</td><td>${customers.stateramp_count || 0}</td><td>-</td></tr>\n <tr><td>CJIS-Enabled Agencies</td><td>${customers.cjis_active_agencies || 0}</td><td>-</td></tr>\n <tr style=\"background:${poamOverdue > 0 ? '#ffe0e0' : '#fff'}\">\n <td>POA&M Overdue Items</td><td>${compliance.poam_overdue || 0}</td><td style=\"color:${poamOverdue > 0 ? 'red' : 'green'}\">${poamOverdue > 0 ? 'ACTION REQUIRED' : 'OK'}</td></tr>\n <tr><td>POA&M Due in 30 Days</td><td>${compliance.poam_due_30d || 0}</td><td style=\"color:orange\">MONITOR</td></tr>\n <tr><td>POA&M Open Total</td><td>${compliance.poam_open_total || 0}</td><td>-</td></tr>\n <tr><td>ConMon Submitted This Month</td><td>${compliance.conmon_submitted_this_month || 0}</td><td>-</td></tr>\n <tr><td>Avg FISMA Score</td><td>${avgFisma}</td><td style=\"color:${avgFisma >= 80 ? 'green' : avgFisma >= 60 ? 'orange' : 'red'}\">${avgFisma >= 80 ? 'STRONG' : avgFisma >= 60 ? 'ACCEPTABLE' : 'NEEDS IMPROVEMENT'}</td></tr>\n</table>`;\nreturn [{ json: { html, subject } }];"
},
"name": "Build KPI Report",
"type": "n8n-nodes-base.code",
"position": [
680,
300
]
},
{
"parameters": {
"fromEmail": "compliance@yourplatform.com",
"toEmail": "ceo@yourplatform.com",
"ccEmail": "cto@yourplatform.com",
"bccEmail": "ciso@yourplatform.com",
"subject": "={{$json.subject}}",
"emailType": "html",
"message": "={{$json.html}}"
},
"name": "Email CEO (BCC CISO)",
"type": "n8n-nodes-base.emailSend",
"position": [
880,
300
]
}
],
"connections": {
"Monday 8AM": {
"main": [
[
{
"node": "Query Customer ATO Status",
"type": "main",
"index": 0
},
{
"node": "Query Compliance Metrics",
"type": "main",
"index": 0
}
]
]
},
"Query Customer ATO Status": {
"main": [
[
{
"node": "Build KPI Report",
"type": "main",
"index": 0
}
]
]
},
"Query Compliance Metrics": {
"main": [
[
{
"node": "Build KPI Report",
"type": "main",
"index": 0
}
]
]
},
"Build KPI Report": {
"main": [
[
{
"node": "Email CEO (BCC CISO)",
"type": "main",
"index": 0
}
]
]
}
}
}
Subject line auto-flags: [POA&M: N OVERDUE] and [ATO EXPIRED: N] appear in the email subject when action is needed — so the CEO sees it even without opening the email. CISO is BCC'd to close the FISMA AU-2 governance loop.
What these workflows replace
Manual work that GovTech compliance teams do today:
- Weekly spreadsheet review of POA&M items across 40+ controls
- Calendar reminders for ConMon submission dates (monthly for FedRAMP, quarterly for StateRAMP)
- Manual email chains to control owners with assessment reminders
- CJIS credential expiry tracking in a shared doc that nobody owns
- Executive status reports built from 3 different spreadsheets every Monday
These workflows eliminate all of that. The compliance team focuses on evidence collection and control improvement — not on tracking whether the tracker was updated.
Get the templates
These 5 workflows are part of the FlowKit n8n template library at stripeai.gumroad.com.
Each template includes:
- Import-ready workflow JSON
- Setup guide with all environment variables documented
- Google Sheet templates for POA&M, control inventory, and deadline registers
If you're building GovTech or CivicTech SaaS and want to discuss how n8n fits your specific FedRAMP boundary setup, drop a comment below.
Top comments (0)