Recruiting Agency Outreach Automation
Recruiting Agency Outreach Automation
A mid-size recruiting agency specializing in tech and finance placements had 4 recruiters spending 6+ hours daily on manual sourcing tasks: searching LinkedIn, copying profiles into Bullhorn, writing personalized outreach emails, and tracking follow-ups in spreadsheets.
Response rates on manual outreach were around 12%. Recruiters had no time to actually work the pipeline because they were buried in sourcing logistics.
We replaced the sourcing and outreach layer with an n8n pipeline that runs on a daily schedule.
The Workflow
Trigger: Daily Schedule (7:00 AM)
The workflow fires automatically each morning. It processes the agency's active job orders and the previous day's new candidate additions to the sourcing queue.
Stage 1: Source Open Roles
Node 1 - Bullhorn API: Fetch Active Job Orders
Pulls all active job orders from Bullhorn where status = "Accepting Candidates". Returns job title, required skills, experience range, location, and assigned recruiter.
Node 2 - Split In Batches Processes each job order in a separate branch. Prevents rate limit issues when running multiple roles simultaneously.
Stage 2: Candidate Sourcing Queue
Node 3 - Airtable: Fetch Sourcing Queue Recruiters add LinkedIn profile URLs to an Airtable "Sourcing Queue" throughout the day. The workflow reads all unprocessed rows (status = "Pending").
Node 4 - HTTP Request → LinkedIn Profile Scraper (Apify) Submits each LinkedIn URL to the Apify LinkedIn scraper. Returns: current title, company, location, years of experience, skills list, education, and connection degree.
Stage 3: Contact Enrichment
Node 5 - Hunter.io: Find Email Attempts to find the candidate's work email using name + company domain. Returns email with confidence score.
Node 6 - Clearbit: Person Enrichment Enriches the profile with additional data: past employers, tech stack signals (from job history descriptions), GitHub activity indicator.
Node 7 - IF Node: Email Found?
- Email found (confidence > 70%) → continues to scoring
- No email → routes to LinkedIn InMail branch (Node 7b)
Stage 4: Fit Scoring
Node 8 - Code Node: Fit Scoring Engine
function scoreCandidate(candidate, jobOrder) {
let score = 0;
// Skills overlap
const requiredSkills = jobOrder.skills.map(s => s.toLowerCase());
const candidateSkills = candidate.skills.map(s => s.toLowerCase());
const skillMatch = requiredSkills.filter(s => candidateSkills.includes(s));
score += (skillMatch.length / requiredSkills.length) * 40;
// Experience range
const exp = candidate.yearsExperience;
const [minExp, maxExp] = jobOrder.experienceRange;
if (exp >= minExp && exp <= maxExp) score += 25;
else if (exp >= minExp - 1) score += 12;
// Location
if (candidate.location === jobOrder.location) score += 20;
else if (candidate.location === jobOrder.remoteEligible) score += 15;
// Current company signal (competitor or target company list)
if (jobOrder.targetCompanies.includes(candidate.currentCompany)) score += 15;
return {
score: Math.round(score),
tier: score >= 70 ? 'A' : score >= 45 ? 'B' : 'C',
matchedSkills: skillMatch
};
}Node 9 - Switch: Route by Tier
- Tier A → priority outreach sequence (same day)
- Tier B → standard outreach sequence
- Tier C → archive in Bullhorn, no outreach
Stage 5: Bullhorn CRM Sync
Node 10 - Bullhorn: Create/Update Candidate Upserts the candidate record: profile data, enriched email, fit score, matched skills, sourcing date, assigned job order, and recruiter owner.
Node 11 - Bullhorn: Create Submission Links the candidate to the relevant job order as a submission with status "Sourced - Pending Outreach".
Stage 6: Personalized Outreach
Node 12 - OpenAI: Generate Outreach Email
Uses GPT-4o to generate a personalized first-touch email:
System: You are a recruiter writing a brief, direct outreach email.
Never use "I hope this finds you well". No fluff. 2–3 sentences max.
User: Write an outreach email for:
- Candidate: {name}, currently {title} at {company}
- Role: {jobTitle} at {clientName}
- Matched skills: {matchedSkills}
- Key selling point: {jobOrder.keySellingPoint}Node 13 - Gmail: Send Outreach (Day 1)
Sends the generated email from the assigned recruiter's Gmail account using OAuth. Subject line: {jobTitle} opportunity for {clientName}.
Node 14 - Airtable: Log Outreach Records: candidate, job order, email sent, timestamp, outreach tier. Used for response rate tracking.
Stage 7: Follow-up Sequence
Node 15 - n8n Schedule: Day 4 Follow-up If no reply detected (Gmail label check), sends a short follow-up:
"Just checking if you saw my note last week. Happy to share more details if timing is right."
Node 16 - n8n Schedule: Day 10 Final Touch Final touch focusing on the role's flexibility/compensation if the candidate is Tier A:
"Last note from me on this. The team has flexibility on [remote/comp]. Worth a 15-min call?"
Node 17 - Gmail: Reply Detection Monitors the recruiter's inbox for replies matching the candidate's email. On reply: updates Bullhorn status to "Responded", removes from follow-up sequence, sends Slack alert to recruiter.
Results
- 6 hrs/day of manual sourcing work replaced
- Outreach response rate: 12% → 28% (personalized AI-generated emails outperform templates)
- 94 candidates/week processed on average (was 30–35 manually)
- Bullhorn data quality improved: all records have email, fit score, and matched skills populated
Stack
| Layer | Tool |
|---|---|
| Automation | n8n (self-hosted) |
| ATS/CRM | Bullhorn |
| Sourcing Queue | Airtable |
| Profile Scraping | Apify |
| Email Finding | Hunter.io |
| Enrichment | Clearbit |
| Personalization | OpenAI GPT-4o |
| Gmail API (OAuth per recruiter) | |
| Alerts | Slack |
My Role
- Ran a workflow audit with the recruiting team and documented every manual step across 4 recruiters
- Built the fit scoring model using 6 months of historical placement data to weight the signals
- Set up individual Gmail OAuth connections per recruiter so outreach comes from their actual accounts
- Built the reply detection and sequence cancellation logic
- Integrated Apify scraper with rate-limit handling and fallback to manual queue on failures
- Delivered with full runbook documentation so the team can modify job order criteria without touching n8n