This workflow corresponds to n8n.io template #15234 — we link there as the canonical source.
This workflow follows the Gmail → Slack 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": "VSoEPJMPHY1yyL03",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Automated Candidate Email SLA Monitoring & Slack Alerts",
"tags": [],
"nodes": [
{
"id": "4513b943-0397-4d99-999e-99aeeff8c82a",
"name": "Fetch Recent Emails",
"type": "n8n-nodes-base.gmail",
"position": [
-432,
-224
],
"parameters": {
"filters": {
"receivedAfter": "={{ new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString() }}"
},
"operation": "getAll"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "5c49a4f6-b811-45d5-ade9-9e32751b154d",
"name": "Filter Candidate Emails Only",
"type": "n8n-nodes-base.code",
"position": [
-240,
-224
],
"parameters": {
"jsCode": "const myEmail = \"user@example.com\";\n\nconst blockedDomains = [\n \"google.com\",\n \"linkedin.com\",\n \"medium.com\",\n \"airtable.com\",\n \"skool.com\",\n \"instagram.com\",\n \"galadia.io\",\n \"woocommerce.com\"\n];\n\nconst blockedKeywords = [\n \"no-reply\",\n \"noreply\",\n \"newsletter\",\n \"updates\",\n \"notification\"\n];\n\nfunction extractEmail(fromField) {\n const match = fromField.match(/<(.+?)>/);\n return match ? match[1].toLowerCase() : fromField.toLowerCase();\n}\n\nreturn items.filter(item => {\n const labels = item.json.labels.map(l => l.name);\n const fromEmail = extractEmail(item.json.From || \"\");\n const subject = (item.json.Subject || \"\").toLowerCase();\n\n const isInbox = labels.includes(\"INBOX\");\n const isSelf = fromEmail === myEmail;\n\n const isBlockedDomain = blockedDomains.some(domain =>\n fromEmail.includes(domain)\n );\n\n const isBlockedKeyword = blockedKeywords.some(word =>\n fromEmail.includes(word)\n );\n\n const isLikelyCandidate =\n subject.includes(\"application\") ||\n subject.includes(\"job\") ||\n subject.includes(\"role\") ||\n subject.includes(\"position\");\n\n return (\n isInbox &&\n !isSelf &&\n !isBlockedDomain &&\n !isBlockedKeyword &&\n isLikelyCandidate\n );\n});"
},
"typeVersion": 2
},
{
"id": "b4680c94-de5e-4c69-b1d0-1826183b29af",
"name": "Get Thread Details",
"type": "n8n-nodes-base.gmail",
"position": [
160,
-224
],
"parameters": {
"options": {},
"resource": "thread",
"threadId": "={{ $json.threadId }}",
"operation": "get"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "5bc605cb-6ca8-40cc-b7f0-1598371e17db",
"name": "Reply Detection",
"type": "n8n-nodes-base.code",
"position": [
336,
-224
],
"parameters": {
"jsCode": "const myEmail = \"user@example.com\";\n\nfunction extractEmail(fromField) {\n const match = fromField.match(/<(.+?)>/);\n return match ? match[1].toLowerCase() : fromField.toLowerCase();\n}\n\nreturn items.map(item => {\n const messages = item.json.messages;\n\n let hasReplied = false;\n let candidateEmail = \"\";\n\n for (const msg of messages) {\n const labels = (msg.labels || []).map(l => l.name);\n const fromEmail = extractEmail(msg.From || \"\");\n\n // detect candidate email\n if (!labels.includes(\"SENT\")) {\n candidateEmail = fromEmail;\n }\n\n // detect reply\n if (labels.includes(\"SENT\") && fromEmail === myEmail) {\n hasReplied = true;\n }\n }\n\n return {\n json: {\n threadId: item.json.id,\n candidateEmail,\n hasReplied\n }\n };\n});"
},
"typeVersion": 2
},
{
"id": "faab3b54-9222-4807-b482-3ba0fd46509a",
"name": "Check \u2013 Has Recruiter Replied?",
"type": "n8n-nodes-base.if",
"position": [
528,
-224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "958828dd-8a51-4ab2-b641-a5498cf8b208",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.hasReplied }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "272e75c5-9bee-4cad-bdf7-df82740c24cb",
"name": "Get Channel Members",
"type": "n8n-nodes-base.slack",
"position": [
960,
-576
],
"parameters": {
"resource": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09S57E2JQ2",
"cachedResultName": "n8n"
},
"operation": "member"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "01a17511-25c9-4f1d-af74-887ca163343e",
"name": "Get User Presence",
"type": "n8n-nodes-base.slack",
"position": [
1216,
-736
],
"parameters": {
"user": {
"__rl": true,
"mode": "id",
"value": "={{ $json.member }}"
},
"resource": "user",
"operation": "getPresence"
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "ed7ee2ba-2f97-45b4-9578-e96a4123909c",
"name": "Merge Presence Data",
"type": "n8n-nodes-base.merge",
"position": [
1488,
-592
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "7f6bf245-7995-4c78-9ae3-a84e5b6950c6",
"name": "Select \u2013 Active/Random User",
"type": "n8n-nodes-base.code",
"position": [
1696,
-592
],
"parameters": {
"jsCode": "const users = items.map(item => ({\n id: item.json.member,\n presence: item.json.presence\n}));\n\n// find active users\nconst activeUsers = users.filter(u => u.presence === \"active\");\n\n// pick user\nlet selectedUser;\n\nif (activeUsers.length > 0) {\n // pick first active (or random active)\n selectedUser = activeUsers[Math.floor(Math.random() * activeUsers.length)];\n} else {\n // pick random from all\n selectedUser = users[Math.floor(Math.random() * users.length)];\n}\n\nreturn [\n {\n json: {\n selectedUserId: selectedUser.id,\n selectedUserPresence: selectedUser.presence\n }\n }\n];"
},
"typeVersion": 2
},
{
"id": "7d6f344f-0d9f-446f-9b85-15377ca18a55",
"name": "Merge Assign User Data",
"type": "n8n-nodes-base.merge",
"position": [
2336,
-224
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "b5ed38da-7eb0-435d-8a01-c6f9827cfca1",
"name": "Send SLA Alert",
"type": "n8n-nodes-base.slack",
"position": [
2576,
-224
],
"parameters": {
"text": "=*SLA Breach Alert \u2013 Candidate Email Not Replied*\n*Candidate:* {{$json.candidateEmail}} \n*Thread ID:* {{$json.threadId}} \n\n*Status:* No reply has been sent within the expected SLA time.\n\n<@{{$json.selectedUserId}}> please review and respond to this candidate as soon as possible.\n\n---\n_Tip: Search the thread ID in Gmail to quickly find the conversation._",
"user": {
"__rl": true,
"mode": "id",
"value": "={{ $json.selectedUserId }}"
},
"select": "user",
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "8cd43b86-955d-45f8-b208-deff90eded94",
"name": "Scheduler \u2013 Every 5 min",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-624,
-224
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "9974a537-2a27-423b-9428-f664e170ce83",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-784,
-336
],
"parameters": {
"color": 7,
"width": 768,
"height": 320,
"content": "## Email Intake & Initial Filtering\nThis section triggers the workflow at a scheduled interval, retrieves emails received within the last 24 hours from Gmail and filters out only relevant candidate emails. It ensures that only meaningful inbound conversations enter the SLA tracking process."
},
"typeVersion": 1
},
{
"id": "35297df5-6fbb-4d2a-9749-f9fb912dc0ec",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
-336
],
"parameters": {
"color": 7,
"width": 752,
"height": 320,
"content": "## Thread Analysis & SLA Evaluation\nThis section fetches full email thread details and analyzes whether the recruiter has responded. Based on the reply status, it determines if the SLA has been breached, allowing only unanswered candidate emails to move forward for alerting."
},
"typeVersion": 1
},
{
"id": "d0808a30-f96e-4eec-aea8-1fd2b6ce912a",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
816,
-848
],
"parameters": {
"color": 7,
"width": 1152,
"height": 448,
"content": "## Slack User Selection Logic\nThis section retrieves Slack channel members and checks their availability status. It intelligently selects an active user if available or randomly assigns one when all users are away, ensuring that every SLA breach is assigned to someone for action."
},
"typeVersion": 1
},
{
"id": "91eea843-6531-43c4-b0e6-57506a4a5196",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2128,
-352
],
"parameters": {
"color": 7,
"width": 816,
"height": 304,
"content": "## Alert Preparation & Notification\nThis section merges SLA breach data with the selected Slack user and sends a structured alert message. It ensures the right person is notified with all necessary details, enabling quick action on pending candidate responses."
},
"typeVersion": 1
},
{
"id": "0cdda982-03dd-4e7a-958c-bce9096d6249",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-832,
-880
],
"parameters": {
"width": 1600,
"height": 480,
"content": "## How the Workflow Works\n- The workflow runs automatically at a defined time interval using a scheduler.\n- It fetches emails received in the last 24 hours from Gmail.\n- Candidate-related emails are filtered from all incoming messages.\n - Each email thread is analyzed to check if a recruiter has replied.\n- If no reply is found, it is marked as an SLA breach.\n- Slack channel members are fetched and their presence status is checked.\n- An active user is selected or a random user if all are away.\n- A Slack alert is sent to notify the selected user for quick action.\n\n## Workflow Setup Steps\n- Configure the scheduler node with your preferred execution interval.\n- Connect your Gmail account and set filters for recent emails (last 24 hours).\n- Add logic to identify candidate emails based on sender or subject.\n- Set up thread analysis to detect whether a reply has been sent.\n- Configure Slack credentials and select the target channel.\n- Enable fetching of channel members and their presence status.\n- Customize the Slack alert message with dynamic data (email, thread, user).\n- Test the workflow and activate it to start automated monitoring."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "4dd4a9f4-e574-4a8d-83d9-38883a1d0ca6",
"connections": {
"Reply Detection": {
"main": [
[
{
"node": "Check \u2013 Has Recruiter Replied?",
"type": "main",
"index": 0
}
]
]
},
"Get User Presence": {
"main": [
[
{
"node": "Merge Presence Data",
"type": "main",
"index": 0
}
]
]
},
"Get Thread Details": {
"main": [
[
{
"node": "Reply Detection",
"type": "main",
"index": 0
}
]
]
},
"Fetch Recent Emails": {
"main": [
[
{
"node": "Filter Candidate Emails Only",
"type": "main",
"index": 0
}
]
]
},
"Get Channel Members": {
"main": [
[
{
"node": "Get User Presence",
"type": "main",
"index": 0
},
{
"node": "Merge Presence Data",
"type": "main",
"index": 1
}
]
]
},
"Merge Presence Data": {
"main": [
[
{
"node": "Select \u2013 Active/Random User",
"type": "main",
"index": 0
}
]
]
},
"Merge Assign User Data": {
"main": [
[
{
"node": "Send SLA Alert",
"type": "main",
"index": 0
}
]
]
},
"Scheduler \u2013 Every 5 min": {
"main": [
[
{
"node": "Fetch Recent Emails",
"type": "main",
"index": 0
}
]
]
},
"Filter Candidate Emails Only": {
"main": [
[
{
"node": "Get Thread Details",
"type": "main",
"index": 0
}
]
]
},
"Select \u2013 Active/Random User": {
"main": [
[
{
"node": "Merge Assign User Data",
"type": "main",
"index": 0
}
]
]
},
"Check \u2013 Has Recruiter Replied?": {
"main": [
[],
[
{
"node": "Get Channel Members",
"type": "main",
"index": 0
},
{
"node": "Merge Assign User Data",
"type": "main",
"index": 1
}
]
]
}
}
}
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.
gmailOAuth2slackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically monitors candidate emails received in the last 24 hours and checks whether a recruiter has replied within the same thread. If no reply is found, it flags it as an SLA breach and sends a Slack alert to an available team member (active if possible,…
Source: https://n8n.io/workflows/15234/ — 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.
This workflow is an automated invoice payment tracking and reminder system for the Polish accounting service iFirma.pl. It monitors unpaid and overdue invoices, then automatically sends escalating rem
Automatically identify clients who haven’t been contacted in 14+ days and re-engage them with personalized Gmail follow-up emails, Google Sheets tracking, and Slack notifications for account managers.
Streamline client retention and contract renewals by automatically identifying expiring accounts, sending personalized reminder emails, and notifying account managers through Slack. This workflow ensu
This workflow runs on a schedule to monitor HubSpot deals with upcoming contract expiry dates. It filters deals that are 30, 60, or 90 days away from expiration and processes each one individually. Ba
This powerful n8n workflow automatically processes, categorizes, and organizes your Gmail inbox using customizable rules stored in Google Sheets. Say goodbye to manual email sorting and hello to a per