82 CVs Ranked by Job Fit in 11 Minutes for 14 Cents

Abstract tech illustration: 82 CVs Ranked by Job Fit in 11 Minutes for 14 Cents

Every ATS on the market filters CVs by keywords. Every halfway-decent candidate games those keywords. So the top of your shortlist is usually just whoever copy-pasted your job ad the cleanest — and your real hires are buried around position 50, waiting for someone to open a PDF at 4pm on Friday.

Here's the exact scoring pipeline I built for a 4-person recruiting shop. It reads the actual job description, ranks 82 CVs in about 11 minutes, costs 14 cents per batch, and — on the run I'll walk through below — surfaced the three eventual hires from positions 47, 52, and 61 in the keyword-sorted pile.

The bottleneck is ranking, not sourcing

Small recruiting shops don't lose hours to intake or scheduling. They lose them to reading. A mid-market backend role goes live Monday. By Thursday there are 82 CVs in a shared Gmail. The founder spends most of Friday clicking PDFs, dragging names into a spreadsheet, and hoping she didn't miss the good one. That's 4–6 hours per role per week, and attention collapses somewhere around CV 30.

The industry answer is an ATS at roughly $39/seat/month. What you actually get is a keyword filter with a nicer UI. It counts hits on Python, B2B, 5 years, and ranks by density. Every candidate coaching video on YouTube tells applicants to stuff those keywords in, so the system rewards the people who read the job ad most carefully — not the people who can do the job.

Ranking a CV against a specific JD is the one thing an LLM is genuinely good at. It's also the one thing platforms hide behind a per-seat price tag.

What the pipeline actually is

  • A Gmail label (intake)
  • An n8n workflow (orchestration)
  • A Claude scoring prompt (the work)
  • A Google Sheet (the shortlist)

Four moving parts. About 40 nodes of config. No vector DB, no fine-tune, no ATS.

Step 1: Gmail label as the intake queue

Skip webhooks and API keys. Gmail labels are a free, durable queue that any recruiter on the team can manage without breaking anything.

  1. Create a label Applicants-<RoleSlug>, e.g. Applicants-BackendSr.
  2. Create a Gmail filter: subject contains the role name, or from: matches your LinkedIn / Indeed forwarder addresses.
  3. Apply label automatically, skip inbox if you want it out of the founder's face.

If your careers alias is jobs@ and LinkedIn EasyApply forwards to it, the filter looks like:

from:(jobs-listings@linkedin.com OR noreply@indeed.com) OR subject:("Backend Engineer")
→ Apply label: Applicants-BackendSr

That's it. Every application from now on lands in a queryable bucket with a stable label.

Step 2: n8n workflow — Gmail → PDF text

In n8n, wire four nodes:

  1. Gmail Trigger — poll every 5 minutes, filter by label Applicants-BackendSr.
  2. Loop Over Items on attachments.
  3. Filter — keep only mimeType = application/pdf. Add a parallel branch for .docx if you want; ~90% of applicants send PDFs, so you can ship v1 without it.
  4. Extract from File node in PDF mode → outputs data.text.

You now have raw CV text in memory, keyed to the sender's email and the original Gmail message ID (keep that — it's your link back to the source of truth).

{
  "candidate_email": "jane.doe@gmail.com",
  "gmail_message_id": "18f2c...",
  "cv_text": "Jane Doe\nSenior Backend Engineer\n8 years experience...",
  "received_at": "2026-08-04T09:12:44Z"
}

One warning: Extract from File chokes on scanned/image PDFs (usually senior candidates who exported from LinkedIn as an image). Add an OCR fallback (Tesseract or a cheap vision call) for the 5–8% that fail — or flag them for manual review. Silent failures are the fastest way to lose trust in the pipeline.

Step 3: The Claude scoring prompt

This is where the real work happens. The prompt has three parts, in this order:

  1. The full raw job description, pasted in exactly as the client wrote it. Not a summary. Not extracted keywords. The whole thing.
  2. The CV text.
  3. The scoring rubric and output schema.

Four weighted dimensions, out of 100:

Dimension Weight What it measures
Domain match 30 Have they worked in this industry / problem space?
Seniority signal 25 Scope of ownership, team size, decision authority
Project evidence 30 Have they shipped something functionally like this?
Red flags −15 Job hopping without reason, missing dates, seniority mismatch

Here's the actual system prompt (trimmed):

You are screening CVs against a specific job description for a
recruiting agency. Score fairly. Do not reward keyword matching.
Reward demonstrated ability to do the work described.

JOB DESCRIPTION:
<<< {{ $json.job_description }} >>>

CANDIDATE CV:
<<< {{ $json.cv_text }} >>>

Score on four dimensions:
- domain_match (0-30): worked in the same problem space
- seniority_signal (0-25): scope, ownership, decision authority
- project_evidence (0-30): shipped something functionally similar
- red_flags (0-15, SUBTRACTED): unexplained gaps, hopping,
  seniority mismatch

Return ONLY valid JSON:
{
  "candidate_name": "string",
  "total_score": 0-100,
  "reason": "two sentences, plain English, why this score",
  "red_flag_note": "one line or empty string"
}

Reason must be exactly two sentences. Not a paragraph.

Two things matter here. First, paste the JD verbatim. Summaries strip the exact context that lets the model distinguish "built a payments platform" from "did payments integration work." Second, cap the reason at two sentences. You want to scan 50 of these in a minute, not read essays.

I use Claude Sonnet for this. On the 82-CV run, average input was ~2,400 tokens (JD + CV + instructions), output ~120 tokens. Total spend: $0.14. That's the whole run, not per CV.

Why the rubric is weighted the way it is

  • Project evidence (30) is tied for the top weight because "shipped X" is the single most predictive signal for mid-senior roles. Titles lie, side projects don't.
  • Domain match (30) matches it because a great generalist in the wrong domain still costs 6 months of ramp.
  • Seniority (25) is lower than most people expect — because it's the most gameable dimension on a CV, and interviews catch fraud here fast.
  • Red flags (−15) is a subtractor, not a filter. A brilliant candidate with an 8-month gap should still surface; the flag just tells the recruiter to ask about it.

Step 4: Google Sheet as the live shortlist

Final n8n node: Google Sheets → Append Row.

name score reason red_flags cv_link received_at
Jane Doe 88 Built and ran a payments ledger at ~2M tx/day, matching the JD's scale requirement. Led a team of 4 backend engineers for 3 years, matches the "senior IC or tech lead" ask. Gmail thread 2026-08-04 09:12
Mark P. 41 Backend experience is mostly CRUD on internal tools, no evidence of the distributed systems work the JD centers on. Titles suggest mid-level, JD asks senior. 2-year gap 2023-2025, no explanation Gmail thread 2026-08-04 09:14

Add a conditional format: score >= 75 → green background. Sort descending. That's the shortlist, updating live as applications land.

The cv_link column is critical — it points back to the original Gmail message so the recruiter can open the actual PDF in one click when a score looks interesting. Never make the sheet the source of truth. It's a view.

What actually happened on the 82-CV run

I ran 82 anonymized CVs from a real mid-senior backend search from last month. Dropped them into the labeled inbox in one batch. 11 minutes later, 82 scored rows.

  • Total time from first CV to sorted sheet: 11m 04s
  • Total Claude spend: $0.14
  • CVs that failed PDF extraction (scanned): 4 (flagged for manual review)
  • Scores above 75 (green): 9

The part that matters: the keyword-sorted version of the same pile had the three eventual hires at positions 47, 52, and 61.

  • Candidate at 47 described her work as "distributed systems" — the JD said "microservices." Same thing, different vocabulary.
  • Candidate at 52 built a payments platform end-to-end but never used the word "fintech."
  • Candidate at 61 had a non-obvious title ("Staff Software Engineer, Platform") that a keyword filter tokenized as generic.

The scoring agent read the JD as context, saw the underlying match, and pushed all three above 85. The founder read 12 reason lines, opened 5 PDFs, and had a shortlist in under 15 minutes. That's the win.

Where this beats a $39/seat ATS

Three concrete reasons, not marketing ones:

  • The JD travels with the prompt. No retraining, no admin setup, no "configure your scoring criteria" wizard. New role Monday morning? Paste the JD, hit run.
  • The reasoning is in plain English. When a client asks why candidate A over candidate B, you have an audit trail written like a human wrote it. Try getting that out of a keyword ranker.
  • Cost scales with usage, not headcount. $0.14 per 82-CV batch. A 4-person agency running 6 roles a month spends under $10/month. The ATS at $39/seat/month is $156. Same shop, 16× cheaper, better output.

The honest limits: this doesn't replace an ATS if you need pipelines, interview scheduling, offer letters, compliance reporting for enterprise clients. It replaces the ranking function, which is the part that was broken anyway. Keep your ATS if you need the rest of the workflow, but stop trusting its shortlist.

Why bizflowai.io helps with this

Most of the pipelines I build for small teams look exactly like this one — a boring intake queue, one LLM call doing the actual thinking, and a plain output surface a human already knows how to use. For recruiting shops specifically, I've wired variants that also draft first-pass rejection emails for scores under 40, auto-schedule intro calls for scores above 85 via Cal.com, and push weekly hiring funnel reports into Slack. Same 40-node shape, different last mile. If you're a 2–10 person shop drowning in one specific repetitive read-and-decide loop, that's the exact size problem this pattern is built for.


Want more like this?

I publish practical AI automation, GenAI engineering, and faceless content workflows on YouTube every week.

Subscribe to bizflowai.io on YouTube — never miss a new tutorial.

Planning an AI automation project or need a second opinion on your architecture?

Connect with me on LinkedIn — Lazar Milicevic, GenAI Engineer & bizflowai.io Founder.

Visit bizflowai.io for our services, case studies, and AI consulting.

Frequently asked questions

What is the real bottleneck in small-agency recruiting?

The bottleneck isn't sourcing, scheduling, or rejection emails. It's ranking: reading each CV against a specific job description and forming a defensible opinion about fit. A small agency can lose four to six hours per role per week to this, and after roughly thirty CVs human attention collapses, making the shortlist effectively random past that point.

Why do keyword-based ATS tools fail at CV ranking?

Most ATS platforms at around thirty-nine dollars per seat per month are essentially keyword filters with a nicer interface. They rank candidates by hits on terms like Python, B2B, or five years. Since candidate coaching videos tell applicants to stuff these exact keywords into their CVs, the system rewards people who read the job ad carefully, not people who can actually do the job.

How do I build an automated CV scoring workflow with n8n and Claude?

You need four parts: a Gmail label with a filter to capture applications, an n8n workflow triggered by that label which extracts PDF text from attachments, a Claude prompt containing the full job description plus scoring instructions, and a Google Sheet that appends each scored result as a row. Build time is about an afternoon from scratch, or forty minutes if you know n8n.

How should I structure a Claude scoring prompt for CVs?

Include three parts: the full job description pasted verbatim, the extracted CV text, and scoring instructions across four weighted dimensions. Score domain match at 30 points, seniority signal at 25, project evidence at 30, and subtract up to 15 for red flags like unexplained job hopping or missing dates. Ask Claude to return JSON with name, total score, a two-sentence reason, and a red flag note.

How much does it cost to score CVs with Claude at scale?

In a live run scoring 82 anonymized CVs for a mid-senior backend role, the entire batch completed in eleven minutes and cost fourteen cents in Claude API spend. That compares to four to six hours of founder time per role per week using manual review, or per-seat ATS subscriptions that only deliver keyword matching.