{
  "id": "tNOLOnPY0dAIq3wC",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Resume Screener(HR Automation)",
  "tags": [],
  "nodes": [
    {
      "id": "584caae3-044e-493a-ac70-6db4ea431c6b",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        176,
        624
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4-turbo",
          "cachedResultName": "gpt-4-turbo"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9a392def-0ab3-4d3b-82dc-1ecd64bce03c",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        -400
      ],
      "parameters": {
        "width": 928,
        "height": 496,
        "content": "## Workflow Overview & Setup Guide\nThis workflow automates the process of screening resumes using AI. It starts by fetching email drafts from Gmail that contain resume attachments. The attachments are formatted and validated to ensure they are usable files, specifically PDFs. Each file is processed individually, and its content is extracted as plain text.\n\nThe extracted resume text is then sent to an AI model, which analyzes candidate details such as skills, experience, and contact information. The AI also calculates a match score based on predefined job requirements. The response is parsed into structured JSON format for consistency.\n\nNext, the workflow evaluates the candidate using the match score. If the score meets or exceeds the defined threshold, the candidate is shortlisted and their details are sent to a Slack channel. If not, the candidate is marked as rejected.\n\nSetup steps:\n1. Connect your Gmail account to fetch drafts with attachments\n2. Configure OpenAI API credentials for AI analysis\n3. Connect Slack account for notifications\n4. Ensure resumes are in PDF format\n5. Adjust job requirements in AI prompt if needed\n\nThis workflow reduces manual screening effort and speeds up the hiring process efficiently.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1909bf65-f0be-4679-941c-e6fbc7043d0c",
      "name": "Fetch Gmail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -1648,
        384
      ],
      "parameters": {
        "filters": {},
        "operation": "getAll"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "cbb75e96-60a8-4620-ad83-c44bcad28f78",
      "name": "Format Attachments",
      "type": "n8n-nodes-base.function",
      "position": [
        -1424,
        384
      ],
      "parameters": {
        "functionCode": "const attachments = items[0].binary || {};\n\nconst files = Object.keys(attachments).map(key => {\n  return {\n    json: {\n      fileName: key\n    },\n    binary: {\n      data: attachments[key]\n    }\n  };\n});\n\nreturn files;"
      },
      "typeVersion": 1
    },
    {
      "id": "fc0829f2-a829-44c0-8eaf-5cbed9d51ff2",
      "name": "Check Attachments Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        -1200,
        384
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{Object.keys($binary || {}).length > 0}}",
              "value2": "={{ true }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7bf4f5c6-e1de-44b5-9484-ef09822eac37",
      "name": "Process Each Attachment",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -864,
        384
      ],
      "parameters": {
        "options": {},
        "batchSize": 1
      },
      "typeVersion": 1
    },
    {
      "id": "0bc95d8b-35a5-430f-86db-6e3ae27a4b86",
      "name": "Validate PDF File",
      "type": "n8n-nodes-base.if",
      "position": [
        -656,
        384
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$binary.data.mimeType}}",
              "value2": "application/pdf"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "75bf5aeb-610e-4b49-bd72-7b42e95cddb9",
      "name": "Extract Resume Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -272,
        384
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "aaf1d2ac-5865-4363-b054-8f9fdf307ea1",
      "name": "AI Resume Analyzer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        176,
        384
      ],
      "parameters": {
        "text": "=You are an expert HR assistant and resume parser.\n\nYour task is to extract structured information from resume text and evaluate the candidate against job requirements.\n\nYou MUST return ONLY valid JSON. No explanation, no markdown, no extra text.\n\n\n\nJOB REQUIREMENTS:\n- Skills: React Native, JavaScript, REST APIs, Git, Expo\n- Experience: 3+ years\n- Location: India (preferred)\n\nRESUME TEXT:\n{{ $json.text }}\n\n---\n\nINSTRUCTIONS:\n\n1. Extract the following fields:\n- full_name\n- email\n- phone\n- skills (array)\n- total_experience (in years, number)\n- current_company (if available)\n\n2. Match candidate skills with job requirements.\n\n3. Calculate a score out of 100 based on:\n- Skills match (50%)\n- Experience (30%)\n- Location relevance (20%)\n\n4. Decision:\n- If score >= 70 \u2192 \"SHORTLISTED\"\n- Else \u2192 \"REJECTED\"\n\n---\n\nOUTPUT FORMAT (STRICT JSON):\n\n{\n  \"full_name\": \"\",\n  \"email\": \"\",\n  \"phone\": \"\",\n  \"skills\": [],\n  \"total_experience\": 0,\n  \"current_company\": \"\",\n  \"match_score\": 0,\n  \"decision\": \"SHORTLISTED or REJECTED\",\n  \"reason\": \"\"\n}",
        "options": {},
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "cf8226b3-a0ec-45ea-aa5e-9fa03c9751bb",
      "name": "Parse AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        480,
        384
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  let raw = item.json.output;\n\n  try {\n    // Step 1: Clean string (remove line breaks)\n    raw = raw.replace(/\\\\n/g, '');\n\n    // Step 2: Parse JSON string\n    const parsed = JSON.parse(raw);\n\n    return {\n      json: parsed\n    };\n\n  } catch (err) {\n    return {\n      json: {\n        success: false,\n        error: \"JSON_PARSE_ERROR\",\n        raw_output: item.json.output\n      }\n    };\n  }\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "53ba0675-ecaa-46a1-8d7e-aceace3c25a2",
      "name": "Evaluate Candidate Score",
      "type": "n8n-nodes-base.if",
      "position": [
        928,
        384
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.match_score }}",
              "value2": 70,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "dd3d6690-bca9-4c7d-8cfb-fa05b2196a1a",
      "name": "Send Shortlisted to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        1536,
        368
      ],
      "parameters": {
        "text": "=Candidate Shortlisted\n\nName: {{ $json.full_name }}\nEmail: {{ $json.email }}\nPhone: {{ $json.phone }}\n\nCurrent Company: {{ $json.current_company }}\nExperience: {{ $json.total_experience }} years\n\nSkills: {{ $json.skills.join(\", \") }}\n\nMatch Score: {{ $json.match_score }}%\n\nReason: {{ $json.reason }}\n\nStatus: SHORTLISTED\nReady for next round of evaluation/interview",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0ALTQD6L0H",
          "cachedResultName": "feature-addition"
        },
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "72451af3-e284-486f-9d6f-ef2cd2f37073",
      "name": "Mark as Rejected",
      "type": "n8n-nodes-base.set",
      "position": [
        1536,
        592
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "status",
              "value": "Rejected candidate"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "715f2534-55f0-4473-b186-c6351fd40469",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1920,
        208
      ],
      "parameters": {
        "color": 7,
        "width": 896,
        "height": 432,
        "content": "## Fetch and Prepare Resume Files\nThis section retrieves email drafts from Gmail and prepares attachments for processing. It ensures all attached files are structured properly and checks whether attachments exist before moving forward. This helps in filtering only relevant emails that contain resume files for further evaluation."
      },
      "typeVersion": 1
    },
    {
      "id": "9150f935-748f-4220-9fb7-037570cb8b84",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -928,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 448,
        "content": "## Process and Validate Resume Files\nThis section handles multiple attachments by splitting them into individual items. It validates whether each file is a PDF and ensures only supported resume formats are processed. This step prevents errors and guarantees that only valid resume files proceed to text extraction."
      },
      "typeVersion": 1
    },
    {
      "id": "8c602ea8-5cde-4822-9eb2-0eb19dfbe31c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -384,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 448,
        "content": "## Extract Resume Content\nThis section extracts readable text from PDF resumes. The extracted content is essential for AI analysis. It converts unstructured resume files into plain text format, enabling further processing like data extraction, skill matching, and candidate evaluation."
      },
      "typeVersion": 1
    },
    {
      "id": "00373f58-a8e4-4138-afe3-437691f43e4f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 592,
        "content": "## Analyze Resume with AI\nThis section sends extracted resume text to an AI model for structured analysis. The AI extracts candidate details such as name, skills, and experience, and calculates a match score. The response is then cleaned and converted into proper JSON format for further decision-making."
      },
      "typeVersion": 1
    },
    {
      "id": "403cc3f4-9e9c-42f3-bf96-6d6159a0eaf0",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 448,
        "content": "## Evaluate Candidate Eligibility\nThis section evaluates the candidate based on the AI-generated match score. It checks whether the score meets the required threshold. Based on the result, the workflow decides if the candidate should be shortlisted or rejected for further processing."
      },
      "typeVersion": 1
    },
    {
      "id": "c2dd667a-835f-4da4-95e8-728e7f2abcd3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1296,
        192
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 608,
        "content": "## Send Results and Final Status\nThis section handles the final outcome of the evaluation. Shortlisted candidates are sent to Slack with detailed information, including skills and match score. Rejected candidates are marked accordingly. This ensures clear communication and proper tracking of candidate status."
      },
      "typeVersion": 1
    },
    {
      "id": "118ad8f9-4af8-43dc-b8b6-d66d2b45d06f",
      "name": "Start Workflow Manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1872,
        384
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "db6caaf6-1fd4-4570-95a5-3922a0888f44",
  "connections": {
    "Fetch Gmail": {
      "main": [
        [
          {
            "node": "Format Attachments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Rejected": {
      "main": [
        []
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Resume Analyzer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Evaluate Candidate Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate PDF File": {
      "main": [
        [
          {
            "node": "Extract Resume Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Resume Analyzer": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Attachments": {
      "main": [
        [
          {
            "node": "Check Attachments Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Resume Text": {
      "main": [
        [
          {
            "node": "AI Resume Analyzer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Attachments Exist": {
      "main": [
        [
          {
            "node": "Process Each Attachment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Each Attachment": {
      "main": [
        [
          {
            "node": "Validate PDF File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Workflow Manually": {
      "main": [
        [
          {
            "node": "Fetch Gmail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Candidate Score": {
      "main": [
        [
          {
            "node": "Send Shortlisted to Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark as Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}