The Offer-Letter Agent: 14 Sent, 11 Signed, $4 in Tokens

A 12-person creative agency was losing two out of every three engineering finalists at the offer stage — not on culture, not on remote policy, but on $15K counter-offers from bigger competitors who simply knew the market better. Their comp spreadsheet was last touched in 2022. Every HR AI demo skips this step because salary recommendations scare enterprise vendors. Here's the agent I built for them in a week.
The step every HR AI demo refuses to touch
Sourcing, screening, scheduling. That's the trinity every HR AI vendor shows off. None of them touch salary because the legal exposure is ugly: if a platform with 100K seats recommends a number and that number shows up in a discrimination suit, the platform gets subpoenaed. Workday, monday, Greenhouse — they all stay in the safe zone.
Which is exactly why a solo builder can ship this in a week. When the agent runs on the client's infrastructure, uses the client's data, and outputs into the client's template, the legal exposure sits with the client (where it already sat when a founder pulled a number out of a 2022 spreadsheet). No vendor is in the chain.
The client's pre-agent numbers were brutal:
| Metric | Before | After |
|---|---|---|
| Offers sent (quarter) | 14 | 14 |
| Accepted first pass | 6 | 11 |
| Founder time per offer | ~90 min | ~90 sec |
| Time from "we want them" to PDF | 2–3 days | 22 seconds |
| Cost per offer | N/A | $0.29 |
Same recruiters. Same pipeline. Same candidates. The pipeline was never broken — the pricing step was.
The stack, and why it's deliberately boring
Nothing exotic here. Every piece was already sitting in the client's environment or costs pennies to run:
- n8n — orchestration, self-hosted on a $12/mo VPS
- Claude API (Sonnet) — the reasoning step
- Google Sheet — the client's own historical comp data, previously unopened
- 3 scraped sources — Levels.fyi, Glassdoor, and one paid industry salary report the client already subscribed to
- Google Doc — their existing offer letter template
- monday form — the trigger the recruiter already used
- Slack + Google Drive — delivery
No new SaaS. No platform tax. Nothing that required a legal or IT review because every tool was already approved. The whole point: the interesting part is the logic, not the toolchain. If you already run n8n and Claude for anything else, you're 80% there.
The trigger and the four steps
Recruiter fills a monday form after the final interview: candidate name, role, location, seniority level, notes from the loop. Submit fires an n8n webhook. Four steps run.
# n8n workflow shape
trigger: monday_form_webhook
steps:
1_scrape_market:
parallel:
- levels_fyi_query: {role, location, seniority}
- glassdoor_query: {role, location, seniority}
- industry_report: {role, location, seniority}
timeout: 15s
2_pull_internal:
source: google_sheets
sheet: "Past Offers"
filter: role_family == input.role_family
columns: [offered_base, counter_amount, accepted, components_won]
3_reason:
model: claude-sonnet
max_tokens: 4000
inputs: [market_data, internal_history, candidate_notes, template]
outputs: [recommended_base, stretch_base, component_strategy, letter_draft]
4_deliver:
- render_pdf: google_docs_template
- drive_upload: /Offers/{{candidate_name}}/
- slack_notify: #hiring channel
Step 1 hits the three comp sources with a targeted query. Senior full-stack in Amsterdam returns senior full-stack in Amsterdam — not a generic "backend" scrape you have to filter later. Nine seconds average, run in parallel.
Step 2 pulls the client's own historical offers. This is the piece nobody else has. Every past offer, what was accepted, what got countered, by how much, and which lever moved the deal. Under a second because it's just a sheet lookup.
Step 3 is where Claude earns its 29¢. Four outputs from one call: recommended base, stretch ceiling the founder pre-approves, which offer components this specific company historically wins on, and the fully drafted letter. Eleven seconds.
Step 4 renders the PDF from the Google Doc template with all merge fields filled, drops it in Drive, pings Slack with the numbers and reasoning.
Total wall time: 22 seconds from form submit to PDF in Drive.
The prompt that does the actual work
The stack is boring. The prompt is where the accept rate comes from. Simplified:
prompt = f"""
You are a compensation analyst for {company_name}, a {company_size}-person
{industry} firm hiring in {location}.
CANDIDATE:
- Role: {role}
- Seniority: {seniority}
- Location: {location}
- Interview signals: {interview_notes}
EXTERNAL MARKET (last 30 days):
{levels_data}
{glassdoor_data}
{industry_report_data}
INTERNAL HISTORY (this company's past offers, same role family):
{internal_comp_sheet}
Return JSON:
{{
"recommended_base": int,
"stretch_base": int,
"reasoning": "2-3 sentences on how you weighted market vs internal",
"component_strategy": {{
"base": int,
"signing_bonus": int,
"equity_units": int,
"remote_stipend": int,
"rationale": "which component this company historically wins on"
}},
"letter_draft": "full offer letter using the template below"
}}
TEMPLATE:
{google_doc_template_text}
RULES:
- Recommended base must sit between the 50th and 65th percentile of market.
- Stretch base cannot exceed the highest accepted offer in internal history
for this seniority + 8%.
- If internal history shows this company loses on base but wins on signing
bonus, front-load signing rather than pushing base up.
- Never recommend below the lowest accepted offer for this seniority.
"""
The "front-load signing bonus" rule came out of the internal data. This client had lost four candidates in a row where they matched base but the competitor threw in a $10K signing. They'd also won three candidates where they under-matched base by $3K but paid an $8K signing. The model surfaces that pattern every time now because it's staring at the win/loss log.
For Priya Sharma, senior backend, Amsterdam — the Slack ping came back: recommended €78K base, stretch to €84K, front-load €8K signing instead of pushing base higher, because that's the lever this client wins on. Founder reviewed for 90 seconds. Sent.
Why two numbers matter more than one
The single biggest lever on accept rate wasn't the market data. It was outputting a stretch number alongside the recommendation.
The old workflow: recruiter sends offer at €75K. Candidate counters at €85K. Recruiter Slacks the founder. Founder is in a client meeting. Three hours pass. Founder replies "let me think." Next morning founder asks two co-founders. Afternoon they agree on €82K. Recruiter emails candidate. Candidate accepted a different offer that morning.
The new workflow: recruiter already knows the stretch is €84K, pre-approved by the founder when the agent generated it. Counter comes in at €85K, recruiter replies within an hour with €84K plus the signing bonus context. Candidate signs.
That single change — pre-approving the ceiling at generation time — is worth more than any comp data. The three-day approval loop was killing more offers than the number itself.
The third thing that matters: draft the whole letter. Don't output a number in Slack and expect the recruiter to open Docs, merge fields, generate a PDF, upload it, and email the candidate. That's 45 minutes of friction that gets deprioritized when the recruiter is running four other loops. Delivering a signed PDF-ready draft into Drive collapses the "we want to make an offer" to "candidate has the PDF" gap from days to under an hour.
The numbers, honestly
Q3 batch, exactly as they came out of the log:
- 14 offers drafted
- 11 accepted first pass (78.6%)
- 2 accepted after using the stretch number
- 1 declined for reasons unrelated to comp (relocation fell through)
- $0.29 average Claude token cost per offer
- $4.06 total token spend for the quarter
- ~22 seconds average form-submit to PDF-in-Drive
- ~90 seconds average founder review time
Previous baseline was 6 accepts out of 14. Going from 6 to 11 with four open engineering roles means the client filled all four seats in Q3 instead of rolling two into Q4. The recruiter time saved is real but small. The founder time saved is significant. The revenue impact of filling billable engineering seats one quarter earlier is the number that actually matters to the client.
What I'd change if I built it again
- Cache the market scrapes for 7 days per role+location+seniority tuple. We're re-scraping identical queries when two offers go out for the same role in the same week.
- Log the model's reasoning to a separate sheet, not just Slack. Useful when the founder wants to review a pattern across offers three months later.
- Add a "declined" webhook so the sheet updates itself when a candidate turns down an offer, closing the feedback loop without a human copy-paste.
Where bizflowai.io fits
This is the shape of work I ship for small teams every week at bizflowai.io — a boring stack, the client's own data as the primary signal, a decision the founder used to make in three days now made in three minutes, and a per-run cost measured in cents. Recruiting is one domain; the same pattern works for pricing quotes, renewal offers, and any decision where the founder is the bottleneck and the data to make it well already exists somewhere nobody's opening. If you have a decision that's costing you deals because it takes days instead of minutes, that's the shape of thing worth automating first.
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 an AI offer pricing agent?
An AI offer pricing agent is an automated workflow that generates competitive salary offers for job candidates. It pulls external market comp data (Levels.fyi, Glassdoor, industry reports), combines it with a company's internal win-loss history, and uses an LLM like Claude to recommend a base salary, stretch number, and drafted offer letter — typically in under 30 seconds per candidate.
How do I build an automated offer letter workflow with n8n and Claude?
Trigger an n8n webhook from a form (e.g., Monday) capturing role, location, and seniority. Have n8n scrape targeted comp sources, pull internal historical offer data from a Google Sheet, then send everything to the Claude API with a prompt that calculates base, stretch, and component recommendations. Finally, render a PDF from a Google Doc template and post it to Slack and Drive.
Why do candidates reject offers even with a good recruiting pipeline?
Candidates often reject offers because the salary bands are outdated, not because of culture or remote policy. In one case study, a 12-person agency was pricing roles from a 2022 spreadsheet, with senior backend bands sitting $18K below what competitors paid juniors. Slow counter-offer approval cycles (three days on Slack) compounded the problem, causing candidates to ghost and dropping accept rates to 6 out of 14.
How much does it cost to run an AI-generated offer letter workflow?
Running offer generation through the Claude API costs roughly 29 cents per offer in tokens. In the documented case, drafting 14 offers across an entire quarter totaled $4.06 in AI spend. The stack uses existing tools — n8n for orchestration, one Google Sheet, scraped comp sources, and a Google Doc template — with no additional SaaS subscriptions or platform fees.
Why don't HR platforms like Workday or Monday recommend salary numbers directly?
Large HR platforms avoid recommending specific salary numbers due to legal exposure. If a platform serving hundreds of thousands of seats suggests compensation figures, it takes on liability for pay equity, discrimination claims, and market accuracy across every customer. A custom agent built for a single company shifts that risk to the business itself, which is why bespoke workflows can do what enterprise SaaS won't.