AutomationFlowsEmail & Gmail › Detect Duplicate Candidates from Gmail to Slack

Detect Duplicate Candidates from Gmail to Slack

Original n8n title: Detect Duplicate Candidates by Email/phone From Gmail to Slack Using Google Sheets

Detect duplicate candidates by email/phone from Gmail to Slack using Google Sheets. Uses gmail, googleSheets, slack. Scheduled trigger; 10 nodes.

Cron / scheduled trigger★★★★☆ complexity10 nodesGmailGoogle SheetsSlack
Email & Gmail Trigger: Cron / scheduled Nodes: 10 Complexity: ★★★★☆ Added:

This workflow follows the Gmail → 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 →

Download .json
{
  "id": "7pODvUSdP3WtnM5C",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Detect duplicate candidates by email/phone from Gmail to Slack using Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "37e5642a-4a66-4f51-9747-fca065dc2899",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -260,
        -80
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "97acb6ce-48ce-4fd1-8cd9-d47be97871e5",
      "name": "Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -40,
        -80
      ],
      "parameters": {
        "filters": {
          "labelIds": [
            "Label_469123395284839348"
          ],
          "receivedAfter": "={{ $json.timestamp.toDateTime().minus(10,'min') }}"
        },
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "268e1fc2-a421-44f5-ba62-8a075ade25ca",
      "name": "Code",
      "type": "n8n-nodes-base.code",
      "position": [
        180,
        -80
      ],
      "parameters": {
        "jsCode": "const email = $input.first().json;\n\nconst snippet = email.snippet || '';\nconst from = email.From || '';\n\n// Use regex to extract values from snippet\nconst nameMatch = snippet.match(/Name:\\s*(.+?)\\s*Email:/i);\nconst emailMatch = snippet.match(/Email:\\s*(\\S+)/i);\nconst phoneMatch = snippet.match(/Phone:\\s*(\\d{10,})/i);\nconst roleMatch = snippet.match(/Role:\\s*(.+?)\\s*Total Experience:/i);\nconst experienceMatch = snippet.match(/Total Experience:\\s*(\\d+)/i);\nconst recruiterMatch = snippet.match(/Recruiter:\\s*(.+?)\\s*Resume/i);\nconst resumeUrlMatch = snippet.match(/Resume URL:\\s*(.+?)(?:\\s|$)/i);\n\n// Extract sender email from From field\nconst fromEmailMatch = from.match(/<(.+?)>/);\nconst sourceEmail = fromEmailMatch ? fromEmailMatch[1] : from;\n\nreturn [\n  {\n    json: {\n      candidate_name: nameMatch?.[1]?.trim() || '',\n      candidate_email: emailMatch?.[1]?.trim() || '',\n      candidate_phone: phoneMatch?.[1]?.trim() || '',\n      role_applied: roleMatch?.[1]?.trim() || '',\n      years_of_experience: experienceMatch?.[1]?.trim() || '',\n      recruiter: recruiterMatch?.[1]?.trim() || '',\n      resume_url: resumeUrlMatch?.[1]?.trim() || '',\n      source_email: sourceEmail\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6db8693c-bb2f-4d74-a7a7-dc13b135faa4",
      "name": "Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1060,
        20
      ],
      "parameters": {
        "columns": {
          "value": {
            "recruiter": "={{ $json.recruiter }}",
            "resume_url": "={{ $json.resume_url }}",
            "role_applied": "={{ $json.role_applied }}",
            "source_email": "={{ $json.source_email }}",
            "candidate_name": "={{ $json.candidate_name }}",
            "candidate_email": "={{ $json.candidate_email }}",
            "candidate_phone": "={{ $json.candidate_phone }}",
            "years_of_experience": "={{ $json.years_of_experience }}"
          },
          "schema": [
            {
              "id": "candidate_name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "candidate_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "candidate_email",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "candidate_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "candidate_phone",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "candidate_phone",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role_applied",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role_applied",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "years_of_experience",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "years_of_experience",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "role_applied",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "role_applied",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "recruiter",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "recruiter",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "resume_url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "resume_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "source_email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "source_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "candidate_email"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1OrKgeY6j5AuaAeglrEU9PsehYkJlrK9hmUgh6_-x0EY",
          "cachedResultUrl": "",
          "cachedResultName": "Candidates"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "12002fc1-3c77-430f-b2e4-cfc6cb9c70b9",
      "name": "Google Sheets1",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        400,
        -80
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1OrKgeY6j5AuaAeglrEU9PsehYkJlrK9hmUgh6_-x0EY",
          "cachedResultUrl": "",
          "cachedResultName": "Candidates"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "545d43a4-51e6-4966-a889-9cba847dd3c8",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        840,
        -80
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "47596994-d10a-4a3b-b695-ff3cfc154374",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isDuplicate }}",
              "rightValue": "={{ $json.candidate_email }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "9fa103e5-9d30-495b-9729-a7355cbdd518",
      "name": "Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1060,
        -180
      ],
      "parameters": {
        "text": "=User  {{ $json.candidate_name }} has already applied for {{ $json.role_applied }}",
        "user": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "select": "user",
        "otherOptions": {}
      },
      "typeVersion": 2.3
    },
    {
      "id": "d4ecbae6-c89f-4d03-a4fe-5d02e89f31d2",
      "name": "Code1",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        620,
        -80
      ],
      "parameters": {
        "jsCode": "const candidate =  $(\"Code\").all()[0].json;\nconst sheetRows = $('Google Sheets1').all();\n\nconst emailToCheck = candidate.candidate_email?.toLowerCase().trim();\n\n// Check if candidate_email already exists\nconst isDuplicate = sheetRows.some(row => {\n  return row.json.candidate_email?.toLowerCase().trim() === emailToCheck;\n});\n\nreturn [\n  {\n    json: {\n      ...candidate,\n      isDuplicate\n    }\n  }\n];\n"
      },
      "executeOnce": false,
      "retryOnFail": false,
      "typeVersion": 2
    },
    {
      "id": "c11aa74d-77ba-4711-b923-00c0dcd2ae96",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        -240
      ],
      "parameters": {
        "width": 1600,
        "height": 440,
        "content": "## Detect duplicate candidates by email/phone from Gmail to Slack using Google Sheets"
      },
      "typeVersion": 1
    },
    {
      "id": "cda5c54b-8456-4c6e-a127-d03f5d823c74",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -320,
        220
      ],
      "parameters": {
        "width": 1600,
        "height": 680,
        "content": "\n##  **Workflow: Detect Duplicate Candidates from Gmail \u2192 Google Sheets / Slack**\n\n###  Purpose:\n\nAutomatically process new job application emails to detect duplicates and act accordingly.\n\n###  **Core Logic (Key Points):**\n\n1. **Trigger the workflow** every few minutes to check for new applicant emails labeled `applicant`.\n\n2. **Fetch recent Gmail messages**, extract structured candidate information (name, email, phone, role, etc.).\n\n3. **Compare the extracted email** with existing entries in a Google Sheet.\n\n4. **If the candidate email already exists**:\n\n   *  Send a **Slack alert** that this user already applied.\n\n5. **If the candidate is new**:\n\n   *  Append their data into the **Google Sheet**.\n\n###  Outcome:\n\n* Prevents duplicate processing of candidate applications.\n* Ensures clean, deduplicated records in Google Sheets.\n* Notifies via Slack if someone reapplies.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bb149ecc-e9cb-4d56-af0c-211a58939fce",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Google Sheets1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code1": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets1": {
      "main": [
        [
          {
            "node": "Code1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Gmail",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

How this works

This workflow streamlines recruitment by automatically scanning incoming Gmail messages for candidate emails and phone numbers, cross-checking them against a Google Sheets database to identify duplicates, and notifying your team via Slack if matches are found. It's ideal for talent acquisition teams or HR professionals handling high volumes of applications, ensuring no promising leads slip through due to oversight. The key step involves processing the extracted contact details in a code node before querying the sheets, enabling quick alerts that save time and prevent redundant outreach.

Use this workflow when managing daily candidate inflows via email, such as job application submissions, to maintain an efficient pipeline without manual checks. Avoid it for low-volume or non-email-based sourcing, where simpler tools suffice, or if your data requires complex AI analysis beyond basic matching. Common variations include adapting the sheets lookup for custom fields like LinkedIn profiles or integrating with CRM systems for broader deduplication.

About this workflow

Detect duplicate candidates by email/phone from Gmail to Slack using Google Sheets. Uses gmail, googleSheets, slack. Scheduled trigger; 10 nodes.

Source: https://github.com/weblineindia/n8n-Detect-duplicate-candidates-from-Gmail-to-Slack-using-Google-Sheets/blob/main/main.json — original creator credit. Request a take-down →

More Email & Gmail workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Email & Gmail

This workflow automatically scans AWS accounts for orphaned resources (unattached EBS volumes, old snapshots &gt;90 days, unassociated Elastic IPs) that waste money. It calculates cost impact, validat

Slack, Gmail, AWS Lambda +1
Email & Gmail

Streamline IT and operations change management by automating approval routing, Jira issue creation, audit logging, and real-time Slack alerts. This workflow ensures faster reviews, traceable approvals

Monday.com, Slack, Jira +2
Email & Gmail

Streamline IT and operations change management by automating approval routing, Jira issue creation, audit logging, and real-time Slack alerts. This workflow ensures faster reviews, traceable approvals

Monday.com, Slack, Jira +2
Email & Gmail

Automate your GoHighLevel (GHL) pipeline tracking and deal management process. This workflow fetches all opportunities, calculates the time spent in each stage, logs historical pipeline data in Google

High Level, Google Sheets, Gmail +1
Email & Gmail

Automatically consolidate Zendesk and Freshdesk ticket data into a unified performance dashboard with KPI calculations, Google Sheets logging, real-time Slack alerts, and weekly Gmail email reports. P

Slack, Gmail, Zendesk +2