
Implementation Guide: Source passive candidates on linkedin and send personalized outreach sequences
Step-by-step implementation guide for deploying AI to source passive candidates on linkedin and send personalized outreach sequences for HR & Staffing clients.
Hardware Procurement
Cloud Virtual Machine for n8n Orchestration
$35–$55/month MSP cost / $75–$100/month suggested resale (bundled into managed service)
Hosts the self-hosted n8n workflow orchestration engine that coordinates the autonomous agent pipeline: triggering sourcing searches, calling OpenAI for message generation, pushing candidates to ATS, and managing outreach sequences. Self-hosting provides full data control and unlimited workflow executions.
Recruiter Workstations
$900–$1,200 per unit MSP cost / $1,100–$1,500 suggested resale (only if client needs new hardware)
Standard business laptops for recruiters to oversee AI campaigns, review candidate shortlists, approve outreach sequences, and manage pipeline in the ATS. Any modern business laptop with 16 GB RAM, Intel Core i5/i7, and Chrome browser is sufficient.
Dedicated Residential Proxy IP Block
$12–$15 per IP/month ($60–$75/month for 5 IPs) MSP cost / bundled into managed service
Provides dedicated static residential IP addresses that match the client's geographic location for LinkedIn automation safety. Each recruiter's Expandi instance routes through a unique residential IP to minimize detection risk and avoid LinkedIn account restrictions.
Software Procurement
hireEZ Professional
$199/user/month (billed annually at $2,388/user/year); 5-seat deployment = $995/month / MSP resale at $249/user/month = $1,245/month
Primary AI sourcing engine with 800M+ candidate profiles, Boolean and AI-powered search, ATS Rediscovery (resurfaces past applicants from 45+ integrated ATS systems), built-in candidate engagement analytics, and native integrations with Bullhorn, Greenhouse, Lever, and other major ATS platforms. Generates ranked candidate shortlists from natural-language job descriptions.
Expandi
$99/seat/month (or $79/seat/month billed annually); 5-seat deployment = $395–$495/month / MSP resale at $125/seat/month = $625/month
Cloud-based LinkedIn automation platform for executing personalized outreach sequences. Handles automated connection requests, follow-up messages, InMail sequences, and profile visits with built-in safety limits, dedicated country-based IP addresses, smart inbox for response management, and webhook triggers for n8n integration.
OpenAI API (GPT-5.4 mini)
~$0.15 per 1M input tokens / $0.60 per 1M output tokens; estimated $30–$80/month for 5,000–15,000 personalized messages / MSP resale bundled into service fee
Powers the AI personalization engine that generates unique, context-aware outreach messages for each candidate based on their LinkedIn profile data, work history, skills, and the specific job opportunity. GPT-5.4 mini provides the optimal cost-to-quality ratio for high-volume message generation.
n8n Self-Hosted (Community Edition)
Free (self-hosted); infrastructure cost covered by AWS VM above / MSP resale bundled into managed service
Visual workflow automation platform that serves as the central orchestration layer for the autonomous agent. Connects hireEZ sourcing output → OpenAI message generation → Expandi campaign creation → ATS candidate record creation → response handling. Provides a visual canvas for building, monitoring, and debugging multi-step agent workflows.
Clay
Pro plan at $349/month (includes enrichment credits at ~$1.20/lead for full enrichment) / MSP resale at $425/month
Waterfall data enrichment platform that finds verified personal email addresses, phone numbers, and company data for sourced candidates. Aggregates data from 75+ providers to maximize hit rates. Feeds enriched contact data into outreach sequences so candidates can be reached via both LinkedIn and email channels.
LinkedIn Recruiter Lite
$170/month per seat ($1,680/year); for 2–5 seats: $270/month per seat ($2,670/year); 5-seat deployment = $1,350/month / MSP resale at $310/seat/month = $1,550/month
Provides advanced candidate search with access to 1st, 2nd, and 3rd degree connections, 30 InMails per month per seat, saved searches, and project folders. Required as the foundational LinkedIn access layer that feeds candidate profiles into the sourcing pipeline. Also provides legitimate platform access that reduces compliance risk versus scraping.
Google Workspace (dedicated sending domain)
$7.20/user/month; 5 mailboxes = $36/month / MSP resale at standard managed email rates
Dedicated email accounts on a sourcing-specific subdomain (e.g., talent.clientfirm.com) for outbound recruiting sequences. Isolated from the client's primary domain to protect main domain reputation. Google Workspace provides reliable deliverability and OAuth integration with outreach tools.
Bullhorn ATS/CRM (or existing ATS)
Client's existing subscription; no additional MSP procurement typically needed. If new: ~$99–$199/user/month
The client's system of record for all candidate data, job orders, submissions, and placements. All sourced candidates, enriched data, and outreach activity sync bidirectionally with Bullhorn via REST API or native hireEZ/Expandi integrations. If client uses Greenhouse, Lever, or JazzHR instead, the integration approach adapts accordingly.
Prerequisites
- Client must have an active ATS subscription (Bullhorn, Greenhouse, Lever, or equivalent) with API access enabled and admin credentials available for integration configuration
- Each recruiter must have a LinkedIn account that is at least 6 months old with 500+ connections — new or thin profiles will be flagged by LinkedIn's automation detection systems
- Client must own a primary domain with DNS management access (e.g., GoDaddy, Cloudflare, Route 53) to configure a dedicated outreach subdomain and email authentication records
- Client must provide detailed job descriptions or intake forms for at least 3–5 active roles to use as initial test campaigns during implementation
- Client must designate a compliance stakeholder (internal counsel or HR leadership) to approve candidate notice language, data retention policies, and bias audit requirements based on jurisdictions where they recruit
- Stable business internet connection (25+ Mbps) at all recruiter locations; VPN configurations must allow direct HTTPS access to cloud platforms without aggressive packet inspection
- Client must provide a signed data processing agreement (DPA) template or approve the MSP-provided DPA for handling candidate PII across all integrated platforms
- AWS account with billing configured (MSP-managed or client-owned) for hosting the n8n orchestration server
- Client credit card or procurement approval for all SaaS subscriptions listed in software procurement — MSP will procure and manage on client's behalf where possible
- Chrome browser (latest stable version) installed on all recruiter workstations; no conflicting LinkedIn browser extensions (e.g., older versions of LinkedIn Helper or Dux-Soup) installed
Installation Steps
Step 1: Provision Dedicated Outreach Email Infrastructure
Set up a dedicated subdomain and email accounts specifically for recruiting outreach. This isolates outreach reputation from the client's primary business email domain, protecting deliverability for regular business communications. We create a subdomain like talent.clientfirm.com, provision Google Workspace mailboxes for each recruiter, and configure all DNS authentication records.
dig TXT talent.clientfirm.com
dig TXT _dmarc.talent.clientfirm.com
dig MX talent.clientfirm.comDNS propagation can take 24–48 hours. Start this step first as the email warm-up period (Step 7) cannot begin until DNS is fully propagated and verified by Google. Use MXToolbox.com to validate all records. Do NOT use the client's primary domain for automated outreach — domain reputation damage is extremely difficult to reverse.
Step 2: Deploy n8n Orchestration Server on AWS
Provision and configure the self-hosted n8n instance that will serve as the central nervous system of the autonomous agent. This server orchestrates all workflows: triggering sourcing, calling the OpenAI API, managing candidate data flow between platforms, and handling webhook events from outreach tools.
aws ec2 run-instances \
--image-id ami-0c7217cdde317cfec \
--instance-type t3.medium \
--key-name msp-client-keypair \
--security-group-ids sg-xxxxxxxxx \
--subnet-id subnet-xxxxxxxxx \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":50,"VolumeType":"gp3"}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=n8n-recruiting-agent},{Key=Client,Value=clientfirm}]'ssh -i msp-client-keypair.pem ubuntu@<EC2-PUBLIC-IP>sudo apt update && sudo apt upgrade -y
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable docker
sudo usermod -aG docker ubuntu
newgrp dockermkdir -p ~/n8n-recruiting && cd ~/n8n-recruiting
mkdir -p .n8nversion: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- '5678:5678'
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=mspadmin
- N8N_BASIC_AUTH_PASSWORD=<GENERATE-STRONG-PASSWORD>
- N8N_HOST=n8n.clientfirm.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.clientfirm.com/
- N8N_ENCRYPTION_KEY=<GENERATE-32-CHAR-KEY>
- GENERIC_TIMEZONE=America/New_York
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=<GENERATE-DB-PASSWORD>
volumes:
- ./.n8n:/home/node/.n8n
depends_on:
- postgres
postgres:
image: postgres:15-alpine
restart: always
environment:
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=<GENERATE-DB-PASSWORD>
- POSTGRES_DB=n8n
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:docker compose up -ddocker compose logs -f n8nReplace all placeholder passwords with strong generated values (use openssl rand -hex 16). Set up an Elastic IP and associate it with the EC2 instance for a stable public IP. Configure a DNS A record for n8n.clientfirm.com pointing to the Elastic IP. For production, place an Nginx reverse proxy or AWS ALB in front with Let's Encrypt SSL. Security group must allow inbound 443 (HTTPS) and restrict SSH (22) to MSP office IPs only. Set up AWS CloudWatch alarms for CPU and disk usage.
Step 3: Configure SSL and Reverse Proxy for n8n
Set up Nginx as a reverse proxy with automatic Let's Encrypt SSL certificates to secure the n8n web interface and webhook endpoints. This is required because Expandi webhooks and API callbacks require HTTPS endpoints.
sudo apt install -y nginx certbot python3-certbot-nginxsudo cat > /etc/nginx/sites-available/n8n << 'EOF'
server {
listen 80;
server_name n8n.clientfirm.com;
location / {
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
chunked_transfer_encoding off;
proxy_buffering off;
}
}
EOFsudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d n8n.clientfirm.com --non-interactive --agree-tos -m msp-admin@mspfirm.comsudo systemctl enable certbot.timercurl -I https://n8n.clientfirm.comEnsure the DNS A record for n8n.clientfirm.com is propagated before running certbot. The SSL certificate auto-renews every 90 days via the certbot timer. If using AWS ALB instead of Nginx, use ACM certificates and skip this step.
Step 4: Provision and Configure hireEZ Professional Accounts
Set up the hireEZ AI sourcing platform with recruiter seats, configure the ATS integration (Bullhorn or client's ATS), and enable ATS Rediscovery to immediately surface existing candidates from the client's database who match new roles. This provides instant ROI from day one.
hireEZ's ATS Rediscovery feature will scan the client's entire existing candidate database and re-rank candidates against current open roles. This often surfaces 50–200 immediately relevant candidates on day one — use this as an early win to demonstrate ROI. Request a dedicated Customer Success Manager from hireEZ during onboarding. The initial ATS sync for large databases (50K+ records) may take up to 24 hours.
Step 5: Set Up Expandi LinkedIn Automation Accounts
Configure Expandi cloud-based LinkedIn automation for each recruiter. This includes connecting LinkedIn accounts, assigning dedicated residential IPs, setting conservative daily limits for the warm-up period, and configuring webhook integration with n8n for autonomous agent orchestration.
Webhook URL: https://n8n.clientfirm.com/webhook/expandi-events
Enabled events: connection_accepted, message_replied, message_sentTHE MOST CRITICAL STEP: LinkedIn warm-up takes 2-3 weeks. Do NOT skip this or accelerate the ramp-up schedule. LinkedIn accounts that suddenly start sending high volumes of connection requests will be flagged and potentially restricted. Start each account at the minimum limits listed above and increase gradually. If any account receives a LinkedIn warning, immediately reduce limits by 50% and pause for 48 hours. Each recruiter should also manually engage (post, like, comment) on LinkedIn during the warm-up period to establish organic activity patterns alongside the automated activity.
Step 6: Configure Clay Data Enrichment Workspace
Set up Clay to enrich sourced candidate profiles with verified personal email addresses and phone numbers. This enables multi-channel outreach (LinkedIn + email) which dramatically increases response rates. Clay's waterfall enrichment queries 75+ data providers to maximize the hit rate for contact information.
Clay Pro plan includes sufficient credits for ~290 full enrichments per month at $349/month. If the client needs higher volume, upgrade to the Team plan. Monitor credit consumption weekly during the first month to calibrate. Waterfall enrichment means Clay tries the cheapest provider first and cascades through more expensive ones only if no result is found, optimizing cost per successful enrichment.
Step 7: Warm Up Outreach Email Accounts
Begin the critical 2–4 week email warm-up process for the dedicated sending mailboxes. This gradually builds sender reputation with email providers so that outreach messages land in primary inboxes rather than spam folders. Use an automated warm-up service to simulate natural email conversations.
- Option A: Use Expandi's built-in email warm-up — In Expandi > Settings > Email > Warm-up
- Connect each recruiter's talent.clientfirm.com Google Workspace account via OAuth
- Enable warm-up with these settings: Daily warm-up emails: Start at 5, increase to 40 over 2 weeks | Reply rate: 30–40% | Warm-up duration: 14–21 days minimum
Email warm-up MUST run for a minimum of 14 days before sending any real outreach. During warm-up, do not send any manual cold emails from these accounts. After warm-up is complete, limit real outreach volume to 50 emails per mailbox per day maximum to maintain deliverability. If bounce rate exceeds 3% or spam complaint rate exceeds 0.1%, immediately pause campaigns and investigate list quality.
Step 8: Configure OpenAI API Access and Test Message Generation
Set up the OpenAI API account, configure API keys, set usage limits, and test the message personalization prompts that will power the autonomous outreach agent. The system uses GPT-5.4 mini for cost-effective high-volume message generation with high quality.
ssh -i msp-client-keypair.pem ubuntu@<EC2-PUBLIC-IP># expected output is a personalized, natural-sounding connection request
# message. Verify the response includes candidate-specific details.
curl https://api.openai.com/v1/chat/completions \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-d '{
"model": "gpt-5.4-mini",
"messages": [
{
"role": "system",
"content": "You are a recruiting outreach specialist. Generate a personalized LinkedIn connection request message (under 300 characters) for a passive candidate."
},
{
"role": "user",
"content": "Candidate: Jane Smith, Senior Software Engineer at Google for 3 years. Previously at Meta. Stanford CS grad. Role: Staff Engineer at Series B fintech startup in NYC. Must mention: equity package and engineering culture."
}
],
"temperature": 0.8,
"max_tokens": 200
}'GPT-5.4 mini is recommended over GPT-5.4 for this use case because outreach messages are short (50–300 words) and the cost difference is ~15x while quality is comparable for this task. At ~$0.15/1M input tokens and ~$0.60/1M output tokens, generating 10,000 personalized messages costs approximately $5–10. Set temperature to 0.7–0.9 for natural variation across messages. Always include a system prompt that enforces compliance (no false claims, no discriminatory language, includes opt-out mention in email sequences).
Step 9: Build Core n8n Autonomous Agent Workflows
Import and configure the four core n8n workflows that comprise the autonomous recruiting agent: (1) Candidate Sourcing Trigger, (2) Profile Enrichment Pipeline, (3) Message Personalization Engine, and (4) Outreach Campaign Orchestrator. These workflows connect hireEZ, Clay, OpenAI, Expandi, and the ATS into a seamless autonomous pipeline.
The workflows are designed with human-in-the-loop gates: the sourcing workflow outputs a candidate shortlist that a recruiter must approve in Bullhorn/ATS before the outreach orchestrator picks them up. This satisfies compliance requirements and ensures quality. All workflows should be tested individually before connecting them. Use n8n's execution log to debug any issues. Set up n8n error notifications via Slack or email webhook.
Step 10: Configure ATS Integration and Candidate Sync
Establish bidirectional data flow between the AI sourcing stack and the client's ATS (Bullhorn, Greenhouse, or Lever). Sourced candidates are automatically created as records in the ATS with source attribution, and recruiter actions in the ATS (e.g., marking a candidate as 'Approved for Outreach') trigger the outreach pipeline.
curl -X POST 'https://auth.bullhornstaffing.com/oauth/authorize' \
-d 'client_id=<CLIENT_ID>&response_type=code&username=<USERNAME>&password=<PASSWORD>&action=Login'curl -X POST 'https://auth.bullhornstaffing.com/oauth/token' \
-d 'grant_type=authorization_code&code=<AUTH_CODE>&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>'curl -X GET 'https://rest.bullhornstaffing.com/rest-services/login?version=2.0&access_token=<ACCESS_TOKEN>'Bullhorn's REST API requires periodic token refresh (tokens expire after ~10 minutes of inactivity). The n8n workflow includes automatic token refresh logic. Custom fields in Bullhorn are critical for tracking which candidates came from AI sourcing and their outreach status. Work with the client's Bullhorn admin (or Bullhorn support) to add custom fields if needed. The hireEZ native integration handles most of the heavy lifting for candidate export; the n8n workflows handle the outreach status updates and response tracking.
Step 11: Build and Deploy Outreach Campaign Templates
Create the multi-step outreach sequence templates in Expandi that the autonomous agent will use. Each sequence includes a connection request, 2–3 follow-up messages, and an optional email fallback. Messages are personalized by the OpenAI engine but follow a proven structural framework.
- In Expandi, create campaign templates:
- Template 1: 'AI Sourced - Technical Roles'
- Step 1 (Day 0): Connection Request with note (300 char limit) — Personalized by OpenAI, placeholder: {{ai_connection_message}}
- Step 2 (Day 2): If connected, send Message 1 — Placeholder: {{ai_followup_1}} - detailed role pitch
- Step 3 (Day 5): If no reply, send Message 2 — Placeholder: {{ai_followup_2}} - social proof / team info
- Step 4 (Day 9): If no reply, send Message 3 (final) — Placeholder: {{ai_followup_3}} - soft close / alternative ask
- Template 2: 'AI Sourced - Executive/Leadership Roles'
- Step 1 (Day 0): Profile View
- Step 2 (Day 1): Connection Request (more formal tone)
- Step 3 (Day 3): Message 1 - executive-focused pitch
- Step 4 (Day 7): Message 2 - market insights angle
- Step 5 (Day 12): Message 3 - referral ask
- Template 3: 'Email Fallback' (for candidates not accepting LinkedIn) — Triggered via n8n after 14 days with no LinkedIn connection
- Step 1: Email via Google Workspace - role introduction
- Step 2 (Day 3): Follow-up email - case study/social proof
- Step 3 (Day 7): Final email - soft close
- Configure Expandi campaign settings:
- Send connection requests only during business hours
- Random delay between actions: 45–180 seconds
- Auto-withdraw pending connection requests after 21 days
- Tag responses: interested, not_interested, referral, out_of_office
LinkedIn connection request notes are limited to 300 characters — the OpenAI prompt must enforce this limit strictly. Follow-up messages can be longer (up to ~8,000 characters on LinkedIn) but keep them under 200 words for optimal response rates. The 3-touch sequence with 3–5 day gaps between messages is the industry standard — more aggressive cadences increase unsubscribe/report rates. Always include an opt-out line in the final message: 'If this isn't relevant, just let me know and I won't reach out again.'
Step 12: Implement Compliance and Human-in-the-Loop Controls
Configure the mandatory compliance layer including candidate notification of AI use, human review gates, data retention policies, opt-out handling, and bias audit infrastructure. This is non-negotiable for legal compliance with NYC LL 144, Illinois HB 3773, Colorado SB24-205, GDPR, and federal anti-discrimination law.
For clients recruiting in NYC: an annual bias audit by an independent auditor is REQUIRED under Local Law 144 if the AI tools are used as an Automated Employment Decision Tool (AEDT). Budget $5,000–$15,000 for a third-party bias audit. Recommended auditors: BLDS (Bayard), Holistic AI, or DCI Consulting. The audit examines selection rates across demographic groups. Even if the client doesn't recruit in NYC today, implementing audit-ready practices now is strongly recommended given the rapid expansion of state-level AI hiring laws. Document everything — audit trails are your best defense.
Step 13: End-to-End Integration Testing and Go-Live
Conduct thorough end-to-end testing of the complete autonomous agent pipeline with real candidate profiles and controlled test campaigns. Verify every integration point, data flow accuracy, message quality, compliance controls, and ATS synchronization before enabling production campaigns.
Testing Protocol: 6-Category End-to-End Test Suite
Run testing over 5–7 business days to verify time-delayed sequence steps fire correctly. Have a recruiter review every AI-generated message during testing — tune the OpenAI prompt based on feedback (too formal? too casual? missing key selling points?). Do not go live until all 6 test categories pass. Keep a testing log with screenshots for the compliance record.
Custom AI Components
Candidate Sourcing Trigger Workflow
Type: workflow
n8n workflow that triggers daily at a configured time, checks for new approved job requisitions in the ATS that need sourcing, calls the hireEZ API to initiate AI-powered candidate searches based on the job description and requirements, and processes the resulting candidate shortlists into the enrichment pipeline. Includes deduplication logic to avoid re-sourcing existing candidates.
Implementation
Node 2 — ATS Fetch URL
{{bullhorn_rest_url}}/search/JobOrder?query=status:Open AND customText1:needs_sourcing&fields=id,title,description,skills,categories&count=20Node 5 — Build hireEZ Search Query (JavaScript)
// builds hireEZ API search payload from Bullhorn job record
const job = $input.first().json;
const searchPayload = {
query: job.title,
skills: job.skills ? job.skills.split(',').map(s => s.trim()) : [],
location: job.address ? job.address.city + ', ' + job.address.state : '',
experience_min: 3,
experience_max: 15,
limit: 50,
exclude_current_company: job.clientCorporation ? job.clientCorporation.name : '',
source: 'linkedin'
};
return [{ json: { ...searchPayload, job_id: job.id, job_title: job.title } }];Node 7 — Deduplicate Against Existing ATS Records (JavaScript)
// checks each hireEZ candidate against Bullhorn ATS by LinkedIn URL to
// prevent duplicate records
const candidates = $input.first().json.results || [];
const deduped = [];
for (const candidate of candidates) {
// Check if LinkedIn URL already exists in ATS
const checkUrl = `${$credentials.bullhorn_rest_url}/search/Candidate?query=customText5:"${encodeURIComponent(candidate.linkedin_url)}"&fields=id&count=1`;
try {
const response = await this.helpers.httpRequest({
method: 'GET',
url: checkUrl,
headers: { 'BhRestToken': $credentials.bullhorn_token }
});
if (response.count === 0) {
deduped.push(candidate);
}
} catch (e) {
deduped.push(candidate); // Include if check fails
}
}
return deduped.map(c => ({ json: c }));Error Handling: Each API call node has retry logic (3 attempts, exponential backoff). Failed jobs are logged and a Slack notification is sent to the MSP monitoring channel.
Profile Enrichment Pipeline Workflow
Type: workflow
n8n workflow triggered by webhook from the Sourcing Trigger. Takes candidate LinkedIn URLs and basic profile data, sends them to Clay for waterfall enrichment (personal email, work email, phone number, company data), waits for enrichment completion, and updates the candidate records in the ATS with enriched contact information.
Implementation
{
"rows": [
{
"full_name": "{{$json.full_name}}",
"linkedin_url": "{{$json.linkedin_url}}",
"current_company": "{{$json.current_company}}",
"current_title": "{{$json.current_title}}",
"job_id": "{{$json.job_id}}"
}
]
}const rows = $input.first().json.rows || [];
const enriched = rows.map(row => ({
linkedin_url: row.linkedin_url,
personal_email: row.find_personal_email || null,
work_email: row.find_work_email || null,
phone: row.find_phone || null,
company_size: row.company_enrichment?.employee_count || null,
company_industry: row.company_enrichment?.industry || null,
enrichment_score: [
row.find_personal_email ? 1 : 0,
row.find_work_email ? 1 : 0,
row.find_phone ? 1 : 0
].reduce((a, b) => a + b, 0),
job_id: row.job_id
}));
return enriched.map(e => ({ json: e }));Rate Limiting: The splitInBatches node with batch size 5 and 2-second delays between batches prevents Clay API rate limit errors. Monitor Clay credit consumption in the summary notification.
AI Message Personalization Engine
Type: workflow
n8n workflow that takes enriched candidate profiles, constructs detailed context prompts for GPT-5.4 mini, generates personalized outreach messages for each step of the sequence (connection request, 3 follow-ups, email versions), validates message quality and compliance, and stores the personalized content ready for the Outreach Orchestrator to deploy.
Implementation
GET {{bullhorn_rest_url}}/entity/JobOrder/{{$json.job_id}}?fields=title,description,salary,benefits,clientCorporationconst candidate = $input.first().json;
const job = $('Fetch Job Details').first().json;
const context = {
candidate_name: candidate.full_name,
candidate_first_name: candidate.full_name.split(' ')[0],
current_title: candidate.current_title,
current_company: candidate.current_company,
linkedin_url: candidate.linkedin_url,
job_title: job.title,
company_name: job.clientCorporation?.name || 'our client',
company_industry: candidate.company_industry,
company_size: candidate.company_size,
key_selling_points: job.description ? job.description.substring(0, 500) : '',
salary_range: job.salary || 'competitive',
candidate_email: candidate.personal_email || candidate.work_email,
has_email: !!(candidate.personal_email || candidate.work_email)
};
return [{ json: context }];Node 4 — Generate LinkedIn Connection Request: System prompt rules: (1) MUST be under 300 characters total including spaces. (2) Mention the candidate's current role or company specifically. (3) Be conversational and genuine, not salesy. (4) Do NOT mention the specific company hiring (say "an exciting opportunity" or similar). (5) End with a soft question or reason to connect. (6) Do NOT use emojis, exclamation marks, or ALL CAPS. (7) Do NOT make claims you cannot verify about the candidate. (8) Never use phrases like "I came across your profile" or "I was impressed by".
Write a LinkedIn connection request note for {{candidate_first_name}}, who is currently a {{current_title}} at {{current_company}}. I'm recruiting for a {{job_title}} role. Keep it under 300 characters.Node 5 — Generate Follow-up Message 1 (Day 2 after connection): System prompt rules: (1) Keep under 150 words. (2) Thank them for connecting (briefly). (3) Introduce the specific opportunity with 2–3 compelling details. (4) Mention the company name: {{company_name}}. (5) Include one specific detail relevant to their background at {{current_company}}. (6) End with a clear but low-pressure call-to-action (suggest a brief call). (7) Be warm and professional, not pushy. (8) Include: "We use AI-assisted tools to identify great candidates. Learn more: [client-website]/ai-recruiting-notice".
Node 6 — Generate Follow-up Message 2 (Day 5): System prompt angle: share a specific reason why this role could be a career-advancing move, reference market trends or team growth.
Node 7 — Generate Follow-up Message 3 - Final (Day 9): System prompt: Soft close. Acknowledge they may not be interested. Ask if they know someone who might be. Include opt-out: "If this isn't relevant, just let me know and I won't reach out again."
const messages = $input.first().json;
const connectionMsg = messages.connection_request;
// Validate connection request length
if (connectionMsg.length > 300) {
// Truncate and regenerate flag
messages.connection_request = connectionMsg.substring(0, 297) + '...';
messages.quality_flag = 'connection_msg_truncated';
}
// Check for prohibited phrases
const prohibited = ['guarantee', 'best candidate', 'perfect fit', 'salary of'];
const allMessages = [messages.connection_request, messages.followup_1, messages.followup_2, messages.followup_3].join(' ').toLowerCase();
for (const phrase of prohibited) {
if (allMessages.includes(phrase)) {
messages.quality_flag = `contains_prohibited_phrase: ${phrase}`;
}
}
// Check for hallucination indicators
if (allMessages.includes('your work on') || allMessages.includes('your project')) {
messages.quality_flag = 'possible_hallucination_detected';
}
return [{ json: messages }];Outreach Campaign Orchestrator Workflow
Type: workflow
n8n workflow that monitors the ATS for recruiter-approved candidates (human-in-the-loop gate), retrieves their personalized messages, creates and launches Expandi campaigns with the personalized content, monitors campaign progress via webhooks, handles responses, and updates the ATS with outreach status and candidate engagement data.
Implementation
GET {{bullhorn_rest_url}}/search/Candidate?query=customText1:ai_sourced AND status:approved AND customText3:NOT:in_sequence&fields=id,firstName,lastName,email,customText5,customObject1s&count=25// enforces 15 candidates/day per recruiter using n8n static data
// Check how many candidates have been added to campaigns today
const today = new Date().toISOString().split('T')[0];
const dailyCount = $workflow.staticData.dailyCounts?.[today] || 0;
const DAILY_LIMIT = 15; // per recruiter, conservative
const candidates = $input.all();
const toProcess = candidates.slice(0, Math.max(0, DAILY_LIMIT - dailyCount));
// Update counter
if (!$workflow.staticData.dailyCounts) $workflow.staticData.dailyCounts = {};
$workflow.staticData.dailyCounts[today] = dailyCount + toProcess.length;
return toProcess;# POST body to
# https://api.expandi.io/api/v1/campaigns/{{campaign_id}}/prospects
# (Authorization: Bearer {{expandi_api_key}})
{
"prospects": [
{
"linkedin_url": "{{candidate.linkedin_url}}",
"first_name": "{{candidate.firstName}}",
"last_name": "{{candidate.lastName}}",
"custom_fields": {
"ai_connection_message": "{{messages.connection_request}}",
"ai_followup_1": "{{messages.followup_1}}",
"ai_followup_2": "{{messages.followup_2}}",
"ai_followup_3": "{{messages.followup_3}}"
}
}
]
}- connection_accepted: Update ATS, log event
- message_replied: Parse reply content → classify with OpenAI → if INTERESTED: update ATS to 'responded_interested' and notify recruiter via Slack/email → if OPT_OUT: trigger suppression workflow, update ATS, stop sequence → if QUESTION: notify recruiter for manual response
- message_sent: Log in ATS activity timeline
- connection_rejected: After 14 days, trigger email fallback sequence if email is available
Node 8: OpenAI Response Classification — System prompt for gpt-5.4-mini (Temperature: 0.1, Max Tokens: 10)
Node 9 (Email Fallback): Emails sent via Google Workspace Gmail node must include a CAN-SPAM compliant footer with physical address and unsubscribe link. Triggered only when LinkedIn connection is not accepted after 14 days AND candidate email is available.
Candidate Opt-Out and Suppression Handler
Type: workflow
Dedicated n8n workflow that handles all opt-out requests across channels (LinkedIn replies, email unsubscribes, manual requests). Ensures immediate suppression from all active and future campaigns, updates the ATS suppression list, sends confirmation to the candidate, and logs the action for compliance audit trail.
Implementation
// Add to n8n static data suppression list
if (!$workflow.staticData.suppressionList) {
$workflow.staticData.suppressionList = [];
}
const candidate = $input.first().json;
const entry = {
linkedin_url: candidate.linkedin_url,
email: candidate.email,
opted_out_date: new Date().toISOString(),
source: candidate.opt_out_source,
reason: candidate.opt_out_message || 'no reason provided'
};
$workflow.staticData.suppressionList.push(entry);
return [{ json: { ...entry, status: 'suppressed' } }];const fs = require('fs');
const logEntry = {
timestamp: new Date().toISOString(),
candidate_id: $input.first().json.candidate_id,
action: 'OPT_OUT_PROCESSED',
channels_suppressed: ['linkedin', 'email'],
confirmation_sent: true,
processed_by: 'automated_workflow'
};
fs.appendFileSync('/home/node/.n8n/compliance_audit_log.jsonl', JSON.stringify(logEntry) + '\n');
return [{ json: logEntry }];Recruiter System Prompt Library
Type: prompt
A comprehensive library of tested and optimized system prompts for the OpenAI API calls throughout the pipeline. These prompts are tuned for the HR/Staffing vertical and enforce compliance, tone, length limits, and anti-hallucination guardrails. Each prompt includes the role context, output format requirements, and explicit constraints.
Implementation:
System Prompt Library for HR/Staffing Autonomous Agent
PROMPT 1: LinkedIn Connection Request Generator
Role: You are a senior recruiter at a boutique staffing firm writing LinkedIn connection request notes.
Task: Write a brief, personalized connection request note.
Rules
Input format: Candidate name, current title, current company, target role title.
PROMPT 2: Follow-Up Message 1 (Post-Connection, Day 2)
Role: You are a senior recruiter following up after a LinkedIn connection was accepted.
Task: Write the first follow-up message introducing a specific job opportunity.
PROMPT 3: Follow-Up Message 2 (Day 5, No Reply)
Role: You are a recruiter sending a gentle second follow-up to a candidate who hasn't replied.
Task: Send a value-add follow-up that gives the candidate a new reason to engage.
PROMPT 4: Follow-Up Message 3 (Day 9, Final)
Role: You are a recruiter sending a final, respectful outreach to a passive candidate.
Task: Write a graceful final message that respects their silence while keeping the door open.
Rules
PROMPT 5: Response Classifier
Role: You are an NLP classifier for recruiting outreach responses.
Task: Classify the candidate's reply into exactly one category.
Categories
- INTERESTED: Wants to learn more, agrees to call, asks for details enthusiastically
- NOT_INTERESTED: Declines, happy in current role, not looking, bad timing
- QUESTION: Asks about salary, location, remote policy, company details, or role specifics
- OUT_OF_OFFICE: Auto-reply, vacation notice, parental leave, sabbatical
- OPT_OUT: Asks to stop being contacted, unsubscribe, remove from list, reports as spam
- REFERRAL: Suggests another person for the role
Rules
PROMPT 6: Email Subject Line Generator
Role: You write email subject lines for recruiting outreach.
Examples of good subject lines:
- [FirstName], quick question about your next move
- [CurrentCompany] → something interesting?
- [RoleTitle] opportunity, [FirstName]
Testing & Validation
- SOURCING TEST: Create a test search in hireEZ for 'Senior Software Engineer, Python, 5+ years, San Francisco Bay Area'. Verify that at least 30 candidate profiles are returned with LinkedIn URLs, current titles, and company names. Cross-reference 5 random profiles by manually checking their LinkedIn pages to confirm data accuracy.
- ATS INTEGRATION TEST: Export 5 candidates from hireEZ to Bullhorn/ATS. Verify that all fields map correctly: first name, last name, current title, current company, LinkedIn URL (in customText5), source attribution (customText1 = 'ai_sourced'), and status (customText3 = 'pending_review'). Confirm no duplicate records were created.
- ENRICHMENT TEST: Run 10 candidate LinkedIn URLs through the Clay enrichment pipeline via the n8n webhook. Verify that at least 6 out of 10 return a valid email address (personal or work). Spot-check 3 email addresses using an email verification service (e.g., NeverBounce or ZeroBounce) to confirm deliverability.
- MESSAGE PERSONALIZATION TEST: Generate personalized outreach messages for 10 candidates using the OpenAI engine. Have two recruiters independently rate each connection request message on: accuracy (no fabricated details), tone (professional but warm), relevance (mentions something specific to the candidate), and length compliance (under 300 characters). Target: 8/10 messages pass all criteria without edits.
- LINKEDIN AUTOMATION TEST: Create a test Expandi campaign targeting 2 internal team members (or recruiter's own secondary LinkedIn account). Verify: (a) connection request sends with personalized note correctly populated from custom fields, (b) after manual acceptance, follow-up message 1 fires on Day 2, (c) webhook fires to n8n upon message sent and reply received.
- HUMAN-IN-THE-LOOP GATE TEST: Confirm that candidates with ATS status 'pending_review' are NOT picked up by the Outreach Orchestrator workflow. Change one candidate's status to 'approved' and verify that within 30 minutes, they are added to an Expandi campaign. Change another to 'rejected' and verify no outreach is triggered.
- OPT-OUT HANDLING TEST: Simulate an opt-out by replying 'please remove me from your list' to a test campaign message. Verify within 5 minutes: (a) Expandi campaign stops for that prospect, (b) ATS record updated to 'opted_out' with Do Not Contact flag, (c) confirmation message sent to the candidate, (d) entry appears in compliance audit log, (e) candidate does not appear in any future sourcing exports.
- EMAIL DELIVERABILITY TEST: After the 2-week warm-up period, send test emails from each recruiter's talent.clientfirm.com mailbox to mail-tester.com. Verify a score of 9/10 or higher. Also send to Gmail, Outlook, and Yahoo test accounts and verify messages land in Primary/Inbox (not Spam or Promotions).
- END-TO-END PIPELINE TEST: Execute the full autonomous agent cycle for one real open job: (1) n8n triggers hireEZ search → (2) candidates created in ATS → (3) Clay enriches → (4) OpenAI personalizes → (5) recruiter reviews and approves 5 candidates → (6) Expandi sends connection requests → (7) monitor for 7 days. Verify all data flows correctly at each stage and ATS activity log shows complete audit trail.
- COMPLIANCE VERIFICATION TEST: Verify the AI Recruiting Notice page is live at clientfirm.com/ai-recruiting-notice with all required disclosures. Verify all outreach messages include the compliance notice link. Verify data retention workflow correctly identifies and flags records older than the configured retention period. If recruiting in NYC: verify bias audit documentation is on file and candidate notice requirements are met per LL 144.
- DAILY VOLUME LIMIT TEST: Attempt to approve more candidates than the daily limit (15 per recruiter). Verify the Outreach Orchestrator respects the limit and queues excess candidates for the following day rather than exceeding safe LinkedIn activity thresholds.
- RESILIENCE TEST: Temporarily disconnect the Clay API key and trigger the enrichment pipeline. Verify the workflow handles the error gracefully (logs error, sends MSP notification, does not crash or lose candidate data). Restore the API key and verify queued candidates are processed on the next run.
Client Handoff
The client handoff session should be a structured 2-hour meeting with the recruiting team lead, all recruiters who will use the system, and the client's compliance stakeholder. Cover the following topics in order:
Documentation to Leave Behind
Maintenance
ONGOING MSP MAINTENANCE RESPONSIBILITIES:
Weekly (1-2 hours/week)
- Monitor n8n workflow execution logs for errors or failures; investigate and resolve any failed executions
- Review LinkedIn account health for each recruiter in Expandi: check for any warnings, restrictions, or unusual activity flags
- Monitor email deliverability metrics via Google Postmaster Tools: domain reputation should remain 'High', bounce rate under 3%, spam rate under 0.1%
- Review Clay credit consumption and enrichment success rates; flag if email hit rate drops below 50%
- Check Expandi campaign metrics: connection acceptance rate (target >30%), reply rate (target >15%), and flag campaigns significantly below benchmarks for prompt tuning
Monthly (3-5 hours/month)
- Update n8n Docker image to latest stable version (test in staging first)
- Review and optimize OpenAI prompts based on recruiter feedback and message performance data; A/B test variations
- Run compliance audit: verify AI Recruiting Notice page is current, suppression list is enforced across all platforms, data retention purge workflow has run successfully
- Generate monthly performance report for client: candidates sourced, enriched, contacted, responded, interviews scheduled, placements attributed to AI pipeline
- Review hireEZ and Expandi platform release notes for new features or breaking changes; apply configuration updates as needed
- Rotate or refresh dedicated residential proxy IPs if any are showing degraded LinkedIn performance
- Verify SSL certificate auto-renewal is functioning (Let's Encrypt certificates every 90 days)
docker compose pull && docker compose up -dQuarterly (5-8 hours/quarter)
- Conduct full security audit of n8n server: apply OS security patches, review access logs, rotate API keys and passwords
- Review and update outreach sequence templates based on 90 days of performance data; retire underperforming sequences and deploy new variants
- Conduct LinkedIn account maintenance: review each recruiter's connection acceptance patterns, adjust daily limits based on account maturity and performance
- Review SaaS vendor pricing and contract renewals; evaluate whether current tier still fits usage patterns
- If client recruits in jurisdictions with AI hiring laws: coordinate annual bias audit with third-party auditor (NYC LL 144 requires annual audit); update candidate notice language as regulations evolve
- Backup n8n PostgreSQL database
sudo apt update && sudo apt upgradedocker exec n8n-postgres pg_dump -U n8n n8n > backup_$(date +%Y%m%d).sqlSLA Considerations
- Critical (n8n server down, ATS integration broken): 4-hour response, 8-hour resolution target
- High (LinkedIn account restricted, email deliverability degraded): 8-hour response, 24-hour resolution target
- Medium (enrichment hit rate drop, prompt quality issues): 24-hour response, 72-hour resolution target
- Low (feature requests, optimization suggestions): Addressed in next monthly review
Escalation Path
Model/Prompt Retraining Triggers
- Outreach response rate drops below 15% for 2 consecutive weeks → review and update personalization prompts
- Connection acceptance rate drops below 20% → review connection request prompt and LinkedIn account health
- Response classifier accuracy drops below 90% (measured by recruiter corrections) → retune classifier prompt with new examples
- New state or federal AI hiring regulation enacted → update compliance notice, review all prompts for new requirements
- Client adds new job categories or industries → create specialized prompt variants tuned for those verticals
Alternatives
All-in-One Platform Approach (SeekOut or Gem)
Instead of assembling a multi-tool stack (hireEZ + Expandi + Clay + OpenAI + n8n), deploy a single enterprise platform like SeekOut ($833+/seat/month) or Gem ($300+/seat/month) that handles sourcing, enrichment, personalization, and outreach sequences natively within one interface. These platforms offer built-in CRM, ATS integration, and compliance features without requiring custom workflow orchestration.
- COST: Significantly higher per-seat cost ($833/seat/month for SeekOut vs. ~$400/seat/month for the assembled stack). For 5 seats, the annual difference is ~$26,000.
- COMPLEXITY: Much lower — no n8n server to maintain, no custom workflows to debug, no multi-vendor API management.
- CAPABILITY: SeekOut and Gem have less flexible personalization (pre-built templates rather than GPT-powered custom generation) and their LinkedIn automation is more conservative (typically limited to InMail rather than connection requests + messaging).
- BEST FOR: Clients with budget for premium tools who prioritize simplicity and vendor support over maximum customization. Also better for clients with strict IT policies that prohibit self-hosted infrastructure.
Budget DIY Stack (Dripify + PhantomBuster + OpenAI)
For cost-sensitive small staffing firms (1-5 recruiters), assemble a minimal stack: LinkedIn Sales Navigator ($99/month) for manual searching, PhantomBuster ($69/month) for LinkedIn profile scraping and data extraction, OpenAI API (~$30/month) for message personalization, and Dripify ($59/seat/month) for automated LinkedIn outreach sequences. Use Zapier ($20/month) instead of self-hosted n8n for simpler workflow automation.
- COST: Total ~$280-$350/month for a single recruiter vs. ~$600-$800/month for the primary approach. Saves ~$3,000-$5,000/year per recruiter.
- COMPLEXITY: Lower initial setup but more manual work daily — no AI-powered candidate discovery (manual LinkedIn searching), less sophisticated enrichment, more hands-on campaign management.
- CAPABILITY: No ATS Rediscovery feature, smaller candidate database (limited to LinkedIn search results), no autonomous sourcing (recruiter must define and execute searches manually). LinkedIn compliance risk is similar.
- BEST FOR: Solo recruiters or micro-agencies with tight budgets who want to test AI-assisted outreach before committing to a full autonomous agent stack. Good starting point that can be upgraded to the primary approach as volume grows.
HeroHunt.ai Autonomous Agent (Turnkey SaaS)
Deploy HeroHunt.ai (~$107/month) as a fully autonomous AI recruiter that handles the entire pipeline — sourcing across LinkedIn, GitHub, and Stack Overflow, generating personalized outreach, and managing candidate pipelines — with minimal configuration. No custom workflows, no self-hosted infrastructure, no multi-vendor integration management.
- COST: Lowest tool cost at ~$107/month, but less MSP recurring revenue opportunity since there's less to manage.
- COMPLEXITY: Minimal — turnkey SaaS with guided setup. Can be deployed in days rather than weeks.
- CAPABILITY: Less customizable than the primary approach — you're locked into HeroHunt's AI models and outreach logic rather than having full control over OpenAI prompts and workflow orchestration. Limited ATS integration options compared to hireEZ or Gem. Newer vendor with less market track record.
- BEST FOR: MSP clients who need fast deployment (under 1 week), have simple ATS setups, and prioritize speed-to-value over customization. Also good as an interim solution while the full autonomous agent stack is being built out.
LinkedIn Recruiter + Hiring Assistant (Native LinkedIn)
Use LinkedIn's own enterprise recruiting tools: LinkedIn Recruiter Corporate ($10,800/seat/year) with the LinkedIn Hiring Assistant AI feature. This stays entirely within LinkedIn's ecosystem, eliminating third-party automation compliance risk. The Hiring Assistant helps draft InMails and provides AI-suggested candidates, though it doesn't provide fully autonomous sourcing or multi-channel outreach.
- COST: Very expensive at $900/seat/month — more than double the primary approach for fewer capabilities.
- COMPLEXITY: Lowest possible — no integrations, no servers, no third-party tools. Everything is within LinkedIn.
- CAPABILITY: Most limited — no autonomous agent behavior (recruiters still manually search, evaluate, and initiate outreach), no email fallback channel, no custom AI personalization (LinkedIn's built-in AI is generic), no multi-platform sourcing (LinkedIn only). Zero risk of LinkedIn account restriction since you're using official tools within their TOS.
- BEST FOR: Highly risk-averse clients (particularly those in regulated industries or with strict legal departments) who cannot tolerate any LinkedIn TOS compliance risk, and who are willing to pay a premium for the safety of staying within LinkedIn's official tools.
Custom Open-Source Agent Stack (LangChain + n8n)
Build a fully custom autonomous recruiting agent using LangChain (Python framework for AI agents), self-hosted open-source LLMs (e.g., Llama 3 or Mistral via Ollama), n8n for orchestration, and direct LinkedIn API/scraping for data collection. This gives maximum control, data privacy, and eliminates per-seat SaaS costs but requires significant development effort.
- COST: Lowest recurring cost (primarily infrastructure: ~$100-$200/month for compute) but highest upfront development cost ($15,000-$30,000 for custom development, 4-8 weeks of senior developer time).
- COMPLEXITY: Highest — requires Python development expertise, LLM fine-tuning, LinkedIn reverse-engineering, and ongoing maintenance of custom code.
- CAPABILITY: Maximum flexibility and customization, full data ownership, no vendor lock-in. But open-source LLMs currently produce lower quality personalized messages than GPT-5.4 mini, and LinkedIn data access without official tools is the highest-risk approach for account restrictions.
- BEST FOR: MSPs with strong in-house development teams who want to build a proprietary, white-labeled AI recruiting product to resell across many clients. The high upfront investment is amortized across the client base. Not recommended for single-client deployments.
Want early access to the full toolkit?