This workflow corresponds to n8n.io template #16378 — we link there as the canonical source.
This workflow follows the Error Trigger → Gmail recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "QtihuOtFZ2LMWsZx",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Construction Incident Triage & Escalation Management System",
"tags": [],
"nodes": [
{
"id": "1b329d67-828a-48fa-93c7-1d9c923e16ac",
"name": "Overview: Construction Incident Bridge",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3536,
4336
],
"parameters": {
"width": 708,
"height": 532,
"content": "## \ud83c\udfd7\ufe0f Construction Site Incident Bridge\n\n### How it works\nThis workflow acts as a real-time incident hub for construction projects. It accepts incoming issue webhooks from Procore or Fieldwire, normalises the payload into a consistent structure, then uses GPT-4o-mini to classify each incident by type (Safety, RFI, Quality, etc.) and urgency (Critical \u2192 Low). Based on urgency, it routes alerts to Slack and email, and logs every incident to Google Sheets. A separate schedule trigger fires at 6 PM daily to pull all open incidents and send a summarised digest to Slack.\n\n### Setup steps\n1. **Webhook** \u2014 Copy the webhook URL and register it in Procore or Fieldwire as your incident-created event endpoint.\n2. **OpenAI** \u2014 Add your OpenAI API key credential under the GPT-4o classify node.\n3. **Slack** \u2014 Connect a Slack OAuth2 account and update the channel IDs in all three Slack nodes to match your project channels.\n4. **Gmail** \u2014 Connect a Gmail OAuth2 account and replace the PM email placeholder with the real address.\n5. **Google Sheets** \u2014 Connect Google Sheets OAuth2 and point the two Sheets nodes to your own incident log spreadsheet.\n6. **Error Slack channel** \u2014 Update `YOUR_SLACK_CHANNEL_ID` in the error handler node.\n7. Run a test using the pinned payload (scaffolding fall scenario) to verify the full flow end-to-end."
},
"typeVersion": 1
},
{
"id": "c40596b1-bd50-4544-8eb7-b6ac713e6faa",
"name": "Section: Intake & Normalisation",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2448,
4800
],
"parameters": {
"color": 7,
"width": 480,
"height": 444,
"content": "## \ud83d\udce1 Intake & Normalisation\nAccepts webhook events from Procore, Fieldwire, or any compatible form. The Code node maps inconsistent field names (`body`, `subject`, `area`, etc.) into one clean incident object before anything else runs."
},
"typeVersion": 1
},
{
"id": "40476da3-3e44-4db1-a920-27569f57a871",
"name": "Section: AI Classification",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1936,
4768
],
"parameters": {
"color": 7,
"width": 512,
"height": 476,
"content": "## \ud83e\udd16 AI Classification\nGPT-4o-mini reads the incident title, description, location, and raw category, then returns a structured JSON object with `issueType`, `urgency`, `summary`, `recommendedAction`, and `tags`. The follow-up Code node merges this back with the original payload."
},
"typeVersion": 1
},
{
"id": "133ae89a-56bc-45d9-b948-1f563680ac73",
"name": "Section: Urgency Router",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1392,
4768
],
"parameters": {
"color": 7,
"width": 380,
"height": 472,
"content": "## \ud83d\udd00 Urgency Router\nThe Switch node splits incidents into four lanes \u2014 Critical, High, Medium, and Low (fallback). Critical incidents trigger both Slack @channel and a PM email. High triggers a Slack notice. Medium and Low go straight to the log."
},
"typeVersion": 1
},
{
"id": "2cc3735b-118a-4c62-a8d9-074fd1b3792e",
"name": "Section: Critical Notifications",
"type": "n8n-nodes-base.stickyNote",
"position": [
-944,
4352
],
"parameters": {
"color": 7,
"width": 380,
"height": 712,
"content": "## \ud83d\udea8 Critical Notifications\nFor Critical incidents only. Sends a rich Slack block message with `<!channel>` mention and a formatted HTML email to the PM. Both fire in parallel so neither delays the other."
},
"typeVersion": 1
},
{
"id": "264b3cab-61e9-4a00-b8f8-61301a8d9514",
"name": "Section: Logging & Standard Alerts",
"type": "n8n-nodes-base.stickyNote",
"position": [
-960,
5088
],
"parameters": {
"color": 7,
"width": 380,
"height": 620,
"content": "## \ud83d\udccb Logging & Standard Alerts\nEvery incident regardless of urgency is appended to the Google Sheets log with status set to `open`. High-urgency incidents also receive a Slack block notice. Medium and Low are logged silently for the daily digest."
},
"typeVersion": 1
},
{
"id": "e629ec77-56c0-43c9-9e09-7436b169ba71",
"name": "Section: Daily Digest",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2384,
5296
],
"parameters": {
"color": 7,
"width": 964,
"height": 364,
"content": "## \ud83d\udcc5 Daily Digest (6 PM)\nA scheduled trigger reads all rows from the incident log and builds a grouped Slack summary \u2014 sorted by urgency \u2014 showing ID, title, type, location, and assignee for every open item. Runs automatically at 18:00 server time each day."
},
"typeVersion": 1
},
{
"id": "01fd6384-f415-406c-8acc-ea1588ae9c68",
"name": "Section: Credentials & Security",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1600,
5904
],
"parameters": {
"color": 3,
"width": 276,
"height": 248,
"content": "## \ud83d\udd10 Credentials & Security\nUse OAuth2 for Slack, Gmail, and Google Sheets. OpenAI uses an API key credential. Replace all emails, channel IDs, and spreadsheet IDs with your own before sharing. Never commit real credentials to a shared template."
},
"typeVersion": 1
},
{
"id": "177c23e5-b62d-4c6b-a7ce-46de8532a9ad",
"name": "Webhook \u2013 Incoming Incident",
"type": "n8n-nodes-base.webhook",
"position": [
-2336,
5040
],
"parameters": {
"path": "ccf539aa-f928-4e1f-aeff-f604d7b6ec7f",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 1
},
{
"id": "c8984533-627c-4116-a55d-07dbf2614ffa",
"name": "Normalise Payload",
"type": "n8n-nodes-base.code",
"position": [
-2096,
5040
],
"parameters": {
"jsCode": "// Normalise payload from Procore OR Fieldwire OR simulated form\nconst body = $input.item.json.body ?? $input.item.json;\n\nconst issue = {\n id: body.id ?? body.issue_id ?? body.record_id ?? `SIM-${Date.now()}`,\n title: body.title ?? body.subject ?? body.name ?? 'Untitled Issue',\n description: body.description ?? body.body ?? body.details ?? '',\n location: body.location ?? body.area ?? body.zone ?? 'Unknown',\n reporter: body.created_by ?? body.reported_by ?? body.submitter ?? 'Unknown',\n assignee: body.assignee ?? body.assigned_to ?? body.responsible ?? '',\n source: body.source ?? body.tool ?? 'Procore/Fieldwire',\n rawType: body.type ?? body.category ?? body.issue_type ?? '',\n receivedAt: new Date().toISOString()\n};\n\nreturn [{ json: issue }];\n"
},
"typeVersion": 2
},
{
"id": "b3fae50a-a930-4b8b-b24d-03dd0bccfb93",
"name": "GPT-4o \u2013 Classify Incident",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
-1888,
5040
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"messages": {
"values": [
{
"content": "=You are a construction-site incident classifier. Given the following issue details, return ONLY a valid JSON object with no markdown, no explanation, no code fences.\n\nTitle: {{ $json.title }}\nDescription: {{ $json.description }}\nLocation: {{ $json.location }}\nReporter: {{ $json.reporter }}\nRaw Category: {{ $json.rawType }}\n\nReturn this exact JSON structure:\n{\n \"issueType\": \"<one of: Safety | RFI | Quality | Punch | Delay | Equipment | Environmental | Other>\",\n \"urgency\": \"<one of: Critical | High | Medium | Low>\",\n \"summary\": \"<one concise sentence, max 120 chars>\",\n \"recommendedAction\": \"<one concise sentence on what to do next>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"]\n}\n\nRules:\n- Safety incidents with injury risk or OSHA implications \u2192 urgency = Critical\n- RFIs blocking work or critical path \u2192 urgency = High\n- Keywords like: fatality, injury, collapse, fire, gas leak, electric shock \u2192 urgency = Critical\n- Return ONLY the JSON object. No extra text."
}
]
}
},
"typeVersion": 1
},
{
"id": "3c28f9a8-3939-4c28-8837-7e2c0d6a0937",
"name": "Merge Classification",
"type": "n8n-nodes-base.code",
"position": [
-1552,
5040
],
"parameters": {
"jsCode": "const raw = $input.item.json.message?.content ?? $input.item.json.choices?.[0]?.message?.content ?? '{}';\nlet classification;\ntry {\n classification = JSON.parse(raw);\n} catch(e) {\n // Attempt to extract JSON from any stray text\n const match = raw.match(/\\{[\\s\\S]*\\}/);\n classification = match ? JSON.parse(match[0]) : { issueType:'Other', urgency:'Low', summary: raw.slice(0,120), recommendedAction:'Review manually', tags:[] };\n}\n\n// Merge classification back with the normalised issue from node 3\n// $('Normalise Payload') gives us the upstream data\nconst issue = $('Normalise Payload').item.json;\n\nreturn [{ json: { ...issue, ...classification } }];\n"
},
"typeVersion": 2
},
{
"id": "5b9cde59-221a-434d-b753-b73a48c0efbb",
"name": "Switch \u2013 Route by Urgency",
"type": "n8n-nodes-base.switch",
"position": [
-1328,
5008
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "critical",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "1a904291-be32-41ce-a9cb-6874f608830f",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.urgency }}",
"rightValue": "Critical"
}
]
},
"renameOutput": true
},
{
"outputKey": "high",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "85692d6f-2117-4227-a2cf-401768f8fe1b",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.urgency }}",
"rightValue": "High"
}
]
},
"renameOutput": true
},
{
"outputKey": "medium",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f2f4d9ed-ad1c-455a-93f4-6ca1e6c80997",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.urgency }}",
"rightValue": "Medium"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "2860a758-c64f-4de7-a2f9-9f8de960170d",
"name": "Slack \u2013 Critical @channel Alert",
"type": "n8n-nodes-base.slack",
"position": [
-816,
4608
],
"parameters": {
"text": "={{ $json.title }}",
"select": "channel",
"blocksUi": "=[\n {\n \"type\": \"header\",\n \"text\": {\n \"type\": \"plain_text\",\n \"text\": \"\ud83d\udea8 CRITICAL INCIDENT \u2013 Immediate Attention Required\"\n }\n },\n {\n \"type\": \"section\",\n \"fields\": [\n { \"type\": \"mrkdwn\", \"text\": \"*ID:*\\n{{ $json.id }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Type:*\\n{{ $json.issueType }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Location:*\\n{{ $json.location }}\" },\n { \"type\": \"mrkdwn\", \"text\": \"*Reporter:*\\n{{ $json.reporter }}\" }\n ]\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Summary:*\\n{{ $json.summary }}\" }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Recommended Action:*\\n{{ $json.recommendedAction }}\" }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"*Assignee:* {{ $json.assignee }}\\n*Tags:* {{ $json.tags.join(', ') }}\\n*Received:* {{ $json.receivedAt }}\" }\n },\n {\n \"type\": \"section\",\n \"text\": { \"type\": \"mrkdwn\", \"text\": \"<!channel> Please respond immediately.\" }\n }\n]",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CRITICAL_CHANNEL_ID",
"cachedResultName": "your-incidents-critical"
},
"messageType": "block",
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.2
},
{
"id": "619fd0d0-ba7f-42c3-9414-3c346ec2eaf8",
"name": "Gmail \u2013 Critical Email to PM",
"type": "n8n-nodes-base.gmail",
"position": [
-816,
4864
],
"parameters": {
"sendTo": "pm@example.com",
"message": "=<h2 style=\"color:#c0392b;\">\ud83d\udea8 Critical Incident Alert</h2>\n<table style=\"border-collapse:collapse;width:100%;font-family:sans-serif;font-size:14px;\">\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">ID</td><td style=\"padding:6px;\">{{ $json.id }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Title</td><td style=\"padding:6px;\">{{ $json.title }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Type</td><td style=\"padding:6px;\">{{ $json.issueType }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Location</td><td style=\"padding:6px;\">{{ $json.location }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Reporter</td><td style=\"padding:6px;\">{{ $json.reporter }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Assignee</td><td style=\"padding:6px;\">{{ $json.assignee || 'Unassigned' }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Summary</td><td style=\"padding:6px;\">{{ $json.summary }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Recommended Action</td><td style=\"padding:6px;\">{{ $json.recommendedAction }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Tags</td><td style=\"padding:6px;\">{{ $json.tags.join(', ') }}</td></tr>\n <tr><td style=\"padding:6px;font-weight:bold;background:#f8f8f8;\">Received At</td><td style=\"padding:6px;\">{{ $json.receivedAt }}</td></tr>\n</table>\n<p style=\"color:#888;font-size:12px;\">Sent by Incident Bridge automation \u2014 reply to this email to acknowledge.</p>",
"options": {},
"subject": "=\ud83d\udea8 CRITICAL INCIDENT [{{ $json.id }}]: {{ $json.title }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "0a7ce5c5-f8e4-4bc0-aa81-26712d8b9779",
"name": "Slack \u2013 High Priority Notice",
"type": "n8n-nodes-base.slack",
"position": [
-800,
5520
],
"parameters": {
"select": "channel",
"blocksUi": {
"blocksValues": [
{
"text": {
"text": "\u26a0\ufe0f High Priority Issue Logged",
"type": "plain_text"
},
"type": "header"
},
{
"type": "section",
"fields": [
{
"text": "*ID:*\n{{ $json.id }}",
"type": "mrkdwn"
},
{
"text": "*Type:*\n{{ $json.issueType }}",
"type": "mrkdwn"
},
{
"text": "*Location:*\n{{ $json.location }}",
"type": "mrkdwn"
},
{
"text": "*Assignee:*\n{{ $json.assignee || 'TBD' }}",
"type": "mrkdwn"
}
]
},
{
"text": {
"text": "*Summary:* {{ $json.summary }}\n*Action:* {{ $json.recommendedAction }}",
"type": "mrkdwn"
},
"type": "section"
}
]
},
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "your-incidents-high"
},
"messageType": "block",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.2
},
{
"id": "3efa2a94-1923-4996-80b3-c4c3be289b07",
"name": "Google Sheets \u2013 Log Incident",
"type": "n8n-nodes-base.googleSheets",
"position": [
-800,
5312
],
"parameters": {
"columns": {
"value": {
"ID": "={{ $json.id }}",
"Tags": "={{ $json.tags.join(', ') }}",
"Title": "={{ $json.title }}",
"Source": "={{ $json.source }}",
"Status": "open",
"Summary": "={{ $json.summary }}",
"Urgency": "={{ $json.urgency }}",
"Assignee": "={{ $json.assignee }}",
"Location": "={{ $json.location }}",
"Reporter": "={{ $json.reporter }}",
"Issue Type": "={{ $json.issueType }}",
"Received At": "={{ $json.receivedAt }}",
"Last Updated": "={{ $json.receivedAt }}",
"Recommended Action": "={{ $json.recommendedAction }}"
},
"schema": [
{
"id": "ID",
"type": "string",
"display": true,
"required": false,
"displayName": "ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Title",
"type": "string",
"display": true,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Issue Type",
"type": "string",
"display": true,
"required": false,
"displayName": "Issue Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Urgency",
"type": "string",
"display": true,
"required": false,
"displayName": "Urgency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Summary",
"type": "string",
"display": true,
"required": false,
"displayName": "Summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Recommended Action",
"type": "string",
"display": true,
"required": false,
"displayName": "Recommended Action",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Location",
"type": "string",
"display": true,
"required": false,
"displayName": "Location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reporter",
"type": "string",
"display": true,
"required": false,
"displayName": "Reporter",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Assignee",
"type": "string",
"display": true,
"required": false,
"displayName": "Assignee",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Tags",
"type": "string",
"display": true,
"required": false,
"displayName": "Tags",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Source",
"type": "string",
"display": true,
"required": false,
"displayName": "Source",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Received At",
"type": "string",
"display": true,
"required": false,
"displayName": "Received At",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Updated",
"type": "string",
"display": true,
"required": false,
"displayName": "Last Updated",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "log incident",
"cachedResultName": "log incident"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEETS_DOCUMENT_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEETS_DOCUMENT_ID/edit",
"cachedResultName": "Incident Log \u2013 Sample Sheet"
}
},
"typeVersion": 4.4
},
{
"id": "205b6a4e-2f05-43c4-8076-6082c22faf40",
"name": "Schedule \u2013 6 PM Daily Digest",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-2304,
5424
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 18 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "e597f46e-f2ff-4530-9fd1-1d63efa8d9c8",
"name": "Sheets \u2013 Fetch Open Incidents",
"type": "n8n-nodes-base.googleSheets",
"position": [
-2064,
5424
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "log incident",
"cachedResultName": "log incident"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEETS_DOCUMENT_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEETS_DOCUMENT_ID/edit",
"cachedResultName": "Incident Log \u2013 Sample Sheet"
}
},
"typeVersion": 4.4
},
{
"id": "34f8e35c-3e75-4679-a9b2-de296a0aee45",
"name": "Slack \u2013 Send Daily Digest",
"type": "n8n-nodes-base.slack",
"position": [
-1584,
5424
],
"parameters": {
"text": "={{ $json.digestText }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_DIGEST_CHANNEL_ID",
"cachedResultName": "your-daily-digest"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.2
},
{
"id": "57a2430f-a9e4-4289-a4e5-89c333e78592",
"name": "Build Digest Message1",
"type": "n8n-nodes-base.code",
"position": [
-1808,
5424
],
"parameters": {
"jsCode": "const items = $input.all();\n\nif (!items || items.length === 0) {\n return [{ json: { digestText: '\\u2705 No open incidents at this time. All clear!', count: 0 } }];\n}\n\nconst rows = items.map(i => i.json);\n\n// Group by urgency\nconst groups = { Critical: [], High: [], Medium: [], Low: [], Other: [] };\nrows.forEach(r => {\n const u = r.Urgency ?? r.urgency ?? 'Other';\n (groups[u] ?? groups.Other).push(r);\n});\n\nconst today = new Date().toLocaleDateString('en-US', { weekday:'long', year:'numeric', month:'long', day:'numeric' });\n\nlet text = '*\\uD83D\\uDCCB Daily Incident Digest - ' + today + '*\\n*Total Open Items: ' + rows.length + '*\\n\\n';\n\nconst urgencyEmoji = { Critical:'\\uD83D\\uDEA8', High:'\\u26A0\\uFE0F', Medium:'\\uD83D\\uDD35', Low:'\\u26AA', Other:'\\u2753' };\n\nfor (const [urgency, issues] of Object.entries(groups)) {\n if (issues.length === 0) continue;\n const emoji = urgencyEmoji[urgency] || '\\u2753';\n text += emoji + ' *' + urgency + ' (' + issues.length + ')*\\n';\n issues.forEach(function(iss) {\n const id = iss.ID || iss.id || 'N/A';\n const title = iss.Title || iss.title || 'Untitled';\n const type = iss['Issue Type'] || iss.issueType || 'Unknown';\n const loc = iss.Location || iss.location || 'Unknown';\n const assignee = iss.Assignee || iss.assignee || 'Unassigned';\n text += ' - [' + id + '] ' + title + ' | ' + type + ' | ' + loc + ' | ' + assignee + '\\n';\n });\n text += '\\n';\n}\n\ntext += '_Reply to any item in Slack or update status in Google Sheets._';\n\nreturn [{ json: { digestText: text, count: rows.length } }];\n"
},
"typeVersion": 2
},
{
"id": "94746f80-183c-47ea-af92-ec2f60f6e1cd",
"name": "Section: Error Handler",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2416,
5776
],
"parameters": {
"color": 7,
"width": 556,
"height": 368,
"content": "## \u26a0\ufe0f Error Handler\nCatches any failure in the workflow and posts a Slack alert with the error message, failing node name, and execution ID. Wire the error output of any critical node here to prevent silent failures going unnoticed."
},
"typeVersion": 1
},
{
"id": "83ef1c0b-c4b9-4ffc-bad2-28d1047ac104",
"name": "Slack \u2013 Send Error Alert",
"type": "n8n-nodes-base.slack",
"position": [
-2064,
5936
],
"parameters": {
"text": "=\u26a0\ufe0f Prescription workflow error: {{ $json.error.message }}\nNode: {{ $json.execution.lastNodeExecuted }}\nExecution ID: {{ $json.execution.id }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "YOUR_SLACK_CHANNEL_ID",
"cachedResultName": "pharmacy-alerts"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "5cb15ff7-5d79-4c16-9d65-17adc586c360",
"name": "On Workflow Error",
"type": "n8n-nodes-base.errorTrigger",
"position": [
-2320,
5936
],
"parameters": {},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "b7d7334b-2c79-431c-b8a8-00874f5ecc3f",
"connections": {
"Normalise Payload": {
"main": [
[
{
"node": "GPT-4o \u2013 Classify Incident",
"type": "main",
"index": 0
}
]
]
},
"On Workflow Error": {
"main": [
[
{
"node": "Slack \u2013 Send Error Alert",
"type": "main",
"index": 0
}
]
]
},
"Merge Classification": {
"main": [
[
{
"node": "Switch \u2013 Route by Urgency",
"type": "main",
"index": 0
}
]
]
},
"Build Digest Message1": {
"main": [
[
{
"node": "Slack \u2013 Send Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Switch \u2013 Route by Urgency": {
"main": [
[
{
"node": "Slack \u2013 Critical @channel Alert",
"type": "main",
"index": 0
},
{
"node": "Gmail \u2013 Critical Email to PM",
"type": "main",
"index": 0
},
{
"node": "Google Sheets \u2013 Log Incident",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack \u2013 High Priority Notice",
"type": "main",
"index": 0
},
{
"node": "Google Sheets \u2013 Log Incident",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Log Incident",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Log Incident",
"type": "main",
"index": 0
}
]
]
},
"GPT-4o \u2013 Classify Incident": {
"main": [
[
{
"node": "Merge Classification",
"type": "main",
"index": 0
}
]
]
},
"Webhook \u2013 Incoming Incident": {
"main": [
[
{
"node": "Normalise Payload",
"type": "main",
"index": 0
}
]
]
},
"Schedule \u2013 6 PM Daily Digest": {
"main": [
[
{
"node": "Sheets \u2013 Fetch Open Incidents",
"type": "main",
"index": 0
}
]
]
},
"Sheets \u2013 Fetch Open Incidents": {
"main": [
[
{
"node": "Build Digest Message1",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
gmailOAuth2slackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow ingests construction incident webhooks, normalizes the payload, and uses OpenAI GPT-4o-mini to classify each incident by type and urgency, then routes alerts to Slack and Gmail and logs incidents to Google Sheets, with a daily Slack digest generated from the sheet.…
Source: https://n8n.io/workflows/16378/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Complete AI-powered sales system Automates lead capture, qualification, and follow-up from multiple channels. AI INTELLIGENCE:
This workflow accepts construction work permit requests via webhook, logs them to Google Sheets, uses OpenAI (gpt-4o-mini) to detect conflicts against active permits, routes the request to a superviso
Imagine your recruitment process transformed into a sleek, efficient, AI-powered assembly line for talent. That's exactly what this system creates. It automates the heavy lifting, allowing your human
Transform customer feedback into actionable insights automatically with AI analysis, professional PDF reports, personalized emails, and real-time team notifications. Overview Features Demo Prerequisit
Transform your webinar registrations from basic form submissions into a verified, personalized, and premium attendee experience.