I built an n8n workflow that parses resumes from Gmail, scores candidates automatically, and notifies HR on Slack — free ATS for small teams

Hiring without an ATS means someone is manually opening every resume email, copy-pasting details into a spreadsheet, and trying to remember which candidates were actually good. Built a workflow that handles the intake process automatically so HR only deals with decisions, not data entry.

What it does

New email with resume attachment → downloads the PDF → extracts candidate info → scores against your required skills → logs to ATS spreadsheet → sends auto-reply to candidate → notifies HR on Slack

Takes about 10-15 seconds per application.

Scoring logic

Three components, 100 points total:

Experience (40 points max)

  • 7+ years → 40 pts

  • 5-6 years → 35 pts

  • 3-4 years → 25 pts

  • 1-2 years → 15 pts

  • Under 1 year → 5 pts

Education (30 points max)

  • PhD / Doctorate → 30 pts

  • Master’s / MBA → 25 pts

  • Bachelor’s → 20 pts

  • No degree found → 10 pts (base)

Skills match (30 points max)

Compares extracted technical skills against your required skills list. 3 points per match, max 10 skills = 30 points. You customize the required skills list in the Score Candidate node.

Status thresholds:

  • 75+ → Schedule Interview

  • 50-74 → Review Further

  • Below 50 → Pass

What gets extracted from the resume

  • Full name, email, phone, location, LinkedIn URL

  • Total years of experience, current job title

  • Work history — company, title, dates, key achievements

  • Education — institution, degree, field, year

  • Technical skills (languages, tools, frameworks)

  • Soft skills, certifications, languages spoken

The Slack notification


📥 New Application Received

Candidate: Jane Smith

Current Role: Senior Frontend Developer

Experience: 6 years

Score: 82/100

Status: Schedule Interview

Top Skills: JavaScript, React, TypeScript, Node.js, AWS

Fires the moment the resume is processed. HR sees the score before opening the spreadsheet.

The auto-reply

Sends a plain acknowledgment email to the candidate automatically:


Dear [Name],

Thank you for your interest in joining our team.

We have received your application and our team is

currently reviewing it.

We will be in touch within the next few business

days regarding next steps.

Best regards,

The Hiring Team

Customizable in the Send Auto-Reply node.

What lands in Google Sheets

Each row: Name, Email, Phone, Location, Current Title, Years Experience, Score, Status, Skills, Skills Matched, Application Date, Processed Date

Filter by Status column to get your shortlist instantly.

Setup

You’ll need:

  • Gmail account (where applications arrive)

  • Google Sheets (free)

  • n8n instance (self-hosted — uses PDF Vector community node)

  • PDF Vector account (free tier: 100 credits/month, roughly 30 resumes)

  • Slack (optional — delete last node if not needed)

About 15 minutes to configure.

Download

Workflow JSON:

github.com/khanhduyvt0101/workflows

Full workflow collection:

khanhduyvt0101/workflows


Setup Guide

Step 1: Get your PDF Vector API key

Sign up at https://www.pdfvector.com — free plan works fine. Go to API Keys and generate a key.

Step 2: Set up your Google Sheet

Create a new spreadsheet with these exact headers in Row 1:


Name | Email | Phone | Location | Current Title | Years Experience | Score | Status | Skills | Skills Matched | Application Date | Processed Date

Copy the Sheet ID from the URL (long string between /d/ and /edit).

Step 3: Import the workflow

Download the JSON from GitHub and import into n8n via Import from File.

Step 4: Configure the nodes

Gmail Trigger:

  • Connect your Google account (OAuth2)

  • Watches all incoming Gmail by default — add a filter label if you want to limit to specific emails (e.g., only emails labeled “Application”)

Get a message:

  • Same Gmail credential

  • downloadAttachments: true is already set — this downloads the resume PDF

Prepare Binary File:

  • No config needed — renames attachment_0 to data for PDF Vector compatibility

PDF Vector - Parse Resume:

  • Add new credential (Bearer Token type)

  • Paste your API key

Score Candidate:

  • Edit the requiredSkills array at the top of the code to match your job requirements

  • Default is set for a software engineering role — change to whatever you’re hiring for

  • Adjust score thresholds at the bottom if needed

Add to ATS:

  • Connect Google Sheets

  • Paste your Sheet ID

Send Auto-Reply:

  • Same Gmail credential

  • Edit the email body to match your company tone

Notify HR Team:

  • Connect Slack

  • Set your HR channel ID

Step 5: Test it

Send yourself a test email with a PDF resume attached. Wait about 30 seconds and check your Sheet, your Slack channel, and the sender’s inbox for the auto-reply.


Accuracy

Tested across resumes in various formats — single column, multi-column, creative layouts, and plain text.

  • Name, email, phone extraction: ~98% on digital PDFs

  • Work experience parsing: ~93% — dates occasionally malformed

  • Skills extraction: ~95%

  • Education: ~97%

  • Years of experience calculation: ~90% — sometimes off when candidates have gaps or overlapping roles

Scanned resumes (photo/image PDFs): drops to ~85% overall. Still usable but worth spot-checking.

Cost

Free tier is 100 credits/month. Each resume uses about 3 credits. So roughly 33 resumes per month for free.

Basic plan is $25/month for 3,000 credits (~1,000 resumes) if you’re doing volume hiring.

Customizing it

Change required skills for different roles:

Open the Score Candidate node and edit the requiredSkills array. For a marketing role you might use ['seo', 'google analytics', 'copywriting', 'hubspot', 'paid ads'].

Adjust experience weighting:

The three scoring components (experience 40, education 30, skills 30) can be rebalanced. For entry-level roles you might weight skills higher and experience lower.

Add a minimum score filter:

Insert an IF node after Score Candidate to only log and notify on candidates above a threshold. Useful if you’re getting a high volume of unqualified applications.

Trigger from Google Drive instead of Gmail:

If candidates submit through a form that saves resumes to Drive, swap the Gmail Trigger and Get a message nodes for a Google Drive Trigger + Download File.

Send rejection email for Pass status:

Add a second email branch after the IF node that sends a polite rejection to candidates who scored below 50.


Limitations

  • Requires self-hosted n8n (PDF Vector is a community node)

  • Watches all Gmail — you’ll want to add a filter label or sender filter for production use

  • Scoring is rules-based, not AI judgment — a candidate with 8 years but no matching skills will still score lower than intended

  • Doesn’t parse LinkedIn profiles, only PDF/Word resumes attached to email

  • Auto-reply sends immediately — no way to review before it fires unless you add an approval step


Links


Questions? Drop a comment if something’s not working or you want to adjust the scoring for a different role type.

very cool!

Nice setup. The rules-based scoring with three explicit components (experience/education/skills) is actually cleaner than using LLM judgment for this — more predictable and auditable, which matters when HR is asking “why did this candidate get rejected.”

A few things I’d add based of running similar pipelines:

On the scanned PDF accuracy drop to ~85% — worth adding an explicit check for extractionMethod or image-based indicators in the PDF metadata before scoring. If it’s a scanned resume, flag it in the Sheets row with a “needs-review” status instead of auto-routing to Pass. Avoids discarding qualified candidates based on OCR noise.

On the Gmail filter — if you’re doing volume hiring across multiple roles, a label-based approach works but breaks down when someone forwards a resume from a different thread. A form-based intake (Google Form → Drive → workflow trigger) is more reliable for production. You mentioned this as a swap option which is the right call.

On the auto-reply timing — one thing that bites people here is the “sends immediately” behavior when candidates apply outside business hours. An IF node checking the hour + day before Send Auto-Reply keeps it from firing at 2am. Minor but candidates notice.

The approval step gap is real — adding a manual review step before the Slack notification for borderline candidates (50-74 range) would make this more defensible for actual hiring decisions vs. just screening. Could be as simple as a Wait node with an email approval before the HR Slack message fires for “Review Further” status.

Good share — this fills a real gap for small teams that can’t justify an ATS subscription.

This is a really solid implementation. The scoring logic is especially well thought out — having experience, education, and skills as separate weighted components makes the output actionable rather than just a single opaque number.

A few things I would add or watch for based on building similar pipelines:

On the PDF parsing step: PDF Vector works well, but edge cases to watch: multi-column resume layouts, text-in-image resumes (scanned PDFs), and resumes with tables for skills/experience. Worth adding a fallback node that flags low-confidence parses for manual review rather than silently scoring them wrong.

On the Gmail trigger: Make sure you are filtering by subject keywords or a specific email address/label — otherwise any attachment in Gmail will trigger it. Adding an IF node early in the workflow to check {{ $json.subject }} contains “application” or {{ $json.from }} matches a recruiting email pattern saves a lot of noise.

On the auto-reply timing: You might want to add a small delay (30-60s) before the auto-reply fires, so it does not look like an instant bot response. Small UX detail but candidates notice.

What model are you using for the extraction step — GPT-4o or 3.5? Curious if you have tested structured output / JSON mode for more reliable field extraction.

Really love how clean the scoring logic is here — three-component system with clear thresholds makes it easy to tune without touching code.

One thing I’ve been playing with in similar intake workflows: adding a fourth signal for “recency weight” on experience. A candidate with 6 years of experience but 4 of them in a different field before a pivot looks very different from 6 straight years on target. GPT handles this surprisingly well if you ask it to flag role relevance separately from raw years.

Also curious how you’re handling resumes that come in as .docx or image-based PDFs — does PDF Vector handle those gracefully, or do you have a pre-processing step?

I run something similar for lead intake (not hiring, but scoring inbound leads against ICP criteria), and the “auto-reply fires immediately” limitation you flagged is real pain. My workaround: added a Slack approval step where HR can choose “approve auto-reply / hold / custom reply” before it sends. Adds one human touchpoint but keeps the rest automated. Worth it for roles where the auto-reply tone matters.

Great share — this is exactly the kind of workflow that saves small teams from drowning in spreadsheets.

Thanks you!

Hi, :blush:all great catches, especially the scanned resume flagging. You’re right, auto-routing OCR’d resumes to “Pass” based on lower scores is brutal. Should flag those for manual review instead.

The 2am auto-reply thing is hilarious because it happened to me once. Candidate applied at 11pm, got instant reply, immediately knew it was automated. Added a business hours check after that.

Form → Drive → trigger is way cleaner for production, you’re right. Gmail works for low volume but gets messy fast when people forward stuff or cc you on random emails.

For the borderline approval - I like the Wait node idea. Right now 50-74 goes straight to Slack as “Review Further” but having HR confirm before any outreach would be smarter. Adds like 2 nodes but makes it way more defensible.

On the model - PDF Vector uses their own extraction (mix of GPT-4 + Claude I think). Haven’t tested structured output mode directly but the JSON schema in the node forces pretty reliable field extraction.

Have you built the approval step flow? Curious how you’re handling the manual review gate.