112 Applicants, 47 Filtered on Salary First: 71¢ Total

You post one role. Twenty applications land. You book eight calls. Four of them collapse the moment you say the number. That's an hour of your week gone because the salary conversation happened at minute twenty-eight instead of minute zero — and the fix is a 40-line node most hiring tutorials skip on purpose.
Why salary has to be the first filter, not the last
The default hiring stack scores skills first and asks about money last. That's backwards for small teams. Skill match without comp alignment produces a prettier list of the same wasted calls — you still end the week with candidates who want $140K on a $95K band, or who ghost the moment you name the number.
The bottleneck in solo and small-team hiring isn't finding qualified people. It's finding qualified people whose number fits your number. Every other filter — years of experience, stack fit, culture, portfolio depth — is downstream of that one gate. If comp doesn't overlap, everything else is theater.
Most published n8n and Zapier "AI recruiter" flows optimize for the demo, not the outcome. They read the CV, score against a JD, dump a Kanban. The tools monday and Ashby ship look great in a screenshot. None of them run a comp-first router before scheduling. That's the piece I'm going to show you.
- The bad path: intake → resume score → shortlist → intro call → salary reveal → collapse
- The right path: intake → salary extraction → three-way router → screening queue → intro call
The stack, and why it's boring on purpose
Boring stacks ship. Here's the wiring I use for clients running burst hires:
- Gmail — intake. One label:
applications-inbound. - n8n — orchestrator. Self-hosted, polls every 2 minutes.
- Claude Haiku — extraction model. Cheap, fast, structured output.
- Notion — talent database. Four custom fields (below).
- Telegram — human-in-the-loop for the ambiguous middle bucket.
Runs on a small home server. No per-seat SaaS tax, no vendor lock-in, no waiting on a product team to ship the filter you actually need. Total infra cost for the client run below: the electricity to keep n8n up, plus 71¢ in Anthropic tokens across 112 candidates.
One thing that matters more than it sounds: PDFs get converted to plain markdown before the model sees them. Vision on a CV is a waste — you're paying vision-tier tokens to read Arial 11pt. A pdf-parse node or pdftotext shell step drops both latency and cost significantly versus shoving the raw PDF at a multimodal model.
# in an n8n Execute Command node
pdftotext -layout "{{$binary.data.fileName}}" - | head -c 12000
Cap the character count. Nobody's comp signal is on page 7.
The 40-line salary extraction node
The prompt is narrow on purpose. Haiku isn't asked to score the candidate, summarize them, or judge fit. It's asked one thing: find any signal about compensation expectations.
That means:
- Explicit ranges — "$85K–$95K", "looking for 110", "current base is 78"
- Soft signals — "competitive", "open to discussion", "market rate"
- Location inference — SF Bay Area applying to a Kansas City mid-market role is a signal
- Employer-based inference — "currently at [named FAANG]" narrows the expected floor
The model returns three fields: extracted range, confidence 0–1, and the exact source snippet it pulled the signal from. That snippet is the whole ballgame — it's your audit trail when a hiring manager asks why a candidate got a polite auto-reply.
// n8n Function node — salary extractor (Anthropic call)
const prompt = `You extract ONLY compensation signals from job applications.
Return JSON:
{
"comp_low_usd": number | null,
"comp_high_usd": number | null,
"confidence": number, // 0.0 - 1.0
"signal_type": "explicit" | "soft" | "inferred" | "none",
"source_snippet": string // exact text, max 240 chars
}
Rules:
- "competitive" / "open" / "market" => soft, confidence <= 0.5
- Explicit range in cover letter => explicit, confidence >= 0.9
- Location + role level inference only => inferred, confidence <= 0.6
- No signal at all => "none", confidence 1.0, snippet ""
- NEVER guess a number without textual grounding.
APPLICATION:
"""
${$json.email_body}
--- CV ---
${$json.cv_markdown}
"""`;
const res = await this.helpers.httpRequest({
method: 'POST',
url: 'https://api.anthropic.com/v1/messages',
headers: {
'x-api-key': $env.ANTHROPIC_KEY,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: {
model: 'claude-haiku-4-5',
max_tokens: 400,
messages: [{ role: 'user', content: prompt }]
},
json: true
});
return [{ json: JSON.parse(res.content[0].text) }];
That's the whole extraction. ~40 lines with the wrapper. Average tokens per candidate in the real run: ~1,400 in, ~120 out. Per-candidate cost sits well under a cent.
The three-way router (this is where most people ship the wrong thing)
Binary routers — match or reject — quietly send great candidates polite rejection emails because a soft signal got misread. The fix is a third bucket for ambiguity.
Route logic in plain English:
- Confidence ≥ 0.75 AND range overlaps band → Notion screening queue
- Confidence ≥ 0.75 AND range clearly outside band → polite auto-reply naming the band, inviting future contact
- Confidence < 0.75 OR
signal_type == "none"→ Telegram alert to hiring manager with name, CV link, snippet, approve/reject buttons
// n8n IF chain
const { comp_low_usd, comp_high_usd, confidence } = $json;
const BAND_LOW = 85000;
const BAND_HIGH = 105000;
const overlaps =
comp_low_usd != null &&
comp_high_usd != null &&
comp_low_usd <= BAND_HIGH &&
comp_high_usd >= BAND_LOW;
if (confidence < 0.75 || $json.signal_type === 'none') {
return [{ json: { route: 'human_review' } }];
}
return [{ json: { route: overlaps ? 'advance' : 'auto_reject' } }];
The auto-reply is honest, not corporate. "Thanks for applying. Our budget for this role tops out at $105K. If that shifts for you, let us know and we'll pick it back up." That message has produced two hires in the last year from candidates who came back three months later with adjusted expectations. A generic "we'll keep your resume on file" burns that bridge.
The Telegram alert for the ambiguous bucket takes the hiring manager ~10 seconds per tap. Phone, thumb, done. No laptop, no inbox archaeology.
The Notion schema that makes this defensible
Your talent database needs four fields you probably don't have today:
| Field | Type | Why it matters |
|---|---|---|
comp_low_usd |
Number | Filter, sort, band analytics later |
comp_high_usd |
Number | Same |
extraction_confidence |
Number (0–1) | Sort ambiguous records for weekly review |
source_snippet |
Text | Non-negotiable — the receipt |
The source snippet is the difference between a black-box filter you can't defend and a system your hiring manager trusts. When someone asks why a candidate got auto-declined, you paste the exact sentence from their cover letter. That single field killed every "the AI is rejecting people unfairly" conversation on the client engagement below.
Add a fifth field — routing_decision — with the value the router produced. Now you have a queryable log: how many auto-rejects last month, how many false positives caught in the Telegram bucket, what the model's confidence distribution looks like. That's how you tune the 0.75 threshold up or down based on actual error rate, not vibes.
The real numbers from one client run
Three-week window, one senior individual-contributor role, US remote, $85K–$105K band.
| Metric | Value |
|---|---|
| Total applicants through the label | 112 |
| Auto-filtered on comp mismatch (≥ 0.75 conf) | 47 |
| Flagged to Telegram for human review | 12 |
| Advanced to screening queue | 53 |
| Total Anthropic token spend | $0.71 |
| Avg time from email arrival to routing decision | ~90 seconds |
| Intro calls that collapsed on salary | 0 |
Of the 47 auto-filtered: 31 were explicitly asking for $130K+, 9 were in high-cost metros with inferred floors above $115K, 7 named a current employer where the floor was public. Of the 12 flagged for review: 8 got approved, 4 got rejected — the model correctly abstained.
The hiring manager stopped taking calls with people whose number was going to blow up minute twenty-eight. That's it. That's the whole win. The system didn't hire anyone — it just stopped wasting the calendar on candidates the math already ruled out.
For comparison: monday's recruiting agent, Ashby's AI screening, and most of the "n8n recruiter" YouTube builds do resume scoring first and comp negotiation never. They optimize the wrong variable. A comp-first router in front of any of them would make them meaningfully better.
Where bizflowai.io helps with this
This is the class of workflow I ship for clients weekly at bizflowai.io — the boring, high-leverage filters that sit in front of the expensive human step. Comp-first hiring routers, invoice triage that flags mismatches before they hit AP, lead qualifiers that reject on budget signal before booking discovery calls. The pattern is always the same: one narrow extraction model, a three-way router with a human-in-the-loop bucket for the ambiguous middle, and an audit trail so the decision is defensible. No SaaS seat tax, no vendor lock-in, runs on infra you control.
What to steal from this
- Put the comp filter before the resume filter, not after
- Convert PDFs to markdown before the model reads them
- Return a
source_snippeton every extraction — it's your audit trail - Never ship a binary router; the third bucket for ambiguity is where you stop breaking things
- Use the auto-reject reply to name the band honestly — you'll get candidates back later
- Log the router decision to Notion; tune your confidence threshold on real error rate
The whole thing is ~200 lines of n8n JSON, one Anthropic key, and a Notion database. Total cost to run 112 candidates: 71 cents plus the electricity. Total calls saved: probably 6–8. Do the math on what your hour is worth.
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
Why should salary be the first filter in an AI hiring pipeline?
In small-team hiring, the bottleneck isn't finding qualified candidates — it's finding qualified candidates whose compensation expectations match your budget. Filtering on skills first produces a prettier list of the same wasted intro calls, where candidates disappear once the number is named. Screening for salary alignment before skills, culture, or fit eliminates hours of wasted calls per week.
How do I extract salary expectations from job applications using AI?
Route application emails to a Gmail label, poll it with n8n every two minutes, convert PDF attachments to markdown, then send the text to Claude Haiku with a narrow prompt asking only for compensation signals. The model returns three fields: extracted comp range, a confidence score from 0 to 1, and the exact source snippet it pulled from for auditability.
Why use a three-way router instead of match-or-reject for candidate filtering?
AI extraction models make mistakes, so a binary match-or-reject router will auto-reject good candidates. A three-way router sends high-confidence matches to a screening queue, high-confidence mismatches to a polite auto-reply, and ambiguous cases (confidence below 0.75 or no signal found) to a human via Telegram alert. This catches model errors before they cost you good hires.
What Notion fields do I need for an AI-powered talent database?
Beyond standard candidate fields, add four: extracted comp low, extracted comp high, confidence score, and source snippet. The source snippet is non-negotiable — it's the exact text the model pulled the salary signal from. This turns your filter from an undefendable black box into an auditable system where you can justify every auto-rejection with a receipt.
What tech stack runs an AI candidate screening pipeline without SaaS fees?
The pipeline uses Gmail for intake, n8n as the workflow orchestrator, Claude Haiku for salary extraction, Notion as the talent database, and Telegram for human-in-the-loop approvals on ambiguous cases. It runs on a small home server with no SaaS platform tax. Converting PDFs to markdown before the model sees them significantly cuts token cost and latency.