This workflow corresponds to n8n.io template #7886 — we link there as the canonical source.
This workflow follows the Gmail Trigger → Google Sheets 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "376b4f9c-7bf1-40a1-88a3-11bbdd1824b8",
"name": "Process Email and Extract",
"type": "n8n-nodes-base.code",
"position": [
464,
176
],
"parameters": {
"jsCode": "// ... (Code from \"Check Device and Notify\" node)\n\n// Group rows by MAC\nconst latestByMac = {};\n\nfor (const item of items) { // 'items' here represents all rows fetched from your Google Sheet\n const mac = item.json.mac;\n const time = new Date(item.json.timestampISO); // Converts the timestamp ISO string to a Date object\n\n if (!mac || isNaN(time)) continue; // Skips if MAC is missing or time is invalid\n\n // This is the crucial part for finding the LATEST status\n if (!latestByMac[mac] || new Date(latestByMac[mac].json.timestampISO) < time) {\n latestByMac[mac] = item; // If this MAC is new, or the current 'item' is newer than what's stored, update it\n }\n}\n\n// Filter the latest per MAC that are still disconnected and have passed their 30-minute threshold\nconst overdueAlerts = Object.values(latestByMac).filter(item => {\n const status = item.json.status; // Get the status of the LATEST entry for this MAC\n const timestampISO = new Date(item.json.timestampISO); // Original event time of the latest entry\n const checkAfter = new Date(item.json.checkAfter); // 30 mins after original event time of the latest entry\n const alertSent = item.json.alertSent;\n\n // This check assumes 'checkAfter' means 30 minutes from timestampISO has passed,\n // and we want to trigger if that condition is met internally, irrespective of 'now'.\n const isOverdueFinal = !isNaN(checkAfter.getTime());\n\n const notAlerted = !(alertSent === true || alertSent === \"true\");\n\nreturn (status === 'disconnected' || status === 'isolated') && isOverdueFinal && notAlerted;\n});\n\nreturn overdueAlerts;"
},
"typeVersion": 2
},
{
"id": "6808c535-be1a-4bce-8a55-17e4878b2c3b",
"name": "Append Row in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
896,
176
],
"parameters": {
"columns": {
"value": {
"mac": "={{ $json.mac }}",
"name": "={{ $json.name }}",
"type": "={{ $json.type }}",
"rowId": "={{ $json.rowId }}",
"status": "={{ $json.status }}",
"category": "={{ $json.category }}",
"severity": "={{ $json.severity }}",
"alertSent": "={{ $json.alertSent }}",
"timestamp": "={{ $json.timestamp }}",
"checkAfter": "={{ $json.checkAfter }}",
"timestampISO": "={{ $json.timestampISO }}",
"timeStampFormated": "={{ $json.timestampFormatted }}"
},
"schema": [
{
"id": "rowId",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "rowId",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestampISO",
"type": "string",
"display": true,
"required": false,
"displayName": "timestampISO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "severity",
"type": "string",
"display": true,
"required": false,
"displayName": "severity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "mac",
"type": "string",
"display": true,
"required": false,
"displayName": "mac",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "type",
"type": "string",
"display": true,
"required": false,
"displayName": "type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "checkAfter",
"type": "string",
"display": true,
"required": false,
"displayName": "checkAfter",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alertSent",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "alertSent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timeStampFormated",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "timeStampFormated",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "YOUR_SHEET_GID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=YOUR_SHEET_GID_HERE",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "bc91b08b-924a-4c0f-8e8b-d1d69d218a33",
"name": "Check Every 5 minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
64,
800
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f35f5433-0aa4-41ba-9665-a749daae3a5a",
"name": "Get Row(s) in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
288,
800
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "YOUR_SHEET_GID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=YOUR_SHEET_GID_HERE",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6,
"alwaysOutputData": true
},
{
"id": "6db058b2-c4d5-4875-b536-ac14285cacf3",
"name": "Check Device and Notify",
"type": "n8n-nodes-base.code",
"position": [
512,
800
],
"parameters": {
"jsCode": "const latestByMac = {};\n\nfor (const item of items) {\n const mac = item.json.mac;\n const time = new Date(item.json.timestampISO);\n\n if (!mac || isNaN(time)) continue;\n\n if (!latestByMac[mac] || new Date(latestByMac[mac].json.timestampISO) < time) {\n latestByMac[mac] = item;\n }\n}\n\n// Adjust to Philippine Time (UTC+8)\nconst now = new Date();\nconst nowLocal = new Date(now.getTime() + (8 * 60 * 60 * 1000));\n\nconst overdueAlerts = Object.values(latestByMac).filter(item => {\n const status = String(item.json.status || \"\").toLowerCase();\n const checkAfter = new Date(item.json.checkAfter);\n const alertSent = String(item.json.alertSent).toLowerCase();\n\n const isOverdue = !isNaN(checkAfter.getTime()) && nowLocal >= checkAfter;\n const notAlerted = alertSent !== \"true\" && alertSent !== \"yes\" && alertSent !== \"1\";\n\n console.log(\"DEBUG\", {\n mac: item.json.mac,\n status,\n nowLocal: nowLocal.toISOString(),\n checkAfter: item.json.checkAfter,\n isOverdue,\n notAlerted,\n alertSentRaw: item.json.alertSent\n });\n\n return (status === \"disconnected\" || status === \"isolated\") && isOverdue && notAlerted;\n});\n\nreturn overdueAlerts.length > 0 ? overdueAlerts : [{ json: { debug: \"No overdue devices found\", nowLocal: nowLocal.toISOString() } }];\n"
},
"typeVersion": 2
},
{
"id": "17b9806a-7ed4-4f49-bbdf-941277fe74ae",
"name": "Update Alert",
"type": "n8n-nodes-base.googleSheets",
"position": [
944,
784
],
"parameters": {
"columns": {
"value": {
"rowId": "={{ $json.rowId }}",
"alertSent": "=true"
},
"schema": [
{
"id": "rowId",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "rowId",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestampISO",
"type": "string",
"display": true,
"required": false,
"displayName": "timestampISO",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "severity",
"type": "string",
"display": true,
"required": false,
"displayName": "severity",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "mac",
"type": "string",
"display": true,
"required": false,
"displayName": "mac",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "type",
"type": "string",
"display": true,
"required": false,
"displayName": "type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "checkAfter",
"type": "string",
"display": true,
"required": false,
"displayName": "checkAfter",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alertSent",
"type": "string",
"display": true,
"required": false,
"displayName": "alertSent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timeStampFormated",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "timeStampFormated",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"rowId"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "YOUR_SHEET_GID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=YOUR_SHEET_GID_HERE",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "64082460-3cfb-4af2-82c5-dea73f6fe80a",
"name": "Receives Alert",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
16,
176
],
"parameters": {
"simple": false,
"filters": {
"q": ""
},
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "1a29a2dd-f37c-45e7-b0b3-9f21f70c7738",
"name": "Clear sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
144,
1328
],
"parameters": {
"clear": "specificRows",
"operation": "clear",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "YOUR_SHEET_GID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit#gid=YOUR_SHEET_GID_HERE",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID_HERE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID_HERE/edit?usp=drivesdk",
"cachedResultName": "YOUR_SHEET_NAME_HERE"
},
"startIndex": 2,
"rowsToDelete": 500
},
"typeVersion": 4.6
},
{
"id": "a1d48f39-9906-4c5d-99d6-10e953afb397",
"name": "Clear Rows Every 2 days",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-80,
1328
],
"parameters": {
"rule": {
"interval": [
{
"daysInterval": 2,
"triggerAtHour": 2
}
]
}
},
"typeVersion": 1.2
},
{
"id": "c66fc6f3-9923-45d5-89e7-0631c22d1bf1",
"name": "Alert User",
"type": "n8n-nodes-base.pushover",
"position": [
944,
992
],
"parameters": {
"message": "={{ $json.name }} is {{ $json.status }}\n\n{{ $json.timeStampFormated }}",
"userKey": "YOUR_PUSHOVER_USER_KEY_HERE",
"priority": 0,
"additionalFields": {}
},
"credentials": {
"pushoverApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "77784bb9-3cae-4150-8346-e423b720a71d",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
16
],
"parameters": {
"width": 464,
"height": 448,
"content": "## This workflow monitors device connection status from Omada Controller alerts.\n\nHow it works:\n- Gmail Trigger receives Omada alert emails\n- Code node parses the alert into structured fields\n- Data is logged into Google Sheets\n- Every 5 minutes, workflow checks if any device has been disconnected for 30+ minutes\n- Sends a Pushover notification for overdue disconnections\n- Clears old rows from the sheet every 2 days\n"
},
"typeVersion": 1
},
{
"id": "b2e2974a-5cf2-40df-9a76-64d3f3ff3534",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Receives Alert**: \n\nGmail trigger for Omada Controller emails "
},
"typeVersion": 1
},
{
"id": "7a743261-e183-4302-9a0d-90c97433c3c5",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
0
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Process Email and Extract**: \n\nExtracts timestamp, device name, MAC, severity, status "
},
"typeVersion": 1
},
{
"id": "75723c9f-062e-438b-974e-9d3154b5d140",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
816,
0
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Append Row in Sheet**: \n\nLogs data to Google Sheets "
},
"typeVersion": 1
},
{
"id": "b1e05077-659a-4774-a7b5-82586944fd07",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
576
],
"parameters": {
"color": 7,
"width": 304,
"height": 144,
"content": "**Check Every 5 minutes**: \n\nScheduled polling to detect overdue disconnections "
},
"typeVersion": 1
},
{
"id": "29b5289b-d53c-44fe-85df-db475a119ebf",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
576
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Check Device and Notify**: \n\nFilters devices disconnected >30 minutes \n"
},
"typeVersion": 1
},
{
"id": "7f0bf309-1e30-43ff-8d18-2ed136e5b82f",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
1168
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Alert User**: \n\nSends push notification via Pushover. You can change this to any notifying app"
},
"typeVersion": 1
},
{
"id": "6c01c483-f76b-47f1-ac07-5ab2ef149541",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
880,
576
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Update Alert**: \n\nMarks row as alerted in Google Sheets \n"
},
"typeVersion": 1
},
{
"id": "639d6524-794d-49ea-bfef-2556d4e00d1e",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-32,
1136
],
"parameters": {
"color": 7,
"width": 224,
"height": 144,
"content": "**Clear Rows Every 2 days**: \n\nCleans up old entries to keep data fresh \n"
},
"typeVersion": 1
}
],
"connections": {
"Receives Alert": {
"main": [
[
{
"node": "Process Email and Extract",
"type": "main",
"index": 0
}
]
]
},
"Get Row(s) in Sheet": {
"main": [
[
{
"node": "Check Device and Notify",
"type": "main",
"index": 0
}
]
]
},
"Check Every 5 minutes": {
"main": [
[
{
"node": "Get Row(s) in Sheet",
"type": "main",
"index": 0
}
]
]
},
"Check Device and Notify": {
"main": [
[
{
"node": "Update Alert",
"type": "main",
"index": 0
},
{
"node": "Alert User",
"type": "main",
"index": 0
}
]
]
},
"Clear Rows Every 2 days": {
"main": [
[
{
"node": "Clear sheet",
"type": "main",
"index": 0
}
]
]
},
"Process Email and Extract": {
"main": [
[
{
"node": "Append Row in Sheet",
"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.
gmailOAuth2googleSheetsOAuth2ApipushoverApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is designed for IT admins, network engineers, or small business owners who need to automatically monitor device connectivity from Omada Controller email alerts. If you want a lightweight, low-cost solution for tracking device uptime without complex monitoring…
Source: https://n8n.io/workflows/7886/ — 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.
Automatically extract structured information from emails using AI-powered document analysis. This workflow processes emails from specified domains, classifies them by type, and extracts structured dat
This weekly workflow helps you stay on top of SEO visibility losses by automatically detecting when your previously strong keywords fall out of Google’s top 10 results.
What This Flow Does
This n8n workflow sends personalized outreach emails automatically while enforcing strict safety rules such as email validation, spam checks, daily limits, and human-like delays.
Receive any business document via email. The attachment is automatically classified (Invoice, Contract, or Purchase Order) using easybits Extractor, then routed down the correct path where a second Ex