This workflow follows the Gmail → 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 →
{
"name": "Job Application Processor & Candidate Scorer",
"nodes": [
{
"parameters": {
"content": "## \ud83d\udccb Job Application Processor\n\n### What this workflow does\n1. Watches Gmail for job applications\n2. Downloads resume attachments\n3. Extracts candidate info from resume\n4. Scores based on experience & skills\n5. Logs to ATS spreadsheet\n6. Sends auto-reply to candidate\n7. Notifies HR team via Slack\n\n### Setup steps\n1. Connect Gmail OAuth2 credentials\n2. Get PDF Vector API key from pdfvector.com/api-keys\n3. Create Google Sheet with columns below\n4. Connect Slack and pick your HR channel\n5. Update required skills in Score Candidate node\n\n### Sheet columns\nName, Email, Phone, Location, Current Title, Years Experience, Score, Status, Skills, Skills Matched, Application Date, Processed Date\n\n### Perfect for\n- HR teams\n- Recruiters\n- Small businesses",
"height": 480,
"width": 320,
"color": 5
},
"id": "sticky-main",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
0,
0
]
},
{
"parameters": {
"content": "## \ud83c\udfaf Scoring Logic\n\n- Experience: 40 points\n- Education: 30 points\n- Skills match: 30 points\n\n### Status\n- 75+ \u2192 Schedule Interview\n- 50-74 \u2192 Review Further\n- <50 \u2192 Pass\n\nEdit required skills in the\nScore Candidate node.",
"height": 220,
"width": 240
},
"id": "sticky-scoring",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
704,
0
]
},
{
"parameters": {
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"filters": {
"includeSpamTrash": false
}
},
"id": "gmail-trigger",
"name": "Gmail Trigger",
"type": "n8n-nodes-base.gmailTrigger",
"typeVersion": 1,
"position": [
128,
272
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "get",
"messageId": "={{ $json.id }}",
"simple": false,
"options": {
"downloadAttachments": true
}
},
"id": "gmail-get",
"name": "Get a message",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.2,
"position": [
320,
272
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Rename attachment_0 to data for PDF Vector compatibility\nconst items = $input.all();\n\nfor (const item of items) {\n if (item.binary && item.binary.attachment_0) {\n item.binary.data = item.binary.attachment_0;\n delete item.binary.attachment_0;\n }\n}\n\nreturn items;"
},
"id": "prepare-binary",
"name": "Prepare Binary File",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
528,
272
]
},
{
"parameters": {
"operation": "extract",
"inputType": "file",
"prompt": "Extract candidate resume data as flat fields. fullName, email, phone, location (city and state), linkedIn, yearsOfExperience (number), currentTitle, mostRecentCompany, secondMostRecentCompany, highestDegree (e.g. Bachelor, Master, PhD, Other), educationField, educationInstitution, technicalSkillsList (comma-separated technical skills as single string), softSkillsList (comma-separated soft skills as single string), certificationsList (comma-separated certifications as single string), languagesList (comma-separated languages as single string), keyAchievements (semicolon-separated top 3 achievements as single string).",
"schema": "{\"type\": \"object\", \"properties\": {\"fullName\": {\"type\": \"string\"}, \"email\": {\"type\": \"string\"}, \"phone\": {\"type\": \"string\"}, \"location\": {\"type\": \"string\"}, \"linkedIn\": {\"type\": \"string\"}, \"yearsOfExperience\": {\"type\": \"number\"}, \"currentTitle\": {\"type\": \"string\"}, \"mostRecentCompany\": {\"type\": \"string\"}, \"secondMostRecentCompany\": {\"type\": \"string\"}, \"highestDegree\": {\"type\": \"string\"}, \"educationField\": {\"type\": \"string\"}, \"educationInstitution\": {\"type\": \"string\"}, \"technicalSkillsList\": {\"type\": \"string\"}, \"softSkillsList\": {\"type\": \"string\"}, \"certificationsList\": {\"type\": \"string\"}, \"languagesList\": {\"type\": \"string\"}, \"keyAchievements\": {\"type\": \"string\"}}, \"additionalProperties\": false}"
},
"id": "pdfvector-extract",
"name": "PDF Vector - Parse Resume",
"type": "n8n-nodes-pdfvector.pdfVector",
"typeVersion": 2,
"position": [
736,
272
],
"credentials": {
"pdfVectorApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const candidate = ($input.first().json?.data || $input.first().json) || {};\nconst emailData = $('Gmail Trigger').item.json;\n\nconst requiredSkills = [\n 'javascript', 'python', 'react', 'node', 'sql',\n 'api', 'git', 'aws', 'docker', 'typescript'\n];\n\n// Experience scoring (40 pts)\nlet expScore = 0;\nconst years = parseFloat(candidate.yearsOfExperience) || 0;\nif (years >= 7) expScore = 40;\nelse if (years >= 5) expScore = 35;\nelse if (years >= 3) expScore = 25;\nelse if (years >= 1) expScore = 15;\nelse expScore = 5;\n\n// Education scoring (30 pts)\nlet eduScore = 10;\nconst degree = (candidate.highestDegree || '').toLowerCase();\nif (degree.includes('phd') || degree.includes('doctorate')) eduScore = 30;\nelse if (degree.includes('master') || degree.includes('mba')) eduScore = 25;\nelse if (degree.includes('bachelor')) eduScore = 20;\n\n// Skills matching (30 pts)\nconst skillsStr = (candidate.technicalSkillsList || '').toLowerCase();\nlet skillsMatched = 0;\nrequiredSkills.forEach(skill => { if (skillsStr.includes(skill)) skillsMatched++; });\nconst skillsScore = Math.min(skillsMatched * 3, 30);\n\nconst totalScore = expScore + eduScore + skillsScore;\nlet status = 'Pass';\nif (totalScore >= 75) status = 'Schedule Interview';\nelse if (totalScore >= 50) status = 'Review Further';\n\nreturn [{ json: {\n fullName: candidate.fullName || 'Unknown',\n email: candidate.email || '',\n phone: candidate.phone || 'N/A',\n location: candidate.location || 'N/A',\n currentTitle: candidate.currentTitle || 'N/A',\n yearsOfExperience: years,\n mostRecentCompany: candidate.mostRecentCompany || 'N/A',\n highestDegree: candidate.highestDegree || 'N/A',\n educationField: candidate.educationField || 'N/A',\n skillsList: candidate.technicalSkillsList || '',\n certificationsList: candidate.certificationsList || 'N/A',\n keyAchievements: candidate.keyAchievements || 'N/A',\n applicantEmail: emailData?.from?.value?.[0]?.address || candidate.email || 'unknown',\n applicationDate: emailData?.date || new Date().toISOString(),\n score: totalScore,\n expScore, eduScore, skillsScore,\n skillsMatched,\n status,\n processedAt: new Date().toISOString()\n}}];"
},
"id": "score-candidate",
"name": "Score Candidate",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
944,
272
]
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "YOUR_SPREADSHEET_ID",
"mode": "list"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"Name": "={{ $json.fullName }}",
"Email": "={{ $json.email || $json.applicantEmail }}",
"Phone": "={{ $json.phone || 'N/A' }}",
"Location": "={{ $json.location || 'N/A' }}",
"Current Title": "={{ $json.currentTitle || 'N/A' }}",
"Years Experience": "={{ $json.yearsOfExperience || 0 }}",
"Score": "={{ $json.score }}/100",
"Status": "={{ $json.status }}",
"Skills": "={{ $json.skillsList }}",
"Skills Matched": "={{ $json.skillsMatched }}/10",
"Application Date": "={{ $json.applicationDate }}",
"Processed Date": "={{ $json.processedAt.split('T')[0] }}"
}
},
"options": {}
},
"id": "sheets-log",
"name": "Add to ATS",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.5,
"position": [
1152,
272
],
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"sendTo": "={{ $('Score Candidate').item.json.email || $('Score Candidate').item.json.applicantEmail }}",
"subject": "Thank you for your application",
"emailType": "text",
"message": "=Dear {{ $('Score Candidate').item.json.fullName }},\n\nThank you for your interest in joining our team. We have received your application and our team is currently reviewing it.\n\nWe will be in touch within the next few business days regarding next steps.\n\nBest regards,\nThe Hiring Team",
"options": {}
},
"id": "gmail-reply",
"name": "Send Auto-Reply",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1344,
272
],
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"authentication": "oAuth2",
"select": "channel",
"channelId": {
"__rl": true,
"value": "YOUR_SLACK_CHANNEL_ID",
"mode": "list"
},
"text": "=\ud83d\udce5 *New Application Received*\n\n*Candidate:* {{ $('Score Candidate').item.json.fullName }}\n*Current Role:* {{ $('Score Candidate').item.json.currentTitle || 'N/A' }}\n*Experience:* {{ $('Score Candidate').item.json.yearsOfExperience || 0 }} years\n*Score:* {{ $('Score Candidate').item.json.score }}/100\n*Status:* {{ $('Score Candidate').item.json.status }}\n\n*Top Skills:* {{ $('Score Candidate').item.json.skillsList.split(', ').slice(0, 5).join(', ') || 'N/A' }}",
"otherOptions": {}
},
"id": "slack-notify",
"name": "Notify HR Team",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
1536,
272
],
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Gmail Trigger": {
"main": [
[
{
"node": "Get a message",
"type": "main",
"index": 0
}
]
]
},
"Get a message": {
"main": [
[
{
"node": "Prepare Binary File",
"type": "main",
"index": 0
}
]
]
},
"Prepare Binary File": {
"main": [
[
{
"node": "PDF Vector - Parse Resume",
"type": "main",
"index": 0
}
]
]
},
"PDF Vector - Parse Resume": {
"main": [
[
{
"node": "Score Candidate",
"type": "main",
"index": 0
}
]
]
},
"Score Candidate": {
"main": [
[
{
"node": "Add to ATS",
"type": "main",
"index": 0
}
]
]
},
"Add to ATS": {
"main": [
[
{
"node": "Send Auto-Reply",
"type": "main",
"index": 0
}
]
]
},
"Send Auto-Reply": {
"main": [
[
{
"node": "Notify HR Team",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"tags": [
{
"name": "PDF Vector"
},
{
"name": "HR"
},
{
"name": "Recruiting"
},
{
"name": "Resume Parser"
},
{
"name": "ATS"
}
],
"meta": {
"templateCredsSetupCompleted": false
}
}
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.
gmailOAuth2googleSheetsOAuth2ApipdfVectorApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
Streamline your recruitment process by automatically processing incoming job applications via email, extracting key details from attached CVs, and scoring candidates based on predefined criteria to prioritise top talent. Ideal for hiring managers or small HR teams handling high volumes of applications without dedicated software. The workflow triggers on new Gmail messages, parses the PDF resume using pdfVector to analyse content, then scores the candidate via custom logic before logging results in Google Sheets for easy review.
Use this workflow when you receive resumes as email attachments and need a quick, automated way to filter applicants, such as for entry-level roles with consistent scoring needs. Avoid it for complex assessments requiring human review or non-PDF formats like Word documents. Common variations include integrating Slack notifications for high-scoring candidates or adapting the scoring code for specific job skills.
About this workflow
Job Application Processor & Candidate Scorer. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.
Source: https://github.com/khanhduyvt0101/workflows/blob/main/n8n-workflows/job-application-processor.json — 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.
13. Insurance Pre-Authorization. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 12 nodes.
Shipping Document Processor. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 10 nodes.
W14 - Purchase Order Processor & Approval Workflow. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 9 nodes.
Insurance Claim Document Processor. Uses gmailTrigger, gmail, n8n-nodes-pdfvector, googleSheets. Event-driven trigger; 9 nodes.
Bank Statement Analyzer & Budget Tracker. Uses stickyNote, gmailTrigger, gmail, n8n-nodes-pdfvector. Event-driven trigger; 8 nodes.