The Bot That Only Sends 'No': 247 Rejections, $7.41

Abstract tech illustration: The Bot That Only Sends 'No': 247 Rejections, $7.41

Every HR automation demo shows you the yes pipeline — sourcing, screening, scheduling. Nobody automates the 85% who get rejected. Here's the exact rejection bot I built for a client last month: 247 no-emails, $7.41 in API cost, eight minutes of human review, and three legal guardrails that keep it defensible.

The math nobody in HR wants to look at

A typical mid-size role in the US pulls around 300 applicants. The recruiter shortlists 40, interviews 8, hires 1. That leaves 250-290 humans who spent real time on your application and now sit waiting. In practice they get one of two things: ghosted (roughly 90% of the time, according to years of Jobvite and CareerPlug candidate-experience surveys), or a template that says "we've decided to move forward with other candidates" — which is worse than ghosting because it insults them politely.

Every one of those people talks. LinkedIn posts, Glassdoor reviews, quiet warnings to friends who might apply next quarter. Your employer brand — the exact thing you're paying LinkedIn Recruiter and Indeed sponsored slots to build — gets demolished at the rejection stage.

Here's the part that stings. Every major HR platform on the market — Greenhouse, Lever, Workable, Ashby, monday — will happily automate your sourcing, screening, and scheduling. Ask them to automate rejections with real, specific feedback and they hand you a text box and wish you luck. Because rejection language is a legal minefield:

  • One wrong sentence about a protected class → EEOC exposure.
  • One implied critique of skill that turns out to be wrong → defamation risk.
  • One false promise like "we'll keep your resume on file" → potential misrepresentation claim.

So the SaaS vendors leave it to you. And you, being human and busy, leave it to nobody. That's the gap this bot closes.

Piece 1 — The taxonomy (this is the whole product)

Before writing a single line of code, sit down with someone who actually understands employment law in your jurisdiction — for US clients that means an employment attorney familiar with EEOC guidance and any relevant state rules (California, New York, and Illinois in particular have stricter recordkeeping requirements). Then define a fixed set of rejection reasons.

I use 12. Examples:

  • LOC_ONSITE_REQ — Role requires on-site presence; candidate applied as remote-only.
  • EXP_BELOW_MIN — Years of relevant experience below the posted minimum.
  • STACK_MISMATCH — Portfolio/experience does not demonstrate the specific technology stack listed in the JD.
  • SENIORITY_MISMATCH — Candidate applied to a level significantly above or below their track record.
  • ROLE_FILLED — Position closed before review completed.
  • COMP_MISMATCH — Salary expectation outside the posted band.

Each reason gets a template email, written by a human, reviewed by a lawyer, sitting in a Google Sheet with two columns:

reason_code email_body
LOC_ONSITE_REQ Hi {{first_name}}, thanks for applying to the {{role_title}} role...
EXP_BELOW_MIN Hi {{first_name}}, thanks for your interest in {{role_title}}...

That sheet is the entire universe of things the bot is allowed to say. It cannot invent a new reason. It cannot free-write a sentence. It picks one of 12, fills in two or three variables, done.

That constraint is what makes the whole thing safe. Everything else in this post is plumbing around that constraint.

Piece 2 — The Gmail trigger the recruiter already uses

The recruiter has a Gmail label called rejected. When they move a candidate thread into that label, they add a one-line internal note at the top of the thread reply — never sent, saved as draft with a specific prefix #INTERNAL:. Something like:

  • #INTERNAL: remote only, we need on-site
  • #INTERNAL: portfolio all WordPress, need React
  • #INTERNAL: 2 yrs experience, we posted 5+

That's it. Ten seconds of work per candidate. Recruiters already write these notes for their own memory — we just standardized where they go.

An n8n workflow watches the label:

trigger: gmail_label_added
label: "rejected"
poll_interval: 5m
extract:
  - thread_id
  - candidate_email
  - candidate_first_name    # parsed from original application
  - role_title              # parsed from thread subject
  - internal_note           # regex: /#INTERNAL:\s*(.+)/

If the internal note is missing, the workflow does nothing and pings the recruiter on Telegram to add one. No note, no email. Ever.

Piece 3 — Claude as classifier, not writer

This is where most builders would let the LLM write the whole email. Don't. That's how you end up in court.

Claude gets three inputs: the candidate's original application text, the recruiter's one-line internal note, and the 12 reason codes with their descriptions. The only thing Claude is allowed to output is a reason code and a confidence score. No prose, no email body, no editorializing.

SYSTEM_PROMPT = """You are a rejection-reason classifier for a US-based
recruiter. You will receive:
  1. The candidate's original application
  2. The recruiter's one-line internal note explaining why they rejected
  3. A fixed list of 12 reason codes with descriptions

Your ONLY job: return JSON with two fields:
  { "reason_code": "<one of the 12 codes>", "confidence": 0.0-1.0 }

HARD RULES:
- If the recruiter's note references age, gender, race, national origin,
  religion, disability, pregnancy, family status, veteran status, or any
  other protected class under US federal or state law, return:
  { "reason_code": "ESCALATE_LEGAL", "confidence": 1.0 }
- If no reason code fits with confidence >= 0.85, return:
  { "reason_code": "ESCALATE_UNCLEAR", "confidence": <your score> }
- Never output prose. Never output an email. Never suggest a new code.
"""

The n8n workflow then does the actual work:

if reason_code starts with "ESCALATE_":
    → flag thread, ping recruiter on Telegram, stop
elif confidence >= 0.85:
    → look up template in Sheet by reason_code
    → substitute {{first_name}}, {{role_title}}
    → create Gmail draft with 24h send delay
    → log to audit table
else:
    → flag for human review

Last month about 8% of cases landed in the low-confidence or escalate bucket. That's where the eight minutes of monthly review time comes from — the recruiter reads the flagged thread, either types a manual reply or corrects the reason code, and moves on.

The 24-hour draft delay matters. It gives the recruiter a chance to catch anything weird before it sends, and it makes the response feel considered rather than instant-reflex.

Piece 4 — Three guardrails that make it defensible

Guardrails aren't a nice-to-have. They're what turns "AI writes rejection emails" from a lawsuit into a defensible business process.

The three non-negotiables

  • Guardrail 1 — Protected-class refusal. The prompt explicitly forbids reference to any protected class under Title VII, the ADA, ADEA, or state equivalents. If the recruiter's internal note mentions one — even accidentally — the classifier escalates instead of categorizing. See the EEOC's guidance on prohibited employment policies for the current list of protected characteristics.
  • Guardrail 2 — No skill critique. No template describes the candidate's work quality. Templates describe the role's requirements and note a gap. "The role requires 5+ years of React and your application lists 2" is safe. "Your React code was weak" is a lawsuit. Every template is phrased as a description of the job, not a judgment of the person.
  • Guardrail 3 — No false hope. Zero templates contain "we'll keep your resume on file for future opportunities" unless the client actually has a system to do that (almost nobody does). No "we were so impressed" when they weren't. No "please apply again" unless the recruiter genuinely means it. Politeness that is factually untrue is still a misrepresentation.

Everything logs. Every classification, every draft, every send goes to an audit table with the thread ID, the reason code, the confidence score, and the exact template version used. If a rejected candidate ever contacts an employment attorney, the client can produce the full decision chain in about 90 seconds.

The numbers from last month, one client

Running this for a single mid-market client hiring across three roles:

Metric Value
Rejection emails sent 247
Recruiter time (across low-confidence flags) 8 minutes
Claude API cost (Sonnet, classifier only) $7.41
Per-email cost ~$0.03
Draft-to-send auto-approval rate 92%
Escalated to human 8% (20 threads)
Candidate thank-you replies 14

Fourteen candidates replied to thank the recruiter for the specific feedback. On rejections. That number surprised me more than anything else in the build. Compare that to the client's previous six months, when a rejection reply was so rare the recruiter couldn't remember the last one.

The cost math is worth staring at. At $0.03 per email, sending 10,000 rejections a year runs about $300 in API costs. A single Glassdoor lawsuit or one viral "this company ghosted 300 applicants" LinkedIn post costs orders of magnitude more.

What I would not do

A few things I explicitly rejected while building this, in case you're tempted:

  • Don't let the LLM write the email body. Even with a great prompt, one bad generation in 500 is enough for a legal problem. Templates + variable substitution is boring and safe.
  • Don't skip the recruiter's one-line note. It's tempting to have the classifier read the full thread and infer the reason. It works 85% of the time, which means it's wrong 15% of the time, which is unacceptable for outbound legal communication.
  • Don't send immediately. The 24-hour draft delay has caught three recruiter-corrected mistakes in the last two months. Cheap insurance.
  • Don't offer feedback the candidate can argue with. "Your portfolio didn't show the required stack" invites a reply of "here's my other portfolio." "The role requires the specific stack listed in the JD" doesn't. Same information, different legal posture.
  • Don't run this without an employment attorney reviewing the taxonomy at least once a year. Federal and state law shifts. Your templates need to shift with it.

Where bizflowai.io fits

This is one of the automations we ship for clients running high-applicant-volume hiring at bizflowai.io — the taxonomy design, the n8n workflow, the classifier prompt, the audit logging, and the recruiter Telegram loop, wired into whatever ATS or Gmail setup the client already uses. The interesting part isn't the code, it's the two-hour session with the recruiter and their employment attorney to lock down the 12 reason codes. That's what makes the system defensible, and it's the part every SaaS vendor refuses to touch.

The uncomfortable takeaway

The rejection stage is where your employer brand is actually won or lost. Not the careers page. Not the Glassdoor "life at company" video. The email that 85% of your applicants receive — or don't. Automating it well, with real constraints and real feedback, costs about three cents per candidate and eight minutes of recruiter time per month. Automating it badly costs a lawsuit. Doing nothing costs your next 300 applicants who read the reviews before they apply.

Pick one.


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 candidate rejection gap in modern hiring?

The rejection gap is the space between the 250-290 applicants per role who don't get hired and the near-total lack of meaningful feedback they receive. About 90% get ghosted, and the rest get generic templates. HR SaaS platforms like Greenhouse, Lever, and Workable automate sourcing and screening but avoid automating rejections because specific feedback carries legal risk, leaving the task to overworked recruiters who skip it entirely.

How do I automate candidate rejection emails safely?

Build a four-piece system: a fixed taxonomy of rejection reasons (around twelve) with lawyer-reviewed email templates in a Google Sheet; a trigger where recruiters label rejected Gmail threads and add a one-line note; a Claude-based classifier that outputs only a reason code and confidence score; and guardrails baked into prompts. High-confidence matches auto-draft emails, while low-confidence cases route to human review.

Why does automating rejection feedback matter for employer brand?

Rejected candidates talk. They post on LinkedIn, tell their networks, and leave Glassdoor reviews. When 250+ applicants per role get ghosted or receive insulting generic templates, they quietly demolish the employer brand that recruiters spend money on job boards to build. Providing specific, respectful rejection feedback protects that brand investment at the exact stage where it's most vulnerable to damage.

Why restrict Claude to outputting only a reason code instead of writing the email?

Rejection language is a legal minefield. One wrong sentence about a protected class, an inaccurate skill critique, or a false promise like keeping a resume on file can trigger a lawsuit. Restricting Claude to picking one of twelve pre-written, lawyer-reviewed templates and outputting only a reason code plus confidence score removes the model's ability to invent language, making the system legally defensible.

When should a rejection email be flagged for human review instead of auto-sent?

Flag for human review when the classifier's confidence score falls below 85%. In this system, about 8% of cases land in that low-confidence bucket and get routed to the recruiter via Telegram. High-confidence classifications auto-draft an email in Gmail with a 24-hour send delay. This threshold keeps monthly manual review time to roughly eight minutes while catching ambiguous cases before they become liabilities.