{
  "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
          }
        ]
      ]
    }
  }
}