{
  "nodes": [
    {
      "id": "54e48d92-7243-49f9-8830-65593d5b48fb",
      "name": "Resume Upload Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1808,
        192
      ],
      "parameters": {
        "path": "resume-upload",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0d0dbd85-ed7e-43c5-80b3-e7ec638b6d09",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -1600,
        192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "jobRole",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Job role to match against (e.g., Software Engineer, Data Analyst)__>"
            },
            {
              "id": "id-2",
              "name": "acceptanceThreshold",
              "type": "number",
              "value": 75
            },
            {
              "id": "id-3",
              "name": "borderlineThreshold",
              "type": "number",
              "value": 50
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "c7868f26-70f2-4f1a-9bab-b13c095f2a21",
      "name": "Extract Resume Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -1312,
        192
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1.1
    },
    {
      "id": "573870a0-feef-45b8-9512-dd589c23f0f3",
      "name": "Prepare Resume Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -1024,
        192
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "resumeText",
              "type": "string",
              "value": "={{ $json.text }}"
            },
            {
              "id": "id-2",
              "name": "candidateName",
              "type": "string",
              "value": "={{ $json.body.candidateName || \"Unknown\" }}"
            },
            {
              "id": "id-3",
              "name": "candidateEmail",
              "type": "string",
              "value": "={{ $json.body.candidateEmail || \"\" }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "320083a3-5d5f-4fb9-9196-adb740ede4ef",
      "name": "AI Resume Scorer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -768,
        192
      ],
      "parameters": {
        "text": "=Job Role: {{ $('Workflow Configuration').first().json.jobRole }}\n\nCandidate Name: {{ $json.candidateName }}\n\nResume Text:\n{{ $json.resumeText }}",
        "options": {
          "systemMessage": "You are an expert HR resume screening assistant. Your task is to evaluate resumes against job requirements and provide objective, unbiased scoring.\n\nYour evaluation process:\n1. Analyze the resume text for relevant skills, experience, and qualifications\n2. Match the candidate's background against the specified job role requirements\n3. Evaluate technical skills, years of experience, education, and role relevance\n4. Generate a score from 0-100 based on:\n   - Skills match (40%)\n   - Experience level and relevance (35%)\n   - Education and certifications (15%)\n   - Overall fit (10%)\n5. Provide a clear decision: accept, reject, or borderline\n6. Write a brief reason explaining your scoring\n\nIMPORTANT: Ensure your evaluation is fair, objective, and free from bias. Focus only on qualifications and job fit."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "681dae0b-1442-4237-a30c-0cf86495d934",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -784,
        432
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "typeVersion": 1.3
    },
    {
      "id": "1c1b52f0-d92f-4742-a677-a2e7d762ab1f",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -560,
        432
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"score\": {\n      \"type\": \"number\",\n      \"description\": \"Score from 0 to 100\"\n    },\n    \"decision\": {\n      \"type\": \"string\",\n      \"enum\": [\"accept\", \"reject\", \"borderline\"],\n      \"description\": \"Final decision\"\n    },\n    \"reason\": {\n      \"type\": \"string\",\n      \"description\": \"Brief explanation of the scoring decision\"\n    }\n  },\n  \"required\": [\"score\", \"decision\", \"reason\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "687eab63-7a6d-4237-bcd3-8b2af319028e",
      "name": "Check Score - Accept",
      "type": "n8n-nodes-base.if",
      "position": [
        -384,
        224
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.score }}",
              "rightValue": "={{ $('Workflow Configuration').first().json.acceptanceThreshold }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ef3d216f-e25d-4b23-8054-27c9d1b81429",
      "name": "Check Score - Borderline",
      "type": "n8n-nodes-base.if",
      "position": [
        -208,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.score }}",
              "rightValue": "={{ $('Workflow Configuration').first().json.borderlineThreshold }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "5f3f421b-9218-4d77-9f7f-0528cc6b64a3",
      "name": "Send Acceptance Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -112,
        464
      ],
      "parameters": {
        "sendTo": "={{ $json.candidateEmail }}",
        "message": "=Dear {{ $json.candidateName }},\n\nThank you for applying! We are pleased to inform you that your resume has been reviewed and you have been selected to move forward in our hiring process.\n\nYour application scored {{ $json.score }}/100 for the {{ $('Workflow Configuration').first().json.jobRole }} position.\n\nOur team will be in touch shortly with next steps.\n\nBest regards,\nHiring Team",
        "options": {},
        "subject": "Congratulations! Next Steps in Your Application"
      },
      "typeVersion": 2.2
    },
    {
      "id": "05d970e1-b78f-4a7a-b657-6ac8404f9179",
      "name": "Send Rejection Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        192,
        144
      ],
      "parameters": {
        "sendTo": "={{ $json.candidateEmail }}",
        "message": "=Dear {{ $json.candidateName }},\n\nThank you for taking the time to apply for the {{ $('Workflow Configuration').first().json.jobRole }} position.\n\nAfter careful review, we have decided to move forward with other candidates whose qualifications more closely match our current needs.\n\nWe appreciate your interest in our company and wish you the best in your job search.\n\nBest regards,\nHiring Team",
        "options": {},
        "subject": "Thank You for Your Application"
      },
      "typeVersion": 2.2
    },
    {
      "id": "15ab4362-fd77-4e2d-b6ec-15ab76f83283",
      "name": "Notify HR - Borderline",
      "type": "n8n-nodes-base.slack",
      "position": [
        160,
        -160
      ],
      "parameters": {
        "text": "=\ud83d\udd14 *Borderline Candidate Requires HR Review*\n\n*Candidate:* {{ $json.candidateName }}\n*Email:* {{ $json.candidateEmail }}\n*Role:* {{ $('Workflow Configuration').first().json.jobRole }}\n*Score:* {{ $json.score }}/100\n*Decision:* {{ $json.decision }}\n\n*Reason:*\n{{ $json.reason }}\n\nPlease review this candidate manually.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "<__PLACEHOLDER_VALUE__Slack channel ID or name (e.g., #hr-reviews)__>"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.4
    },
    {
      "id": "fc763339-85a4-4f70-80e9-3b798dae237d",
      "name": "Log to Google Sheets - Accept",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        144,
        464
      ],
      "parameters": {
        "columns": {
          "value": {
            "role": "={{ $('Workflow Configuration').first().json.jobRole }}",
            "email": "={{ $json.candidateEmail }}",
            "notes": "={{ $json.reason }}",
            "score": "={{ $json.score }}",
            "status": "accepted",
            "timestamp": "={{ $now.toISO() }}",
            "candidate_name": "={{ $json.candidateName }}"
          },
          "schema": [
            {
              "id": "candidate_name",
              "required": false,
              "displayName": "candidate_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "required": false,
              "displayName": "email",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "required": false,
              "displayName": "notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "email"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "<__PLACEHOLDER_VALUE__Sheet name (e.g., Candidates)__>"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Google Sheets document ID__>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "7ed3fd81-78c2-40cb-9ec8-c3b345938d4c",
      "name": "Log to Google Sheets - Reject",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        512,
        144
      ],
      "parameters": {
        "columns": {
          "value": {
            "role": "={{ $('Workflow Configuration').first().json.jobRole }}",
            "email": "={{ $json.candidateEmail }}",
            "notes": "={{ $json.reason }}",
            "score": "={{ $json.score }}",
            "status": "rejected",
            "timestamp": "={{ $now.toISO() }}",
            "candidate_name": "={{ $json.candidateName }}"
          },
          "schema": [
            {
              "id": "candidate_name",
              "required": false,
              "displayName": "candidate_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "required": false,
              "displayName": "email",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "required": false,
              "displayName": "notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "email"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "<__PLACEHOLDER_VALUE__Sheet name (e.g., Candidates)__>"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Google Sheets document ID__>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "bcfedb2e-c90d-4c36-9b2b-f77583a81512",
      "name": "Log to Google Sheets - Borderline",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        448,
        -160
      ],
      "parameters": {
        "columns": {
          "value": {
            "role": "={{ $('Workflow Configuration').first().json.jobRole }}",
            "email": "={{ $json.candidateEmail }}",
            "notes": "={{ $json.reason }}",
            "score": "={{ $json.score }}",
            "status": "escalated",
            "timestamp": "={{ $now.toISO() }}",
            "candidate_name": "={{ $json.candidateName }}"
          },
          "schema": [
            {
              "id": "candidate_name",
              "required": false,
              "displayName": "candidate_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "email",
              "required": false,
              "displayName": "email",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "role",
              "required": false,
              "displayName": "role",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "score",
              "required": false,
              "displayName": "score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notes",
              "required": false,
              "displayName": "notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "email"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "<__PLACEHOLDER_VALUE__Sheet name (e.g., Candidates)__>"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Google Sheets document ID__>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "90b27025-6acf-434b-8a85-037b5b86b74a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2480,
        48
      ],
      "parameters": {
        "width": 528,
        "height": 496,
        "content": "## How it works\nThis workflow automates resume screening using AI. When a candidate uploads a resume via webhook, the system extracts the text and evaluates it against a target job role.\n\nAn AI agent scores the candidate (0\u2013100) based on skills, experience, education, and overall fit. Based on thresholds, candidates are automatically categorized as accepted, rejected, or borderline.\n\nAccepted candidates receive a success email, rejected candidates receive a polite rejection, and borderline candidates are flagged for manual HR review via Slack.\n\nAll outcomes are logged into Google Sheets for tracking and audit.\n\n## Setup steps\n- Configure webhook for resume upload\n- Add OpenAI API credentials\n- Set Gmail and Slack credentials\n- Connect Google Sheets for logging\n- Define job role and score thresholds"
      },
      "typeVersion": 1
    },
    {
      "id": "24f4ab89-d3e2-4b4d-a93c-ddbab29fb597",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 464,
        "height": 272,
        "content": "## Input Layer\nWebhook receives resume and Define job role and scoring thresholds"
      },
      "typeVersion": 1
    },
    {
      "id": "a54cdfac-f715-4184-aeca-365970ed6473",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1072,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 272,
        "content": "## Resume Processing\nExtract and structure resume text data"
      },
      "typeVersion": 1
    },
    {
      "id": "d226c39b-4ca2-45eb-81a8-9dcbd194846a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 272
      },
      "typeVersion": 1
    },
    {
      "id": "cdc8dcd5-5759-4d55-99e8-fa79481bbf81",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -256
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 272,
        "content": "## Borderline Flow\nNotify HR in Slack for manual review"
      },
      "typeVersion": 1
    },
    {
      "id": "2f8eb1ca-bd70-455f-b4f1-becdf1256236",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        64
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 272,
        "content": "## Rejection email and logging \nSend rejection email and log to sheets"
      },
      "typeVersion": 1
    },
    {
      "id": "47a40211-2d57-4d3f-b965-3c177e365335",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 272,
        "content": "## accepted resumes\nSend acceptance email and log to sheets"
      },
      "typeVersion": 1
    },
    {
      "id": "2b10200c-3c9b-4435-ad4c-3282fcc4e70c",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -784,
        32
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 320,
        "content": "## AI Evaluation\nScore candidate and generate decision"
      },
      "typeVersion": 1
    },
    {
      "id": "28c4899e-1a63-4f15-92cc-333de906e766",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -448,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 272,
        "content": "## Decision Routing\nRoute candidates based on score thresholds"
      },
      "typeVersion": 1
    },
    {
      "id": "0162d169-9a1e-4085-9997-d911b56d6813",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 272,
        "content": "## flow for low or borderline scored resumes"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "AI Resume Scorer": {
      "main": [
        [
          {
            "node": "Check Score - Accept",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Resume Scorer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Resume Text": {
      "main": [
        [
          {
            "node": "Prepare Resume Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Resume Data": {
      "main": [
        [
          {
            "node": "AI Resume Scorer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Score - Accept": {
      "main": [
        [
          {
            "node": "Send Acceptance Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check Score - Borderline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Rejection Email": {
      "main": [
        [
          {
            "node": "Log to Google Sheets - Reject",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resume Upload Webhook": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Acceptance Email": {
      "main": [
        [
          {
            "node": "Log to Google Sheets - Accept",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify HR - Borderline": {
      "main": [
        [
          {
            "node": "Log to Google Sheets - Borderline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Extract Resume Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Score - Borderline": {
      "main": [
        [
          {
            "node": "Notify HR - Borderline",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Rejection Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI Resume Scorer",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}