This workflow corresponds to n8n.io template #9764 — we link there as the canonical source.
This workflow follows the Agent → Gmail Trigger 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": "qDFNsMCGATg1FRWI",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "First-Round Fast Track AI Recruiter Assistant",
"tags": [
{
"id": "KJBejNus697fO7pf",
"name": "AI Recruitment",
"createdAt": "2025-10-20T04:22:26.368Z",
"updatedAt": "2025-10-20T04:22:26.368Z"
},
{
"id": "0YIv3sHe4YfGRTXi",
"name": "AI CV Screening",
"createdAt": "2025-10-20T04:29:28.171Z",
"updatedAt": "2025-10-20T04:29:28.171Z"
},
{
"id": "4taM1rx0a5pq1YGF",
"name": "AI Candidate Scoring",
"createdAt": "2025-10-20T04:29:37.354Z",
"updatedAt": "2025-10-20T04:29:37.354Z"
},
{
"id": "OW4VQhn7AYD0hmTw",
"name": "AI Job Matching",
"createdAt": "2025-10-20T04:48:14.245Z",
"updatedAt": "2025-10-20T04:48:14.245Z"
}
],
"nodes": [
{
"id": "67ba2250-7192-46bb-ae03-3f52e52d1212",
"name": "Trigger Google Docs Conversion",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1920,
-208
],
"parameters": {
"url": "={{ $json.headers.location }}",
"method": "PUT",
"options": {
"response": {
"response": {
"fullResponse": true
}
}
},
"sendBody": true,
"sendQuery": true,
"contentType": "binaryData",
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"queryParameters": {
"parameters": [
{
"name": "fields",
"value": "=id,name,mimeType,webViewLink,parents"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "={{ ( (b => {\n const fn = (b?.fileName || '').toLowerCase();\n const mt = (b?.mimeType || '').toLowerCase();\n if (fn.endsWith('.doc')) return 'application/msword';\n if (fn.endsWith('.docx')) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n if (mt && mt !== 'application/octet-stream') return mt;\n return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';\n})(Object.values($binary || {})[0] || {})).replace(/[\\u0000-\\u001F\\u007F]+/g,'').trim() }}"
}
]
},
"inputDataFieldName": "={{ Object.keys($binary || {})[0] }}",
"nodeCredentialType": "googleDriveOAuth2Api"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "51864d3b-8a5d-472b-9d69-61f3c0b810fb",
"name": "Stream Doc/Docx File",
"type": "n8n-nodes-base.httpRequest",
"position": [
-2320,
-112
],
"parameters": {
"url": "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true",
"method": "POST",
"options": {
"response": {
"response": {
"fullResponse": true
}
}
},
"jsonBody": "={\n \"name\": \"{{ ( () => {\n const sender = ($json.from?.value?.[0]?.name || '').toString().trim().replace(/[\\r\\n]+/g,'');\n const fallback = (Object.values($binary||{})[0]?.fileName || 'document').replace(/\\\\.[^/.]+$/,'');\n const who = sender || fallback;\n\n const parts = new Intl.DateTimeFormat('en-GB', {\n timeZone: 'UTC',\n year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', second: '2-digit',\n hour12: false\n }).formatToParts(new Date());\n\n const p = {}; parts.forEach(x => p[x.type] = x.value);\n const date = `${p.year}-${p.month}-${p.day}`; // YYYY-MM-DD\n const time = `${p.hour}${p.minute}${p.second}`;\n\n return `${who} - CV - ${date}_${time}`.replace(/_{2,}/g,'_').trim();\n })() }}\",\n \"mimeType\": \"application/vnd.google-apps.document\",\n \"parents\": [\"YOUR_FOLDER_ID_HERE\"]\n}\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "=application/json; charset=UTF-8"
}
]
},
"nodeCredentialType": "googleDriveOAuth2Api"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "64ce3d1c-ac9d-4562-bbf1-4250f50fb555",
"name": "Preserve CV file",
"type": "n8n-nodes-base.code",
"position": [
-2320,
-288
],
"parameters": {
"jsCode": "// simply pass the incoming items untouched so downstream Merge gets original binary\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "18fa7d4c-12b3-45a3-8aac-e756f2c08ef5",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
-2096,
-208
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "c3d0d223-88c6-4af6-8e43-454c779c3a60",
"name": "Standardize",
"type": "n8n-nodes-base.set",
"position": [
-992,
48
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "b4b5c9c5-fe76-40eb-b234-48c042f0baa7",
"name": "cv_text",
"type": "string",
"value": "={{ $json.cv_text }}"
},
{
"id": "e6933fbf-fcf7-48cc-a763-9ca67de28322",
"name": "cv_webviewlink",
"type": "string",
"value": "={{ $json.cv_webviewlink }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f3219f5d-cda5-41b6-94fc-743d0b1e8279",
"name": "Extract from File",
"type": "n8n-nodes-base.extractFromFile",
"position": [
1296,
-80
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "bdc929ce-fc8e-4779-b0e1-f74965bfd44e",
"name": "Information Extractor",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
-512,
448
],
"parameters": {
"text": "={{ $('Standardize').first().json.cv_text }}",
"options": {},
"attributes": {
"attributes": [
{
"name": "First Name",
"required": true,
"description": "First name of the candidtae"
},
{
"name": "Last Name",
"required": true,
"description": "Last name of the candidate"
},
{
"name": "Email Address",
"description": "Email address of the candidate"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "73b50c74-acf9-4e0e-8881-0b5860a19136",
"name": "Append row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
-176,
448
],
"parameters": {
"columns": {
"value": {
"CV": "={{ $('Standardize').first().json.cv_webviewlink }}",
"Date": "={{ $now.setZone('UTC').format('yyyy-MM-dd HH:mm') }}",
"Email": "={{ $json.output['Email Address'] }}",
"JD Match": "={{ $('Recruiter Scoring Agent').item.json.output.selected_jd_filename }}",
"Last Name": "={{ $json.output['Last Name'] }}",
"Strengths": "={{ $('Recruiter Scoring Agent').item.json.output.candidate_strengths.join(\"\\n\\n\") }}",
"First Name": "={{ $json.output['First Name'] }}",
"Weaknesses": "={{ $('Recruiter Scoring Agent').item.json.output.candidate_weaknesses.join(\"\\n\\n\") }}",
"Overall Fit": "={{ $('Recruiter Scoring Agent').item.json.output.overall_fit_rating }}",
"Risk Factor": "={{ $('Recruiter Scoring Agent').item.json.output.risk_factor.score }} \n\n{{ $('Recruiter Scoring Agent').item.json.output.risk_factor.explanation }}",
"Justification": "={{ $('Recruiter Scoring Agent').item.json.output.justification_for_rating }}",
"Reward Factor": "={{ $('Recruiter Scoring Agent').item.json.output.reward_factor.score }}\n\n{{ $('Recruiter Scoring Agent').item.json.output.reward_factor.explanation }}",
"Submission ID": "={{ \n (( $json.output?.['Last Name'] || 'unknown' ) + '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '') // sanitize\n + '_' +\n new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0,14) // UTC YYYYMMDDHHmmss\n}}",
"Sent From Email": "={{ $('Receive CV via Email').first().json.from.value[0].address }}"
},
"schema": [
{
"id": "Submission ID",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Submission ID",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date",
"type": "string",
"display": true,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "CV",
"type": "string",
"display": true,
"required": false,
"displayName": "CV",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "First Name",
"type": "string",
"display": true,
"required": false,
"displayName": "First Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Last Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Email",
"type": "string",
"display": true,
"required": false,
"displayName": "Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Sent From Email",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Sent From Email",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Strengths",
"type": "string",
"display": true,
"required": false,
"displayName": "Strengths",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Weaknesses",
"type": "string",
"display": true,
"required": false,
"displayName": "Weaknesses",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Risk Factor",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Risk Factor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reward Factor",
"type": "string",
"display": true,
"required": false,
"displayName": "Reward Factor",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "JD Match",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "JD Match",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Overall Fit",
"type": "string",
"display": true,
"required": false,
"displayName": "Overall Fit",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Justification",
"type": "string",
"display": true,
"required": false,
"displayName": "Justification",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "TC Decision",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "TC Decision",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ZNXqOSfSusmQxmCbNQ61Y-Ln9l0jCTzSfRc-JA_y9Oo/edit#gid=0",
"cachedResultName": "1. AI Candidate Screening"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1ZNXqOSfSusmQxmCbNQ61Y-Ln9l0jCTzSfRc-JA_y9Oo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1ZNXqOSfSusmQxmCbNQ61Y-Ln9l0jCTzSfRc-JA_y9Oo/edit?usp=drivesdk",
"cachedResultName": "AI Candidate Screening"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "8e9c7f2c-3cb6-44cb-936f-27bd5e1f3dd7",
"name": "Get Web Link",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1728,
-208
],
"parameters": {
"url": "=https://www.googleapis.com/drive/v3/files/{{ $json.body.id }}?fields=id,name,mimeType,webViewLink,webContentLink,parents",
"options": {},
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleDriveOAuth2Api"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "341f3daf-bffa-4388-97a0-457334d61774",
"name": "Download CV - PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
-1840,
208
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "56d16d49-6123-482b-b2a5-ea1919fc3ce2",
"name": "Download CV - GDoc as PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
-1536,
-208
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.id }}"
},
"options": {
"googleFileConversion": {
"conversion": {
"docsToFormat": "application/pdf"
}
}
},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "7b7210cb-fde5-4f8f-a4ba-b68af8711f17",
"name": "Switch - File Type",
"type": "n8n-nodes-base.switch",
"position": [
-2624,
48
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Doc-Docx",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f3cab524-be2f-44ad-bc73-664ee982e4ff",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ (Object.values($binary || {})[0]?.mimeType || '').toLowerCase() }}",
"rightValue": "={{ \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\" || \"application/msword\" }}"
}
]
},
"renameOutput": true
},
{
"outputKey": "PDF",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "dac4774b-bbf3-4033-a144-15802529d97a",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ (Object.values($binary || {})[0]?.mimeType || '').toLowerCase() }}",
"rightValue": "application/pdf"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "10b57b25-585c-4e27-8024-945763f66d99",
"name": "Upload CV - PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
-2144,
208
],
"parameters": {
"name": "={{ $('Receive CV via Email').item.json.from.value[0].name + ' - CV - ' + (()=>{const parts=new Intl.DateTimeFormat('en-GB',{timeZone:'UTC',year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false}).formatToParts(new Date());const p={};parts.forEach(x=>p[x.type]=x.value);return `${p.year}-${p.month}-${p.day}_${p.hour}${p.minute}${p.second}`;})() }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "1tabXACSCjelI0y82m7M7TKlvm0ta6_06",
"cachedResultUrl": "https://drive.google.com/drive/folders/1tabXACSCjelI0y82m7M7TKlvm0ta6_06",
"cachedResultName": "Candidate CVs"
},
"inputDataFieldName": "={{ Object.keys($binary || {})[0] }}"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "8616f65e-71e2-453c-bfd8-cda7c512fe35",
"name": "Extract from PDF Download",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-1344,
-208
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "59848ee7-40b0-4d99-8b27-49088f249fbb",
"name": "Extract from PDF",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-1536,
208
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "7461a572-e6c2-4e6a-b63c-88788e1c5a1e",
"name": "Download Selected JD",
"type": "n8n-nodes-base.googleDrive",
"position": [
944,
-256
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.output.email_match.jd_file_id }}"
},
"options": {
"googleFileConversion": {
"conversion": {
"docsToFormat": "application/pdf"
}
}
},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "19be4be4-3335-4f53-9bb2-103c9a1e5cca",
"name": "Receive CV via Email",
"type": "n8n-nodes-base.gmailTrigger",
"position": [
-2848,
48
],
"parameters": {
"simple": false,
"filters": {
"labelIds": [
"Label_5882671977694855295"
]
},
"options": {
"downloadAttachments": true,
"dataPropertyAttachmentsPrefixName": "cv_"
},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
}
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "0fae31aa-69ea-4176-9b4c-f4ab7fa2fdf1",
"name": "JD Matching Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"position": [
-672,
-272
],
"parameters": {
"text": "=## Candidate's Email (Prority JD matching method)\nEMAIL SUBJECT: {{ $('Receive CV via Email').item.json.subject }}\nEMAIL BODY: {{ $('Receive CV via Email').item.json.text }}\n\n## CANDIDATE CV (Fallback JD matching method if candidate's email does not state which role they're applying for)\n{{ $json.cv_text }}\n\n\n## AVAILABLE JOB DESCRIPTIONS:\nUse the Google Drvie tool to see the list of our current job description files and file IDs. They are each aptly named with a job title in the file name.\n\nAnalyze the email context first, and secondly the candidate's CV as a fallback method.\n- Select the SINGLE most appropriate job description if there is one that clearly relates to candidate's email contents. IF EMAIL SUBJECT OR BODY MENTIONS A ROLE THAT IS IDENTICAL OR CLOSELY RELATES TO A JD YOU SEE IN THE LIST PROVIDED, YOU MUST SELECT THAT JD.\n- OR, as the fallback method, select up to a maximum of 3 job descriptions that are a best match to the candidate's CV and profile.\n\n\n## YOUR RESPONSE FORMAT\nReturn your response in this exact JSON format:\n\nFor email match:\n{\n \"match_type\": \"email_match\",\n \"email_match\": {\n \"jd_filename\": \"Marketing Director JD\",\n \"jd_file_id\": \"1xxxxxxxxxxxxxxxxxxxxxx\",\n \"confidence\": \"high\"\n }\n}\n\nFor CV match (up to a maximum of 3 best-match JDs):\n{\n \"match_type\": \"cv_match\", \n \"cv_match\": [\n {\n \"jd_filename\": \"Marketing Director JD\",\n \"jd_file_id\": \"2xxxxxxxxxxxxxxxxxxxxxx\"\n },\n {\n \"jd_filename\": \"COO_JD.pdf\", \n \"jd_file_id\": \"3xxxxxxxxxxxxxxxxxxxxxx\"\n },\n {\n \"jd_filename\": \"Sales Enablement Lead - job description.pdf\",\n \"jd_file_id\": \"4xxxxxxxxxxxxxxxxxxxxxx\"\n }\n ]\n}",
"options": {
"systemMessage": "=You are an expert HR tech recruiter. Your task is to match a candidate with the most appropriate job description from as list of job descriptions I provide to you. \n\nAs a priority, you should first try to match a single JD based on the candidate's email (subject line and message) where possible. \n\nIf the contents of their email do not clearly state a JD or specific role, then you should match up to 3 JDs based on the content of their CV as a fallback method - but you do not have to match always 3: it can be less JDs if there is just 1 or 2 JDs that are only the best fit.\n\n## COMPANY DESCRIPTION\nOur company specialises in providing AI and Automation workflow solutions for small businesses, and we use tools such as n8n, Zapier, OpenAI, Claude Code, Airtable, and more."
},
"promptType": "define",
"hasOutputParser": true
},
"executeOnce": false,
"typeVersion": 2.2
},
{
"id": "99f8d6a6-3820-4744-8011-e15f66a4b133",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2432,
112
],
"parameters": {
"color": 5,
"width": 1472,
"height": 320,
"content": "## Get CV via PDF Format"
},
"typeVersion": 1
},
{
"id": "56eb1d87-b1cd-469a-ac19-8b71fe8735c6",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2432,
-384
],
"parameters": {
"color": 6,
"width": 1472,
"height": 464,
"content": "## Get CV via Word/Doc/Docx Format"
},
"typeVersion": 1
},
{
"id": "5ce6b66d-301e-45b0-a8a8-b2fdee67d833",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-928,
-384
],
"parameters": {
"color": 2,
"width": 1712,
"height": 624,
"content": "## Job Description (Vacany) Matching with Candidate's CV"
},
"typeVersion": 1
},
{
"id": "abd2b59f-381e-4f73-9fea-561466127101",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-928,
272
],
"parameters": {
"width": 1392,
"height": 560,
"content": "## CV Analysis and Feedback"
},
"typeVersion": 1
},
{
"id": "8e857a30-d21e-4753-b8f0-ecd834f5b195",
"name": "Detailed JD Matching Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
944,
-48
],
"parameters": {
"text": "=Compare this candidate's full CV against the detailed job descriptions provided. There will be a maximum of 3 separate job descriptions that were previously identified as being a best match for this candidate's profile.\n\nSelect the SINGLE best JD match by simply responding with the file name exactly like the input of each file name provided to you.\n\n\n## Candidate's CV\n{{ $('Standardize').item.json.cv_text }}\n\n\n## Pre-matched Job Descriptions\n\nJob Description 1: {{ $('Loop Over Items').all()[0].json.jd_filename }}\n{{ $('Loop Over Items').all()[0].json.text }}\n\n\n{% if $('Loop Over Items').all()[1] %}\nJob Description 2: {{ $('Loop Over Items').all()[1].json.jd_filename }}\n{{ $('Loop Over Items').all()[1].json.text }}\n{% endif %}\n\n\n{% if $('Loop Over Items').all()[2] %}\nJob Description 3: {{ $('Loop Over Items').all()[2].json.jd_filename }}\n{{ $('Loop Over Items').all()[2].json.text }}\n{% endif %}\n\n\n--\n\nSelect the best match and respond in this JSON format:\n{\n \"selected_jd\": {\n \"jd_filename\": \"exact_filename_here\"\n }\n}",
"options": {
"systemMessage": "=You are an expert HR tech recruiter. Your task is to match a candidate's profile based on their CV provided, with the best-fit job description provided from a maximum of 3 job descriptions.\n\n## COMPANY DESCRIPTION\nOur company specialises in providing AI and Automation workflow solutions for small businesses, and we use tools such as n8n, Zapier, OpenAI, Claude Code, Airtable, and more."
},
"promptType": "define",
"hasOutputParser": true
},
"executeOnce": false,
"typeVersion": 2.2
},
{
"id": "98c0d280-0aca-48e8-aaaa-86be4e2bde45",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
128,
-144
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "e8a4e7f4-b26a-4853-a23e-2710ce09cc51",
"name": "Download Selected JD1",
"type": "n8n-nodes-base.googleDrive",
"position": [
304,
0
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.jd_file_id }}"
},
"options": {
"googleFileConversion": {
"conversion": {
"docsToFormat": "application/pdf"
}
}
},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "1e55c3ab-4dc4-42fa-990b-26600f5f4d9e",
"name": "Extract from File1",
"type": "n8n-nodes-base.extractFromFile",
"position": [
464,
0
],
"parameters": {
"options": {},
"operation": "pdf"
},
"typeVersion": 1
},
{
"id": "eaea53b0-fd3e-4d16-bfd9-d6d7c93f3e0e",
"name": "Gemini 2.5 Flash",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-736,
-80
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "f0311480-1820-4229-89d6-87634e1aae32",
"name": "Access JD Files",
"type": "n8n-nodes-base.googleDriveTool",
"position": [
-576,
-80
],
"parameters": {
"filter": {
"folderId": {
"__rl": true,
"mode": "list",
"value": "1UWI0TanlIGOec_d3S2HJzDymT-51BxHm",
"cachedResultUrl": "https://drive.google.com/drive/folders/1UWI0TanlIGOec_d3S2HJzDymT-51BxHm",
"cachedResultName": "Job Descriptions"
}
},
"options": {},
"resource": "fileFolder"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
},
{
"id": "74d68403-76d4-4970-bd17-8bdeab243e8f",
"name": "Transform for Multiple JDs",
"type": "n8n-nodes-base.code",
"position": [
-96,
-176
],
"parameters": {
"jsCode": "// Transform the AI agent output for looping\nif ($json.output.match_type === \"cv_match\") {\n // Return each JD as a separate item for the loop\n return $json.output.cv_match.map(jd => ({\n jd_filename: jd.jd_filename,\n jd_file_id: jd.jd_file_id\n }));\n} else {\n // For email match, return single item\n return [{\n jd_filename: $json.output.email_match.jd_filename,\n jd_file_id: $json.output.email_match.jd_file_id\n }];\n}"
},
"typeVersion": 2
},
{
"id": "203a733c-9916-420e-91b1-e8704f666ed5",
"name": "JD Match w/Email?",
"type": "n8n-nodes-base.if",
"position": [
-304,
-272
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "dde6fee9-e053-49de-aacf-b63f33fabec3",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.output.match_type }}",
"rightValue": "email_match"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "19dcdeaa-edf5-4a8d-8060-ebcd6cb32c8b",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
496,
-384
],
"parameters": {
"color": 2,
"width": 1296,
"height": 1216,
"content": ""
},
"typeVersion": 1
},
{
"id": "423054a7-88f1-410e-bc0e-c2801f706acf",
"name": "Set",
"type": "n8n-nodes-base.set",
"position": [
624,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c1376530-e0d7-4ce5-abb7-bd5839a34ecb",
"name": "jd_filename",
"type": "string",
"value": "={{ $('Loop Over Items').item.json.jd_filename }}"
},
{
"id": "85efbcf5-b293-4058-aef6-8a6dfbed1c0a",
"name": "jd_file_id",
"type": "string",
"value": "={{ $('Loop Over Items').item.json.jd_file_id }}"
},
{
"id": "63d88a73-7144-458f-812b-f53ccd8fda49",
"name": "text",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "4b7440a0-2382-4afb-ab4e-ca58146b1d56",
"name": "Set as Selected JD Format",
"type": "n8n-nodes-base.set",
"position": [
1552,
448
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "271dc7a2-e8c3-40f7-9e7c-04c4463490f0",
"name": "selected_jd_filename",
"type": "string",
"value": "={{ $('JD Match w/Email?').item.json.output.email_match.jd_filename }}"
},
{
"id": "2e987315-7b9b-47ca-a2ca-400978944b7a",
"name": "selected_jd_file_id",
"type": "string",
"value": "={{ $('JD Match w/Email?').item.json.output.email_match.jd_file_id }}"
},
{
"id": "f9d90d18-8193-46d0-a9b2-a3fe4edcd7e5",
"name": "selected_jd_text",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e376226f-f68d-41f6-b38c-3268f5c4b970",
"name": "Match Selected JD Name with Full Text",
"type": "n8n-nodes-base.code",
"position": [
1296,
208
],
"parameters": {
"jsCode": "// Get the selected filename from the AI agent output\nconst selectedFilename = $json.output.selected_jd.jd_filename;\n\n// Get all the JD data from the loop output\nconst allJDs = $('Loop Over Items').all();\n\n// Find the matching JD by filename\nconst selectedJD = allJDs.find(jd => jd.json.jd_filename === selectedFilename);\n\nif (!selectedJD) {\n throw new Error(`No JD found with filename: ${selectedFilename}`);\n}\n\n// Return just the selected JD data\nreturn [{\n selected_jd_filename: selectedJD.json.jd_filename,\n selected_jd_file_id: selectedJD.json.jd_file_id,\n selected_jd_text: selectedJD.json.text\n}];"
},
"typeVersion": 2
},
{
"id": "642dac3e-e494-4f70-ae54-ea5dab98cea2",
"name": "Gemini 2.5 Pro-1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
912,
160
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-pro"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "450ddf83-b4ad-4834-9286-29b63c4552f6",
"name": "Gemini 2.5 Flash-1",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-512,
672
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "44889a9c-1185-4aaa-b14b-864a4599b2b7",
"name": "Standardize Web Link and CV Text (PDF)",
"type": "n8n-nodes-base.set",
"position": [
-1168,
208
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "f7a58896-424f-46a5-8849-a75f89bf20b0",
"name": "cv_webviewlink",
"type": "string",
"value": "={{ $('Upload CV - PDF').item.json.webViewLink }}"
},
{
"id": "0e202a35-b3c6-4c2d-893e-ad986d00ef6c",
"name": "cv_text",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "8132c536-2de6-440c-8202-fc7cec6ceccb",
"name": "Standardize Web Link and CV Text (GDoc)",
"type": "n8n-nodes-base.set",
"position": [
-1168,
-208
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1c868154-561f-4897-8828-e12c66dba01b",
"name": "cv_webviewlink",
"type": "string",
"value": "={{ $('Get Web Link').item.json.webViewLink }}"
},
{
"id": "0a02aa8c-b464-44c1-a962-bbddcde33fa9",
"name": "cv_text",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d8af1a95-21f4-4ee8-8de5-9a6387e2475f",
"name": "Send Candidate Screening Confirmation",
"type": "n8n-nodes-base.slack",
"position": [
32,
448
],
"parameters": {
"select": "channel",
"blocksUi": "={\n\t\"blocks\": [\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"I've just completed the screening of a new candidate, who sent their CV moments ago. I've intelligently matched the candidate with the JD we have -- either through the candidate directly applying for this role, or selecting the best matched JD if they didn't apply for a specific role.\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"I've provided the following *Overall Fit* score and *Justification*:\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"divider\"\n\t\t},\n\t\t{\n\t\t\t\"type\": \"table\",\n\t\t\t\"rows\": [\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"Name\",\n\t\t\t\t\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\t\t\t\t\"bold\": true\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"CV\",\n\t\t\t\t\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\t\t\t\t\"bold\": true\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"Job Role Matched\",\n\t\t\t\t\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\t\t\t\t\"bold\": true\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"Overall Fit\",\n\t\t\t\t\t\t\t\t\t\t\"style\": {\n\t\t\t\t\t\t\t\t\t\t\t\"bold\": true\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t}\n\t\t\t\t],\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"{{ $json['First Name'] }} {{ $json['Last Name'] }}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"link\",\n\t\t\t\t\t\t\t\t\t\t\"url\": \"{{ $json.CV }}\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"CV - {{ $json['First Name'] }} {{ $json['Last Name'] }}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"{{ $json['JD Match'] }}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"type\": \"rich_text\",\n\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"type\": \"rich_text_section\",\n\t\t\t\t\t\t\t\t\"elements\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"type\": \"text\",\n\t\t\t\t\t\t\t\t\t\t\"text\": \"{{ $json['Overall Fit'] }}/10\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"type\": \"divider\"\n\t\t},\n\t\t{\n\t\t\t\"type\": \"context\",\n\t\t\t\"elements\": [\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\"text\": \"_Submission ID: {{ $json['Submission ID'] }}_\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"type\": \"section\",\n\t\t\t\"text\": {\n\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\"text\": \"<https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit?usp=sharing|View the full analysis in the AI Candidate Screening sheet>\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"actions\",\n\t\t\t\"elements\": [\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"button\",\n\t\t\t\t\t\"text\": {\n\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\"text\": \"Proceed w/Candidate\",\n\t\t\t\t\t\t\"emoji\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"value\": \"{\\\"action\\\":\\\"proceed_candidate\\\",\\\"submission_id\\\":\\\"{{ $json['Submission ID'] }}\\\",\\\"candidate_name\\\":\\\"{{ $json['First Name'] }} {{ $json['Last Name'] }}\\\",\\\"email\\\":\\\"{{ $json.Email }}\\\",\\\"cv_url\\\":\\\"{{ $json.CV }}\\\",\\\"jd_match\\\":\\\"{{ $json['JD Match'] }}\\\",\\\"overall_fit\\\":\\\"{{ $json['Overall Fit'] }}\\\",\\\"first_name\\\":\\\"{{ $json['First Name'] }}\\\",\\\"last_name\\\":\\\"{{ $json['Last Name'] }}\\\"}\",\n\t\t\t\t\t\"action_id\": \"proceed_action\",\n\t\t\t\t\t\"style\": \"primary\",\n\t\t\t\t\t\"confirm\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Confirm Proceed\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"text\": {\n\t\t\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\t\t\"text\": \"Are you sure you want to **proceed** with this candidate?\\n\\nThis will:\\n\u2022 Move them to the next interview stage\\n\u2022 Update the screening spreadsheet\\n\u2022 Notify the hiring team\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"confirm\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Yes, Proceed\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"deny\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Cancel\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"button\",\n\t\t\t\t\t\"text\": {\n\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\"text\": \"Reject Candidate\",\n\t\t\t\t\t\t\"emoji\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"value\": \"{\\\"action\\\":\\\"reject_candidate\\\",\\\"submission_id\\\":\\\"{{ $json['Submission ID'] }}\\\",\\\"candidate_name\\\":\\\"{{ $json['First Name'] }} {{ $json['Last Name'] }}\\\",\\\"email\\\":\\\"{{ $json.Email }}\\\",\\\"cv_url\\\":\\\"{{ $json.CV }}\\\",\\\"jd_match\\\":\\\"{{ $json['JD Match'] }}\\\",\\\"overall_fit\\\":\\\"{{ $json['Overall Fit'] }}\\\",\\\"first_name\\\":\\\"{{ $json['First Name'] }}\\\",\\\"last_name\\\":\\\"{{ $json['Last Name'] }}\\\"}\",\n\t\t\t\t\t\"action_id\": \"reject_action\",\n\t\t\t\t\t\"style\": \"danger\",\n\t\t\t\t\t\"confirm\": {\n\t\t\t\t\t\t\"title\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Confirm Rejection\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"text\": {\n\t\t\t\t\t\t\t\"type\": \"mrkdwn\",\n\t\t\t\t\t\t\t\"text\": \"Are you sure you want to **reject** this candidate?\\n\\nThis will:\\n\u2022 Mark them as rejected in the system\\n\u2022 Update the screening spreadsheet\\n\u2022 Send the candidate a friendly rejection email\\n\u2022 This action cannot be undone\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"confirm\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Yes, Reject\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"deny\": {\n\t\t\t\t\t\t\t\"type\": \"plain_text\",\n\t\t\t\t\t\t\t\"text\": \"Cancel\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}\n",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09G7DR7X52",
"cachedResultName": "talent"
},
"messageType": "block",
"otherOptions": {
"includeLinkToWorkflow": false
},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "5a0b4b2c-1c67-4fd1-b72d-d047ad727cb5",
"name": "Recruiter Scoring Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-832,
448
],
"parameters": {
"text": "=Candidate's CV (Resume):\n{{ $('Standardize').first().json.cv_text }}\n",
"options": {
"systemMessage": "=# Overview\nYou are an expert sales and technical recruiter specializing in AI, automation, and software roles. You have been given a job description and a candidate resume. Your task is to analyze the resume in relation to the job description and provide a detailed screening report.\n\nFocus specifically on how well the candidate matches the core requirements and ideal profile outlined in the job description. Evaluate both technical skill alignment and business-context understanding. Use reasoning grounded in the actual content of the resume and job post \u2013 avoid making assumptions.\n\n## Output\nYour output should follow this exact format:\n\nJob Description Matched:\nA simple direct copy of {{ $json.selected_jd_filename }}\n\nCandidate Strengths:\nList the top strengths or relevant qualifications the candidate brings to the table. Be specific.\n\nCandidate Weaknesses:\nList areas where the candidate is lacking or mismatched based on the job description.\n\nRisk Factor:\n- Assign a risk score (Low / Medium / High)\n- Explain the worst-case scenario if this candidate is hired.\n\nReward Factor:\n- Assign a reward score (Low / Medium / High)\n- Describe the best-case scenario \u2013 what value could this candidate unlock?\n- Does the candidate appear to be a short-term or long-term fit?\n\nOverall Fit Rating (0\u201310):\nAssign a number between 0 (terrible match) and 10 (perfect match). Do not give decimals.\n\nJustification for Rating:\nExplain clearly why this candidate received that score. Reference specific resume content and how it aligns or doesn't with the job description.\n\n\n## Job Description\n\nFilename: {{ $json.selected_jd_filename }}\n\nJD content:\n{{ $json.selected_jd_text }}\n"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "37f0cded-a0bd-4f8c-9512-54cd1312d171",
"name": "Gemini 2.5 Pro-2",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
-848,
672
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.5-pro"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6b175b49-903b-4682-b610-1bfaa97c9623",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-3840,
-880
],
"parameters": {
"color": 7,
"width": 912,
"height": 1536,
"content": "# **First-Round Fast Track AI Recruiter Assistant**\n *CV \u2192 Match \u2192 Screen \u2192 Decide, all automated*\n\n This workflow automatically processes candidate CVs from email, intelligently matches them to job descriptions, performs\n AI-powered screening analysis, and sends actionable summaries to your team in Slack.\n\n **Good to know**\n - Handles both PDF and Word document CVs automatically\n - Two-stage JD matching: prioritizes role mentioned in candidate's email, falls back to CV analysis if needed\n - Uses Google Gemini API for AI screening (generous free tier and rate limits, typically enough to avoid paying for API requests, but check latest pricing at [Google AI Pricing](https://ai.google.dev/pricing))\n - All CVs stored in Google Drive with standardized naming (candidate name + date/time)\n - Complete audit trail logged in Google Sheets\n\n ## Who's it for\n Hiring teams and recruiters who want to automate first-round CV screening while maintaining quality. Perfect for companies \n receiving high volumes of applications across multiple roles, especially in tech, sales, or automation-focused positions.\n\n ## How it works\n 1. Gmail monitors inbox for CVs with specific label and downloads attachments\n 2. Detects file type (PDF or Word) and converts/standardizes format for text extraction\n 3. AI agent matches candidate to best-fit job description by analyzing email context first (if candidate mentioned a role), or \n CV content as fallback (selects up to 3 potential JD matches)\n 4. If multiple JDs matched, second AI agent selects the single best fit\n 5. AI recruiter agent analyzes CV against selected JD and generates structured screening report (strengths, weaknesses,\n risk/reward factors, overall fit score 0-10 with justification)\n 6. Extracts candidate details (name, email) from CV text\n 7. Logs complete analysis to Google Sheets tracker\n 8. Sends formatted summary to Slack with Proceed/Reject action buttons for instant team decisions\n\n ## Requirements\n - Gmail account with API access\n - Google Drive account (OAuth2)\n - Google Sheets account (OAuth2)\n - Slack workspace with bot permissions\n - Google Gemini API key ([Get free key here](https://makersuite.google.com/app/apikey))\n - Google Drive folders: one for CVs, one for Job Descriptions (as PDFs or Google Docs)\n\n ## How to set up\n 1. Add credentials: Gmail OAuth2, Google Drive OAuth2, Google Sheets OAuth2, Slack OAuth2, Google Gemini API\n 2. Create Gmail label (e.g., \"CV-Screening\") for incoming candidate emails\n 3. In \"Receive CV via Email\" node: select your Gmail label for filtering\n 4. Create two Google Drive folders: \"Candidate CVs\" and \"Job Descriptions\"\n 5. In \"Upload CV - PDF\" and \"Stream Doc/Docx File\" nodes: update folder ID to your \"Candidate CVs\" folder\n 6. In \"Access JD Files\" node: update folder ID to your \"Job Descriptions\" folder\n 7. Create Google Sheet named \"AI Candidate Screening\" with columns matching the [sample AI Candidate Screening sheet](https://docs.google.com/spreadsheets/d/16HebkHqsM2ZE_IdJzQk1mDE3i2-HwsUqa5gEwXaF-7A/edit?usp=sharing) \n 8. In \"Append row in sheet\" node: select your Google Sheet\n 9. In \"Send Candidate Screening Confirmation\" node: select your Slack channel and enter your Google Sheet ID in the Blocks section\n 10. Activate workflow\n\n ## Customizing this workflow\n - **Change JD matching logic**: Edit \"JD Matching Agent\" node prompt to adjust how CVs are matched to roles (e.g., weight\n technical skills vs. experience).\n - **Change \"Company Description\" in AI prompts**: Insert your \"Company Description\" in System Message sections in \"JD Matching Agent\" and \"Detailed JD Matching Agent\" nodes\n - **Modify screening criteria**: Edit \"Recruiter Scoring Agent\" node system message to focus on specific qualities (culture \n fit, leadership, technical depth, etc.)\n - **Add more storage locations**: Add nodes to save CVs to other systems (Notion, Airtable, ATS platforms)\n - **Customize Slack message**: Edit \"Send Candidate Screening Confirmation\" node to change formatting, add more context, or \n include additional candidate data\n - **Auto-proceed logic**: Add IF node after screening to auto-proceed candidates with fit score above threshold (e.g., 8+/10) \n - **Add email responses**: Connect nodes to automatically email candidates (confirmation, rejection, interview invite)\n - **Add human-in-the-loop**: Sub-workflow triggered by Slack response or email response, to update Sheet with approve/reject status\n- **Add candidate email responses + interview scheduling**: For approved candidates, trigger email to candidate with Cal.com or Calendly link so they can book their interview"
},
"typeVersion": 1
},
{
"id": "3fc25e0b-d3a9-428b-8d75-a684a397301a",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2896,
464
],
"parameters": {
"color": 7,
"width": 544,
"height": 192,
"content": "## Acknowledgments\n This workflow was inspired by [Nate Herk's YouTube demonstration](https://www.youtube.com/watch?v=M0s6O8xtVUE) on building a resume analysis system. This implementation builds upon that foundation by adding dynamic job description matching (initial + detailed JD matching agents), Slack Block Kit integration with interactive buttons, updated Google Drive API methods for document handling, and enhanced candidate data capture in Google Sheets."
},
"typeVersion": 1
},
{
"id": "5d1d64c0-df2b-4b9d-b7af-46ae3619e5d9",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2896,
-880
],
"parameters": {
"color": 7,
"width": 864,
"height": 464,
"content": " ## Quick Troubleshooting\n * **No CVs being processed**: Check Gmail label is correctly set in \"Receive CV via Email\" node and emails are being labeled \n * **Word documents failing**: Verify \"Stream Doc/Docx File\" node has correct parent folder ID and Google Drive credentials \n authorized\n * **JD matching returns no results**: Check \"Access JD Files\" node folder ID points to your Job Descriptions folder, and JD \n files are named clearly (e.g., \"Marketing Director JD.pdf\")\n * **JD matching is not relevant for my company**: Update the \"Company Description\" in the System Messages in the \"JD Matching Agent\" and \"Detailed JD Matching Agent\" nodes\n * **\"Can't find matching JD\"**: Ensure candidate's email mentions role name OR their CV clearly indicates relevant experience \n for available JDs\n * **Google Sheets errors**: Verify sheet name is \"AI Candidate Screening\" and column headers exactly match workflow\n expectations (Submission ID, Date, CV, First Name, etc.)\n * **Slack message not appearing**: Re-authorize Slack credentials and confirm channel ID in \"Send Candidate Screening\n Confirmation\" node\n * **Missing candidate name/email**: CV text must be readable - check PDF extraction quality or try converting complex CVs to \n simpler format\n * **401/403 API errors**: Re-authorize all OAuth2 credentials (Gmail, Google Drive, Google Sheets, Slack)\n * **AI analysis quality issues**: Edit system prompts in \"JD Matching Agent\" and \"Recruiter Scoring Agent\" nodes to refine \n screening criteria"
},
"typeVersion": 1
},
{
"id": "c3bcef58-069f-4193-b6d9-f3cafdb30677",
"name": "Structured Output Parser-1",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-416,
-80
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"match_type\": {\n \"type\": \"string\",\n \"enum\": [\"email_match\", \"cv_match\"],\n \"description\": \"Whether the match was found in email or requires CV analysis\"\n },\n \"email_match\": {\n \"type\": \"object\",\n \"properties\": {\n \"jd_filename\": {\n \"type\": \"string\",\n \"description\": \"Exact filename of the matched JD (PDF or Google Doc)\"\n },\n \"jd_file_id\": {\n \"type\": \"string\",\n \"description\": \"Google Drive file ID for downloading the JD file\"\n },\n \"confidence\": {\n \"type\": \"string\",\n \"enum\": [\"high\", \"medium\", \"low\"],\n \"description\": \"Confidence level of the email-based match\"\n }\n },\n \"required\": [\"jd_filename\", \"jd_file_id\", \"confidence\"]\n },\n \"cv_match\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"jd_filename\": {\n \"type\": \"string\",\n \"description\": \"JD filename to download and analyze (PDF or Google Doc)\"\n },\n \"jd_file_id\": {\n \"type\": \"string\",\n \"description\": \"Google Drive file ID for downloading the JD file\"\n }\n },\n \"required\": [\"jd_filename\", \"jd_file_id\"]\n },\n \"minItems\": 1,\n \"maxItems\": 3,\n \"description\": \"Up to a maximum of 3 JD files for detailed analysis, but 1-2 JD files is also fine if you do not think there are 3 JD files relevant\"\n }\n },\n \"required\": [\"match_type\"],\n \"oneOf\": [\n {\n \"properties\": {\n \"match_type\": {\"const\": \"email_match\"}\n },\n \"required\": [\"email_match\"]\n },\n {\n \"properties\": {\n \"match_type\": {\"const\": \"cv_match\"} \n },\n \"required\": [\"cv_match\"]\n }\n ]\n}"
},
"typeVersion": 1.3
},
{
"id": "3977e0ec-5ba4-4a31-94c3-1efb5d3e2e2c",
"name": "Structured Output Parser-3",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-672,
672
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"name\": \"resume_screening_evaluation\",\n \"description\":
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.
gmailOAuth2googleDriveOAuth2ApigooglePalmApigoogleSheetsOAuth2ApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
CV → Match → Screen → Decide, all automated
Source: https://n8n.io/workflows/9764/ — 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.
🎯 Create viral TikToks, Shorts, Reels, podcasts, and ASMR videos in minutes — all on autopilot.
> Note: This workflow uses sticky notes extensively to document each logical section of the automation. Sticky notes are mandatory and already included to explain OCR, AI parsing, folder logic, dup
This automation is designed to help you generate AI-powered music tracks, cover art, and fully rendered music videos — all triggered from a simple Telegram chat and managed via Google Sheets.
LinkedIn URL → Scrape → Match → Screen → Decide, all automated
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.