The Silver Medalist Pool: 312 Re-Engaged, 47 Replies, $2.17

Your best next hire probably isn't on LinkedIn. They're already in your ATS, tagged "good candidate, revisit later" from a round-three loss eight months ago. If you're paying $850/month for a LinkedIn Recruiter seat while ignoring 400+ people you already interviewed and already liked, you're skipping the cheapest hire on the table.
Here's the exact n8n agent I built for a client that woke up 312 of those silver medalists in 38 minutes for $2.17 in tokens — and the one filter that saved me from a lawsuit-adjacent email.
Why the silver medalist pool is the cheapest hire you'll ever make
Every ATS holds a pile of candidates who cleared round two or three, who the hiring manager actually liked, but got beaten by someone marginally stronger. They get a polite rejection, a "revisit later" tag, and then sit there forever. My client had ~1,100 of them stacked up over two years. Zero had ever been contacted a second time.
Meanwhile the same company was burning:
| Line item | Monthly |
|---|---|
| LinkedIn Recruiter seat | $850 |
| Job board credits (Indeed + niche boards) | $1,200 |
| Sourcing contractor (10 hrs/wk) | $1,600 |
| Total spent looking outside | $3,650 |
You already paid to interview these people. You have their resume, salary expectation, hiring manager notes, sometimes a scorecard. Reactivation converts roughly 4× faster than cold outbound because they already know the company, they already went through the process, and they already wanted the job.
Nobody does it because manually it's a week of soul-crushing work per 300 candidates: open each profile, re-read notes, cross-check open roles, write something personal, track replies. That's the exact shape of problem an agent handles well.
The stack: n8n, Claude, ATS export, Gmail
Nothing exotic. The whole pipeline is five nodes and two LLM calls.
[ATS CSV export]
→ [Filter node: date window + status]
→ [Claude #1: classifier — should we contact?]
→ [Claude #2: drafter — 90-word email]
→ [Gmail send, 20s spacing]
→ [Airtable log: sent, replied, booked]
Step one is the data pull. Greenhouse, Lever, Ashby, Workable, or Airtable if that's your setup — all let you export candidates by status. We pulled everyone tagged silver_medalist, strong_no_hire, or good_candidate_revisit going back 18 months. That gave us 406 rows. Each row: name, email, role applied to, rejection date, interview notes.
Total build time was about 3 hours for a working v1, plus another 2 hours tuning the classifier prompt after the first dry run.
The filter node that saved me from getting yelled at
This is the one I want you to steal even if you steal nothing else. Before the LLM sees anything, filter mercilessly:
// n8n Function node — pre-classifier filter
const now = new Date();
const SIX_MONTHS = 1000 * 60 * 60 * 24 * 180;
const EIGHTEEN_MONTHS = 1000 * 60 * 60 * 24 * 540;
return items.filter(item => {
const c = item.json;
const lastContact = new Date(c.rejection_date).getTime();
const gap = now - lastContact;
// hard exclusions — never contact
if (c.do_not_contact === true) return false;
if (c.notes?.toLowerCase().includes('withdrew')) return false;
if (c.notes?.toLowerCase().includes('do not reach out')) return false;
if (c.hired_elsewhere_confirmed === true) return false;
// time window: not too fresh, not too stale
return gap > SIX_MONTHS && gap < EIGHTEEN_MONTHS;
});
That took 406 rows down to 312. The six-month floor is the important part. Anything fresher and you're re-poking someone whose rejection still stings. Anything past 18 months and the interview notes are too stale to reference credibly.
I also learned to check a do_not_contact column the hard way. In my first test batch, three replies came back annoyed. One of them was a candidate who had explicitly asked to be removed from all outreach a year prior — that flag existed in the ATS, I just wasn't reading it. Adding that check is a two-minute fix and it's the difference between a good campaign and a complaint to your careers@ inbox.
The classifier prompt: decide first, write later
Most people who build outreach agents make one Claude call that does everything — decide + draft + personalize. That's a mistake. You want the decision separate from the writing, because a bad decision produces a beautifully written email to someone you shouldn't have contacted.
The classifier gets three inputs and answers one question:
System: You are a recruiting coordinator. You will be given:
1. A past candidate's original role, rejection reason, and interview notes.
2. A list of currently open roles with 1-line descriptions.
Return JSON only. Answer this single question:
Is there a currently open role where this candidate would be a
STRONGER fit than a typical cold applicant? Only say yes if you can
point to a specific line from the interview notes that supports it.
Output schema:
{
"should_contact": boolean,
"matched_role_id": string | null,
"reason_hook": string | null, // one sentence, must quote or
// paraphrase a specific note detail
"skip_reason": string | null // required if should_contact=false
}
Rules:
- If notes mention relocation blocker, salary mismatch >20%,
culture concern, or performance concern that hasn't changed:
should_contact=false.
- If no open role clearly matches: should_contact=false.
- Do not infer skills not stated in the notes.
Out of 312 candidates, Claude returned should_contact: true for 241. The 71 skips broke down roughly:
- 34 — no current role matched their background
- 22 — salary expectation gap flagged in notes
- 9 — relocation blocker still applied (candidate was remote-only, role was on-site)
- 6 — hiring manager had flagged a soft concern I told the model to respect
Structured output is doing real work here. I use response_format with a JSON schema so the drafter node downstream can trust the shape without defensive parsing.
The drafter: 90 words, quote-only personalization
Second Claude call takes the classifier output plus the candidate's name and matched role, and writes the actual email. Strict system prompt:
Write a re-engagement email. Rules, all mandatory:
- Under 90 words.
- No template phrases: no "we were impressed", no "your background
stood out", no "hope this finds you well".
- Reference ONE specific detail from the interview notes. You may
only reference details you can quote verbatim from the notes.
Do not infer, extrapolate, or guess.
- Name the specific open role and one concrete reason it differs
from what they applied to before.
- End with a single low-friction ask: 15-minute call this week or
next. Offer two specific time windows.
- Sign as {{recruiter_first_name}}. No title, no company boilerplate.
The "quote verbatim" line matters. My first ten test drafts went to the hiring manager for review. Nine were fine. One referenced a "React Native side project" that the candidate had never actually built — the notes said "interested in mobile, has explored React Native tutorials." Claude compressed that into a project claim. Adding the verbatim rule fixed it on the next batch and it never recurred across 241 sends.
Sample output (redacted):
Hi Marcus — you interviewed for the Senior Backend role in March and it came down to you and one other person. We just opened a Platform Engineer seat that's closer to the distributed systems work you mentioned doing at your last startup, and reports into Sarah rather than the previous team. Worth a 15-min call Tue 2-3pm ET or Thu 10-11am ET to see if the shape fits? — Priya
Human, specific, no fluff. 68 words.
Send mechanics and what actually came back
Gmail node, sending from the recruiter's real inbox with reply-to routed to her, spaced at 20-second intervals. That pacing matters — Google will flag 200+ sends in a burst as spam behavior even from a warm inbox. At 20s per send, 241 emails takes about 80 minutes of send time (the LLM work ran in parallel, so total wall clock was 38 minutes).
Token cost breakdown:
| Stage | Calls | Model | Cost |
|---|---|---|---|
| Classifier | 312 | Claude Sonnet | $1.34 |
| Drafter | 241 | Claude Sonnet | $0.83 |
| Total | 553 | $2.17 |
Results at day 7:
- 241 emails sent
- 47 replies (19.5% reply rate — roughly 10× typical cold outbound)
- 12 phone screens booked
- 6 moved to onsite
- 3 offers extended
- 2 accepted
Cost per hire on that batch, including my build time at contractor rates, was under $100. Compare that to the fully-loaded cost of a LinkedIn-sourced hire — recruiter time, seat cost, InMail credits — which lands somewhere in the $4,000-8,000 range for a mid-level engineering role.
Failure modes worth knowing
- 8 candidates had already been hired elsewhere. Three of those replied annoyed. One had been hired by a direct competitor 4 months prior — awkward but not damaging.
- 2 replies were opt-out requests. Both got added to the
do_not_contactlist within the hour via a simple Airtable webhook triggered from a Gmail label. - 1 hiring manager mismatch. Classifier matched a candidate to a role the hiring manager didn't actually want them for. Fixed by having the recruiter approve matches in bulk (a 5-minute batch review) before the drafter runs.
The bulk-approval step is the only piece I'd add to any v2. It costs 5 minutes of human time and eliminates the "wait, why did we email that person" conversation entirely.
Why bizflowai.io helps with this
Most of the client work I do at bizflowai.io looks exactly like this — an existing dataset in a SaaS tool that nobody's mining, connected to Claude for one narrow decision, then routed through the client's own inbox or CRM. Silver medalist reactivation is one variant; the same architecture works for dormant lead re-engagement, expired-trial follow-ups, and past-customer winback. The n8n workflow above ships in about a week including the safety filters and the approval loop, and it runs for pennies per pass because the LLM work is bounded and structured.
Templates worth copying
If you want to run this yourself before hiring anyone:
- Export criteria: status IN (
silver_medalist,strong_hire_not_selected,good_candidate_revisit), rejected 6-18 months ago,do_not_contact = false - Two LLM calls, not one: classify first (JSON out), draft second (prose out)
- Verbatim rule: the drafter can only reference details it can quote from notes
- Human gate: batch-approve classifier matches before drafts generate
- Pacing: 20s between sends, from a real warm inbox, with reply-to to a real person
The whole thing is boring engineering. That's why it works.
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 a silver medalist candidate in recruiting?
A silver medalist is a candidate who advanced to round two or three of interviews, was liked by the hiring manager, but lost the offer to a slightly stronger applicant. They typically receive a polite rejection, get tagged as 'good candidate, revisit later' in the ATS, and are never contacted again, despite being pre-vetted assets sitting in the system.
Why does reactivating past candidates matter for hiring costs?
Reactivating silver medalists costs a fraction of sourcing strangers because the interviews, resumes, salary expectations, and hiring manager notes already exist. They also convert roughly four times faster since they know the company, completed the process, and previously wanted the job. Compare that to spending $850/month on LinkedIn Recruiter plus $1,200/month on job board credits to find new candidates.
How do I build an AI agent to re-engage silver medalist candidates?
Use n8n, Claude via API, an ATS export, and Gmail. Pull candidates tagged as silver medalist or revisit-later, filter by last-contact date (6-18 months ago), then run a Claude classifier that matches each candidate to current open roles and outputs structured JSON. A second Claude call drafts a short personalized email referencing a specific interview detail with a low-friction ask.
What should the classifier prompt do in a candidate re-engagement workflow?
The classifier prompt should make a decision, not write an email. Feed Claude the candidate's original role, interview notes, current open roles with descriptions, and rejection reason. Ask one question: is there a current role where this candidate would beat a cold applicant, and if so, which role and what interview detail should the outreach reference. Output structured JSON with a boolean, role ID, and hook.
When should a re-engagement email be skipped for a past candidate?
Skip candidates when no current open role fits their background, or when prior interview notes flagged a blocker that hasn't changed, such as relocation constraints or salary mismatch. In one workflow, Claude approved 241 of 312 filtered candidates and skipped 71 for these reasons. Also filter out anyone contacted within the last six months to avoid appearing spammy.