What You'll Build
This system delivers a complete prospecting automation pipeline with these capabilities:
| Component |
Technology |
Purpose |
| Data Source |
Crunchbase CSV/API |
Daily import of newly funded startups (≥$4M) |
| Company Enrichment |
LinkedIn + Web Scraping |
Extract team structure, careers pages, founder profiles |
| Finance Team Detection |
LLM (GPT-4/Claude) |
Classify whether company has in-house finance function |
| Scoring Engine |
n8n Function Nodes |
Calculate founder quality and investor quality scores |
| ICP Classification |
Business Logic + LLM |
Assign ICP-1, ICP-2, or ICP-3 tier based on signals |
| System of Record |
Airtable |
Searchable database with filtering by sector, geography, score |
| Change Detection |
Scheduled Re-scans |
Monitor companies for team changes, new hires, website updates |
| Alert System |
Slack + Email |
Real-time notifications for ICP-1 prospects, daily/weekly digests |
Key features:
- Automatic deduplication to prevent processing the same company twice
- Confidence scoring for each data point (high/medium/low)
- Subsector filtering (robotics, fintech, healthcare, etc.)
- Geographic filtering for regional targeting
- Historical change tracking to detect when companies become sales-ready
- Batch processing to handle 50-100 new companies daily
Prerequisites
Before starting, ensure you have:
Step 1: Set Up Data Source and Import Pipeline
This phase establishes the foundation by connecting to Crunchbase and creating the initial data ingestion workflow.
Configure Crunchbase Data Import
- Create a new n8n workflow and add a Schedule Trigger node set to run daily at 6 AM
- Add an HTTP Request node to fetch Crunchbase data (or use CSV import via Google Sheets)
- Configure filtering logic to only process companies with funding_total ≥ $4,000,000
Node configuration for Crunchbase filtering:
// Function node: Filter by funding amount
const items = $input.all();
const filtered = items.filter(item => {
const funding = parseFloat(item.json.funding_total_usd);
return funding >= 4000000;
});
return filtered;
Why this works:
The Schedule Trigger ensures you check for new companies daily without manual intervention. Filtering at the source prevents wasting API calls and processing time on companies that don't meet your minimum criteria. Think of this as a bouncer at a club—only companies with the right credentials (funding level) get through the door.
Set up Airtable as system of record
- Create an Airtable base with tables: Companies, Enrichment_Data, Scores, Change_Log
- Add an Airtable node in n8n configured for "Create" operation
- Map Crunchbase fields to Airtable columns: company_name, website, funding_total, founded_date, location, crunchbase_url
Critical field mappings:
unique_id: Use Crunchbase permalink or domain as deduplication key
last_updated: Timestamp for change detection
processing_status: Track enrichment pipeline progress (pending/enriching/scored/complete)
Deduplication logic:
// Function node: Check if company exists
const domain = $json.website.replace(/^https?:\/\//, '').split('/')[0];
const existing = await $('Airtable').getAll({
filterByFormula: `{domain} = '${domain}'`
});
if (existing.length > 0) {
return { skip: true, reason: 'duplicate' };
}
return { skip: false, company: $json };
Why this approach:
Using domain as the deduplication key is more reliable than company name (which can have variations like "Acme Inc." vs "Acme"). The processing_status field lets you resume interrupted workflows without reprocessing completed companies. This is like a factory assembly line—each station knows exactly which products have already been processed.
Step 2: Build Company Enrichment Layer
This phase extracts detailed information about each company's team structure, leadership, and operational signals.
Configure LinkedIn Enrichment
- Add an HTTP Request node for LinkedIn Sales Navigator API (or use a scraping service like Apify)
- Extract founder profiles, C-suite executives, and finance team members
- Store results in Airtable Enrichment_Data table with confidence scores
LinkedIn data extraction:
// Function node: Parse LinkedIn profiles
const profiles = $json.people;
const founders = profiles.filter(p =>
p.title.toLowerCase().includes('founder') ||
p.title.toLowerCase().includes('ceo')
);
const financeTeam = profiles.filter(p =>
p.title.toLowerCase().includes('cfo') ||
p.title.toLowerCase().includes('controller') ||
p.title.toLowerCase().includes('finance')
);
return {
founder_count: founders.length,
founder_names: founders.map(f => f.name),
has_finance_team: financeTeam.length > 0,
finance_team_size: financeTeam.length,
confidence: profiles.length > 5 ? 'high' : 'medium'
};
Web scraping for careers pages
- Add an HTTP Request node to fetch company website
- Use a Function node with regex to find careers/team page URLs
- Extract job postings related to finance roles (CFO, Controller, Accountant)
Scraping logic:
// Function node: Detect finance hiring signals
const html = $json.body;
const financeKeywords = ['cfo', 'controller', 'accountant', 'finance manager', 'fp&a'];
const hasFinanceJobs = financeKeywords.some(keyword =>
html.toLowerCase().includes(keyword)
);
const teamPageUrl = html.match(/href=["'](.*?(?:team|about|careers)[^"']*)/i);
return {
has_finance_job_posting: hasFinanceJobs,
team_page_url: teamPageUrl ? teamPageUrl[1] : null,
scraped_at: new Date().toISOString()
};
Why this works:
LinkedIn provides structured data about current employees, while careers pages reveal hiring intent. A company posting for a CFO is a different signal than one that already has a full finance team. You're looking for the Goldilocks zone—companies big enough to need help but without an established finance function yet.
Step 3: Implement LLM-Based Classification
This phase uses large language models to make intelligent judgments about whether a company matches your ideal customer profile.
Configure OpenAI/Claude Integration
- Add an OpenAI node (or HTTP Request for Claude API)
- Set model to GPT-4 or Claude 3 Opus for best reasoning
- Configure for JSON output mode to ensure structured responses
LLM prompt for finance team detection:
// Function node: Prepare LLM prompt
const prompt = `Analyze this startup and determine if they have an in-house finance team.
Company: ${$json.company_name}
Employee count: ${$json.employee_count}
LinkedIn profiles: ${JSON.stringify($json.linkedin_profiles)}
Careers page data: ${$json.careers_data}
Website content: ${$json.website_excerpt}
Return JSON with this exact structure:
{
"has_finance_team": boolean,
"confidence": "high" | "medium" | "low",
"reasoning": "brief explanation",
"finance_team_size": number,
"key_signals": ["signal1", "signal2"]
}
Consider:
- Job titles containing CFO, Controller, Finance Manager
- Team pages listing finance department
- Job postings for finance roles
- Company size (>50 employees more likely to have finance team)`;
return { prompt };
OpenAI node configuration:
- Model:
gpt-4-turbo-preview
- Temperature: 0.2 (lower = more consistent)
- Response format:
json_object
- Max tokens: 500
Why this approach:
LLMs excel at synthesizing multiple weak signals into a confident judgment. A human would look at LinkedIn profiles, careers pages, and team descriptions to make this call—the LLM does the same reasoning at scale. The low temperature (0.2) ensures consistent classifications across similar companies. Think of this as hiring an analyst who reads all the data and gives you a clear yes/no answer with their reasoning.
Error handling for LLM failures:
// Function node: Handle LLM errors
try {
const result = JSON.parse($json.response);
return result;
} catch (error) {
return {
has_finance_team: null,
confidence: 'low',
reasoning: 'LLM parsing failed',
error: error.message,
requires_manual_review: true
};
}
Step 4: Build Scoring and ICP Classification Engine
This phase calculates numerical scores for founder quality and investor quality, then assigns each company to an ICP tier.
Configure Founder Quality Scoring
- Add a Function node to calculate founder score (0-100)
- Weight factors: previous exits, education (top universities), prior experience at notable companies, LinkedIn connections
Founder scoring logic:
// Function node: Calculate founder score
const founders = $json.founders;
let score = 0;
founders.forEach(founder => {
// Previous exits (30 points max)
if (founder.previous_exits > 0) score += Math.min(founder.previous_exits * 15, 30);
// Top university (20 points)
const topSchools = ['Stanford', 'MIT', 'Harvard', 'Berkeley', 'Carnegie Mellon'];
if (topSchools.some(school => founder.education.includes(school))) score += 20;
// Notable company experience (25 points)
const notableCompanies = ['Google', 'Meta', 'Amazon', 'Microsoft', 'Apple', 'Tesla'];
if (notableCompanies.some(co => founder.experience.includes(co))) score += 25;
// LinkedIn connections as proxy for network (25 points max)
score += Math.min(founder.linkedin_connections / 200, 25);
});
// Average across founders
const finalScore = Math.min(Math.round(score / founders.length), 100);
return {
founder_score: finalScore,
founder_tier: finalScore >= 70 ? 'A' : finalScore >= 50 ? 'B' : 'C'
};
Configure Investor Quality Scoring
- Add a Function node to score investors (0-100)
- Weight factors: tier-1 VCs (Sequoia, a16z, etc.), total AUM, successful exits from portfolio
Investor scoring logic:
// Function node: Calculate investor score
const investors = $json.investors;
let score = 0;
const tier1VCs = ['Sequoia', 'Andreessen Horowitz', 'Benchmark', 'Accel', 'Greylock'];
const tier2VCs = ['First Round', 'NEA', 'Lightspeed', 'Bessemer'];
investors.forEach(investor => {
if (tier1VCs.some(vc => investor.name.includes(vc))) {
score += 40;
} else if (tier2VCs.some(vc => investor.name.includes(vc))) {
score += 25;
} else {
score += 10; // Other institutional investors
}
});
const finalScore = Math.min(score, 100);
return {
investor_score: finalScore,
investor_tier: finalScore >= 70 ? 'A' : finalScore >= 40 ? 'B' : 'C',
lead_investors: investors.slice(0, 3).map(i => i.name)
};
ICP Classification Logic
- Add a Function node that combines all signals to assign ICP tier
- ICP-1: No finance team + high founder/investor scores + right subsector
- ICP-2: Partial finance team or medium scores
- ICP-3: Full finance team or low scores
ICP assignment:
// Function node: Assign ICP tier
const hasFinanceTeam = $json.has_finance_team;
const founderScore = $json.founder_score;
const investorScore = $json.investor_score;
const fundingAmount = $json.funding_total_usd;
const subsector = $json.subsector;
// Priority subsectors
const targetSubsectors = ['robotics', 'climate tech', 'enterprise software', 'fintech'];
const isTargetSubsector = targetSubsectors.includes(subsector.toLowerCase());
// ICP-1: Perfect fit
if (!hasFinanceTeam &&
founderScore >= 60 &&
investorScore >= 50 &&
fundingAmount >= 10000000 &&
isTargetSubsector) {
return { icp_tier: 'ICP-1', priority: 'high' };
}
// ICP-2: Good fit
if ((!hasFinanceTeam || $json.finance_team_size <= 1) &&
(founderScore >= 50 || investorScore >= 40) &&
fundingAmount >= 5000000) {
return { icp_tier: 'ICP-2', priority: 'medium' };
}
// ICP-3: Low priority
return { icp_tier: 'ICP-3', priority: 'low' };
Why this approach:
Scoring combines objective metrics (funding amount, investor names) with subjective signals (founder quality) into a single prioritization framework. The tiered system lets your sales team focus energy on ICP-1 prospects first. This is like a triage system in an emergency room—you treat the most critical patients first based on multiple vital signs, not just one symptom.
Variables to customize:
tier1VCs: Add your preferred top-tier investors
targetSubsectors: Adjust based on your firm's expertise
fundingAmount thresholds: Change based on your typical deal size
- Score weights: Increase/decrease importance of founder vs investor quality
Step 5: Implement Change Detection and Monitoring
This phase creates a system that periodically re-scans companies to detect when they become more sales-ready.
Configure Scheduled Re-scans
- Add a separate Schedule Trigger node set to run weekly
- Query Airtable for companies that haven't been checked in 7+ days
- Re-run enrichment and scoring workflows
Change detection logic:
// Function node: Detect meaningful changes
const previous = $json.previous_data;
const current = $json.current_data;
const changes = [];
// Finance team changes
if (!previous.has_finance_team && current.has_finance_team) {
changes.push({
type: 'finance_team_added',
significance: 'high',
message: 'Company hired finance team - may no longer need services'
});
}
// New job postings
if (current.finance_job_postings > previous.finance_job_postings) {
changes.push({
type: 'hiring_finance_roles',
significance: 'medium',
message: 'Actively hiring finance roles - window closing'
});
}
// Funding round
if (current.funding_total > previous.funding_total) {
const newFunding = current.funding_total - previous.funding_total;
changes.push({
type: 'new_funding',
significance: 'high',
message: `Raised $${(newFunding / 1000000).toFixed(1)}M - prime time to reach out`
});
}
// ICP tier improvement
if (previous.icp_tier === 'ICP-2' && current.icp_tier === 'ICP-1') {
changes.push({
type: 'icp_upgrade',
significance: 'high',
message: 'Moved to ICP-1 - prioritize outreach'
});
}
return {
has_changes: changes.length > 0,
changes: changes,
last_checked: new Date().toISOString()
};
Why this works:
Companies evolve rapidly. A startup without a finance team today might hire a CFO next month, closing your sales window. Change detection acts like a motion sensor—it only alerts you when something meaningful happens, not just because time passed. This prevents your team from reaching out too late or wasting time on companies that are no longer good fits.
Step 6: Build Alert and Digest System
This phase creates intelligent notifications that surface the right prospects at the right time.
Configure Slack Real-Time Alerts
- Add a Slack node for immediate notifications on ICP-1 prospects
- Include company name, scores, key signals, and direct link to Airtable record
Slack message format:
// Function node: Format Slack alert
const company = $json;
const message = {
text: `🎯 New ICP-1 Prospect: ${company.company_name}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${company.company_name}* just raised *$${(company.funding_total_usd / 1000000).toFixed(1)}M*`
}
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Founder Score:*
${company.founder_score}/100` },
{ type: 'mrkdwn', text: `*Investor Score:*
${company.investor_score}/100` },
{ type: 'mrkdwn', text: `*Finance Team:*
${company.has_finance_team ? '❌ Yes' : '✅ No'}` },
{ type: 'mrkdwn', text: `*Subsector:*
${company.subsector}` }
]
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Why this matters:*
${company.icp_reasoning}`
}
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'View in Airtable' },
url: company.airtable_url
},
{
type: 'button',
text: { type: 'plain_text', text: 'LinkedIn' },
url: company.linkedin_url
}
]
}
]
};
return { message };
Configure Daily and Weekly Digests
- Add Schedule Triggers for daily (8 AM) and weekly (Monday 8 AM) digests
- Query Airtable for new ICP-1 and ICP-2 prospects from the period
- Send formatted email with summary statistics and top prospects
Daily digest logic:
// Function node: Generate daily digest
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const newProspects = $json.companies.filter(c =>
new Date(c.created_at) >= yesterday
);
const icp1 = newProspects.filter(c => c.icp_tier === 'ICP-1');
const icp2 = newProspects.filter(c => c.icp_tier === 'ICP-2');
const summary = {
total_new: newProspects.length,
icp1_count: icp1.length,
icp2_count: icp2.length,
avg_funding: newProspects.reduce((sum, c) => sum + c.funding_total_usd, 0) / newProspects.length,
top_subsectors: [...new Set(newProspects.map(c => c.subsector))].slice(0, 5)
};
const emailBody = `
<h2>Daily Prospect Digest - ${new Date().toLocaleDateString()}</h2>
<h3>Summary</h3>
<ul>
<li>New prospects: ${summary.total_new}</li>
<li>ICP-1 (high priority): ${summary.icp1_count}</li>
<li>ICP-2 (medium priority): ${summary.icp2_count}</li>
<li>Avg funding: $${(summary.avg_funding / 1000000).toFixed(1)}M</li>
</ul>
<h3>Top ICP-1 Prospects to Contact Today</h3>
${icp1.slice(0, 5).map(c => `
<div style="border: 1px solid #ccc; padding: 10px; margin: 10px 0;">
<h4>${c.company_name}</h4>
<p><strong>Funding:</strong> $${(c.funding_total_usd / 1000000).toFixed(1)}M |
<strong>Founder Score:</strong> ${c.founder_score}/100 |
<strong>Investor Score:</strong> ${c.investor_score}/100</p>
<p><strong>Why now:</strong> ${c.icp_reasoning}</p>
<p><a href="${c.airtable_url}">View in Airtable</a> |
<a href="${c.linkedin_url}">LinkedIn</a> |
<a href="${c.website}">Website</a></p>
</div>
`).join('')}
`;
return { subject: `Daily Digest: ${summary.icp1_count} High-Priority Prospects`, body: emailBody };
Why this approach:
Real-time Slack alerts ensure you never miss a hot prospect, while digests prevent notification fatigue. The digest format lets your team plan their day—they see the top 5 prospects to focus on rather than being overwhelmed by 50 notifications. This is like the difference between a fire alarm (immediate Slack alert for ICP-1) and a daily weather report (digest showing overall trends).
Workflow Architecture Overview
This workflow consists of 47 nodes organized into 6 main sections:
- Data ingestion (Nodes 1-8): Schedule trigger fetches Crunchbase data, filters by funding amount, deduplicates against Airtable, and initiates enrichment pipeline
- Enrichment layer (Nodes 9-24): Parallel branches for LinkedIn scraping, website analysis, careers page extraction, and team structure detection
- LLM classification (Nodes 25-32): OpenAI nodes analyze enriched data to determine finance team presence with confidence scoring
- Scoring engine (Nodes 33-39): Function nodes calculate founder quality, investor quality, and assign ICP tiers
- Storage and change detection (Nodes 40-43): Airtable updates with new scores, comparison logic detects meaningful changes
- Alert system (Nodes 44-47): Conditional routing sends Slack alerts for ICP-1, queues daily/weekly digests
Execution flow:
- Trigger: Daily at 6 AM (new prospects) + Weekly on Mondays (re-scans)
- Average run time: 45-90 seconds per company (depending on API response times)
- Key dependencies: Crunchbase data access, OpenAI API, Airtable base configured with proper tables
Critical nodes:
- HTTP Request (Crunchbase): Handles rate limiting with retry logic, 3 attempts with exponential backoff
- OpenAI (Finance Team Detection): Uses GPT-4 with JSON mode, temperature 0.2 for consistency
- Function (ICP Classification): Combines 8+ signals into final tier assignment with reasoning
- Airtable (System of Record): Stores all data with timestamps for change detection and audit trail
The complete n8n workflow JSON template is available at the bottom of this article.
Key Configuration Details
Critical Configuration Settings
OpenAI Integration
Required fields:
- API Key: Your OpenAI API key from platform.openai.com
- Model:
gpt-4-turbo-preview (or gpt-4 for better reasoning)
- Temperature: 0.2 (lower = more consistent classifications)
- Response format:
json_object (ensures structured output)
- Max tokens: 500 (sufficient for classification tasks)
Common issues:
- Using GPT-3.5 → Results in less accurate finance team detection (use GPT-4)
- Temperature > 0.5 → Inconsistent classifications for similar companies
- Not setting
response_format: json_object → Parsing errors when LLM returns text
Airtable Configuration
Required tables and fields:
| Table |
Key Fields |
Purpose |
| Companies |
company_name, domain, funding_total_usd, icp_tier, created_at |
Main prospect database |
| Enrichment_Data |
company_id (linked), linkedin_profiles, careers_data, confidence |
Raw enrichment results |
| Scores |
company_id (linked), founder_score, investor_score, reasoning |
Scoring calculations |
| Change_Log |
company_id (linked), change_type, detected_at, significance |
Historical tracking |
API settings:
- Rate limit: 5 requests/second (Airtable free tier)
- Batch size: 10 records per request (for bulk updates)
- Retry logic: 3 attempts with 2-second delays
Why this approach:
Separating enrichment data from scores into different tables keeps your main Companies table clean and queryable. The Change_Log table acts as an audit trail—you can see exactly when a company moved from ICP-2 to ICP-1 and why. This structure mirrors how a data warehouse separates fact tables (Companies) from dimension tables (Enrichment_Data, Scores).
Crunchbase Data Handling
CSV import method:
- Export daily funding announcements from Crunchbase Pro
- Upload CSV to Google Sheets
- Use Google Sheets node in n8n to read new rows
- Trigger workflow on new entries
API method (if available):
- Endpoint:
https://api.crunchbase.com/api/v4/searches/organizations
- Filter:
funding_total: {gte: 4000000} and last_funding_at: {gte: [yesterday]}
- Authentication: Bearer token in header
Rate limits:
- CSV method: No rate limits, but manual upload required
- API method: 200 requests/day on basic plan, 5000/day on enterprise
Scoring Customization
Variables to customize:
// Adjust these constants in Function nodes
const SCORING_WEIGHTS = {
founder_previous_exits: 15, // Points per previous exit (max 30)
founder_top_university: 20, // Points for elite education
founder_notable_company: 25, // Points for FAANG experience
founder_network_size: 25, // Max points for LinkedIn connections
investor_tier1: 40, // Points for Sequoia, a16z, etc.
investor_tier2: 25, // Points for First Round, NEA, etc.
investor_other: 10, // Points for other institutional VCs
funding_minimum: 4000000, // Minimum funding to process
funding_ideal: 10000000 // Funding amount for ICP-1 consideration
};
const ICP_THRESHOLDS = {
icp1_founder_min: 60, // Minimum founder score for ICP-1
icp1_investor_min: 50, // Minimum investor score for ICP-1
icp2_founder_min: 50, // Minimum founder score for ICP-2
icp2_investor_min: 40 // Minimum investor score for ICP-2
};
Why these numbers:
These weights reflect typical venture capital evaluation criteria. Previous exits are the strongest signal of founder quality (hence 15 points each), while education and experience provide supporting evidence. Tier-1 VCs get 40 points because their due diligence is rigorous—if Sequoia invested, the company likely has strong fundamentals. Adjust these based on your firm's specific ICP definition.
Testing & Validation
Component Testing Strategy
Test each workflow section independently before connecting them:
1. Data ingestion testing:
- Manually trigger workflow with 5 test companies from Crunchbase
- Verify deduplication logic catches existing companies
- Check Airtable records are created with all required fields
- Expected result: 5 new records in Companies table, no duplicates
2. Enrichment testing:
- Use a known company (e.g., a recent Series A startup)
- Verify LinkedIn data extraction returns founder profiles
- Check website scraping finds careers page
- Validate data quality: confidence scores should be "high" for companies with >10 employees
- Expected result: Enrichment_Data table populated with LinkedIn profiles, careers URLs, team size
3. LLM classification testing:
- Test with 3 companies: one with clear finance team, one without, one ambiguous
- Review LLM reasoning field for each classification
- Verify confidence scores match data quality (high confidence when LinkedIn shows CFO, low when data is sparse)
- Expected result: Accurate classifications with clear reasoning
4. Scoring validation:
- Calculate scores manually for 2 test companies
- Compare manual calculations to workflow output
- Verify ICP tier assignments match your expectations
- Expected result: Scores within 5 points of manual calculation, correct ICP tier
5. Alert testing:
- Create a test company that meets ICP-1 criteria
- Verify Slack message appears in correct channel
- Check email digest formatting and links
- Expected result: Slack alert within 60 seconds, properly formatted with working links
Common Issues and Fixes
| Issue |
Cause |
Solution |
| "OpenAI API rate limit exceeded" |
Too many concurrent requests |
Add 2-second delay between LLM calls using Wait node |
| "Airtable record not found" |
Deduplication logic failing |
Change unique key from company_name to domain |
| "LinkedIn scraping returns empty" |
Rate limiting or blocked IP |
Use residential proxy or LinkedIn API alternative |
| "Scores always return 0" |
Function node syntax error |
Check console logs, verify variable names match input data |
| "Slack alerts not sending" |
Webhook URL expired |
Regenerate webhook in Slack app settings |
Evaluation Metrics
Track these metrics to validate system performance:
// Function node: Calculate system metrics
const metrics = {
// Data quality
enrichment_success_rate: (successful_enrichments / total_companies) * 100,
avg_confidence_score: enrichment_data.reduce((sum, d) => sum + d.confidence_score, 0) / enrichment_data.length,
// Classification accuracy (requires manual review of sample)
finance_team_accuracy: correct_classifications / total_reviewed,
// Scoring distribution
icp1_percentage: (icp1_count / total_companies) * 100,
icp2_percentage: (icp2_count / total_companies) * 100,
// Alert effectiveness
alert_response_time: avg_time_to_first_contact_after_alert,
false_positive_rate: (incorrect_alerts / total_alerts) * 100
};
// Target benchmarks
const targets = {
enrichment_success_rate: 85, // 85% of companies successfully enriched
avg_confidence_score: 0.75, // Average confidence of 75%
finance_team_accuracy: 90, // 90% accurate classifications
icp1_percentage: 15, // 15% of companies are ICP-1 (adjust based on market)
alert_response_time: 24, // Contact within 24 hours of alert
false_positive_rate: 10 // <10% of alerts are not actually good fits
};
Manual review process:
- Weekly: Review 10 random ICP-1 classifications to verify accuracy
- Monthly: Audit scoring logic by manually calculating scores for 20 companies
- Quarterly: Survey sales team on alert quality and adjust thresholds
Deployment Considerations
Production Deployment Checklist
| Area |
Requirement |
Why It Matters |
| Error Handling |
Retry logic with exponential backoff on all API nodes |
Prevents data loss when LinkedIn or OpenAI APIs have temporary outages |
| Monitoring |
Webhook health checks every 5 minutes |
Detect workflow failures within 5 minutes vs discovering 3 days later when sales team asks why no alerts |
| Rate Limiting |
Implement delays between API calls (2-5 seconds) |
Avoid getting IP banned by LinkedIn or hitting OpenAI rate limits |
| Data Backup |
Daily Airtable exports to Google Drive |
Recover from accidental deletions or data corruption |
| Documentation |
Node-by-node comments explaining logic |
Reduces modification time from 4 hours to 30 minutes when adjusting scoring |
| Access Control |
Separate API keys for dev/prod environments |
Prevent test data from polluting production database |
| Cost Monitoring |
Track OpenAI API usage daily |
Catch runaway costs before $500 bill (GPT-4 is $0.03/1K tokens) |
| Version Control |
Export workflow JSON weekly |
Roll back to previous version if changes break system |
Error Handling Strategy
Implement these error handlers at critical points:
// Function node: Comprehensive error handler
try {
// Main workflow logic here
const result = await processCompany($json);
return result;
} catch (error) {
// Log error details
const errorLog = {
company_id: $json.id,
error_type: error.name,
error_message: error.message,
timestamp: new Date().toISOString(),
node_name: 'LinkedIn Enrichment',
input_data: JSON.stringify($json)
};
// Store in Airtable Error_Log table
await $('Airtable').create('Error_Log', errorLog);
// Send Slack alert for critical errors
if (error.name === 'APIRateLimitError' || error.name === 'AuthenticationError') {
await $('Slack').send({
channel: '#automation-alerts',
text: `🚨 Critical error in lead sourcing workflow: ${error.message}`
});
}
// Return partial data to continue workflow
return {
...($json),
enrichment_status: 'failed',
error: error.message,
requires_manual_review: true
};
}
Monitoring Setup
Create a separate monitoring workflow:
- Schedule Trigger: Every 5 minutes
- HTTP Request: Ping main workflow webhook to verify it's responsive
- Airtable Query: Check for companies stuck in "processing" status >30 minutes
- Function: Calculate key metrics (companies processed today, error rate, avg processing time)
- Slack: Send alert if any metric exceeds threshold
Performance Optimization
For high-volume processing (100+ companies/day):
// Function node: Batch processing
const companies = $input.all();
const batchSize = 10;
const batches = [];
for (let i = 0; i < companies.length; i += batchSize) {
batches.push(companies.slice(i, i + batchSize));
}
// Process batches with delays
for (const batch of batches) {
await processBatch(batch);
await sleep(5000); // 5-second delay between batches
}
Caching strategy:
- Cache LinkedIn profile data for 30 days (profiles don't change often)
- Cache investor scores indefinitely (Sequoia's quality doesn't change)
- Re-fetch website data weekly (careers pages update frequently)
Customization Ideas
Geographic filtering:
Add a Function node after Crunchbase import:
// Filter by geography
const targetRegions = ['San Francisco Bay Area', 'New York', 'Boston', 'Austin'];
const location = $json.location;
const isTargetRegion = targetRegions.some(region =>
location.toLowerCase().includes(region.toLowerCase())
);
if (!isTargetRegion) {
return { skip: true, reason: 'outside_target_geography' };
}
return { skip: false };
Subsector prioritization:
Modify ICP classification to boost scores for priority subsectors:
// Subsector boost
const prioritySubsectors = {
'robotics': 15, // +15 points
'climate tech': 12, // +12 points
'enterprise software': 10 // +10 points
};
const boost = prioritySubsectors[$json.subsector] || 0;
const adjustedScore = $json.total_score + boost;
Use Cases & Variations
Real-World Use Cases
Use Case 1: Venture Capital Firm Deal Sourcing
- Industry: Venture Capital
- Scale: 200 new companies/week across all funding stages
- Modifications needed:
- Add Series filter (only Series A and B)
- Score based on market size and competitive moat instead of finance team
- Integrate with Pitchbook for deeper company data
- Alert partners via email digest with investment memo template
Use Case 2: SaaS Sales Intelligence
- Industry: B2B SaaS selling to startups
- Scale: 500 companies/month, focus on Series A-C
- Modifications needed:
- Replace "finance team" detection with "uses competitor product" detection
- Add technographic data (current tech stack from BuiltWith or Clearbit)
- Score based on tech stack compatibility and growth rate
- Integrate with Salesforce to auto-create leads
Use Case 3: Recruiting Firm Candidate Sourcing
- Industry: Executive Search
- Scale: 50 high-growth companies/week
- Modifications needed:
- Focus on companies actively hiring (careers page analysis)
- Score based on funding velocity (raised 2+ rounds in 18 months)
- Extract hiring manager names from LinkedIn
- Alert recruiters when target companies post C-suite roles
Use Case 4: Market Research & Competitive Intelligence
- Industry: Strategy Consulting
- Scale: 1000+ companies tracked continuously
- Modifications needed:
- Add competitor tracking (monitor when competitors raise funding)
- Track product launches via website change detection
- Score based on market positioning and differentiation
- Generate weekly market landscape reports by subsector
Use Case 5: Partnership Development
- Industry: Enterprise Software Company
- Scale: 100 potential partners/quarter
- Modifications needed:
- Score based on complementary product fit
- Track integration partnerships (via press releases and blog scraping)
- Identify companies using similar tech stack
- Alert BD team when target companies announce new features
Customizations & Extensions
Alternative Integrations
Instead of Crunchbase:
- PitchBook: Best for deeper financial data and exit history - requires swapping HTTP Request node to PitchBook API, adding authentication header
- Dealroom: Better European startup coverage - change data source URL, adjust field mappings for different JSON structure
- AngelList: Use when targeting earlier-stage companies - API returns different funding data format, requires new parsing logic
Instead of Airtable:
- Notion Database: Better for team collaboration - use Notion API node, create linked databases for Companies/Enrichment/Scores
- PostgreSQL + Retool: Better for high-volume (10K+ companies) - replace Airtable nodes with Postgres nodes, build Retool interface for filtering
- Google Sheets: Simplest option for small teams - use Google Sheets node, but performance degrades above 5000 rows
Instead of OpenAI:
- Anthropic Claude: Better reasoning for complex classifications - swap OpenAI node for HTTP Request to Claude API, adjust prompt format
- Open-source LLMs (Llama 3): Best for cost control at scale - requires self-hosted inference server, add HTTP Request node to your endpoint
Workflow Extensions
Add automated reporting:
- Add a Schedule node to run every Monday at 8 AM
- Query Airtable for weekly metrics (new ICP-1 count, avg scores, top subsectors)
- Connect to Google Slides API to generate executive summary presentations
- Email PDF report to leadership team
- Nodes needed: +7 (Schedule, Airtable query, Function for calculations, HTTP Request to Slides API, PDF conversion, Email)
Scale to handle more data:
- Replace Google Sheets with PostgreSQL for Crunchbase data (handles 100K+ rows)
- Add batch processing: process 50 companies at a time instead of one-by-one
- Implement Redis caching layer for LinkedIn profiles (reduce API calls by 60%)
- Use n8n's Queue mode for parallel processing
- Performance improvement: 10x faster for >5000 companies (from 2 hours to 12 minutes)
Add outbound sequencing:
- Connect to Instantly.ai or Lemlist for email campaigns
- Auto-generate personalized first lines using GPT-4 based on company data
- Create separate sequences for ICP-1 (aggressive 5-touch) vs ICP-2 (gentle 3-touch)
- Track email opens and replies in Airtable
- Nodes needed: +12 (Instantly.ai API, GPT-4 for personalization, Airtable updates for tracking)
Integration possibilities:
| Add This |
To Get This |
Complexity |
| Clearbit Enrichment |
Technographic data (tech stack, employee count) |
Easy (3 nodes: HTTP Request, Function to parse, Airtable update) |
| BuiltWith API |
Detect competitor product usage |
Easy (2 nodes: HTTP Request, conditional logic) |
| HubSpot CRM |
Auto-create deals for ICP-1 prospects |
Medium (5 nodes: Airtable query, HubSpot create deal, custom properties mapping) |
| Zapier Webhooks |
Connect to 1000+ other tools |
Easy (1 node: Webhook trigger sends data to Zapier) |
| Meilisearch |
Fast full-text search across all companies |
Medium (8 nodes: Meilisearch index creation, document sync, search API) |
| Salesforce |
Enterprise CRM integration |
Hard (12 nodes: OAuth flow, field mapping, duplicate detection, opportunity creation) |
Advanced customization: Multi-stage scoring
Replace simple scoring with a funnel-based approach:
// Function node: Multi-stage scoring
const stages = {
awareness: {
weight: 0.2,
signals: ['website_traffic', 'social_media_presence', 'press_mentions']
},
consideration: {
weight: 0.3,
signals: ['funding_amount', 'investor_quality', 'team_size']
},
decision: {
weight: 0.5,
signals: ['no_finance_team', 'active_hiring', 'recent_funding']
}
};
let totalScore = 0;
for (const [stage, config] of Object.entries(stages)) {
const stageScore = config.signals.reduce((sum, signal) =>
sum + ($json[signal] || 0), 0
) / config.signals.length;
totalScore += stageScore * config.weight;
}
return {
total_score: Math.round(totalScore),
stage_scores: {
awareness: awarenessScore,
consideration: considerationScore,
decision: decisionScore
},
recommended_action: totalScore >= 70 ? 'immediate_outreach' :
totalScore >= 50 ? 'nurture_sequence' : 'monitor'
};
This approach weights signals differently based on where the prospect is in their buying journey. A company with recent funding and no finance team (decision stage signals) scores higher than one with great investors but a full finance team (consideration stage only).
Get Started Today
Ready to automate your startup prospecting?
- Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
- Import to n8n: Go to Workflows → Import from URL or File, paste the JSON
- Configure your services: Add your API credentials for Crunchbase, OpenAI, Airtable, and Slack
- Customize scoring logic: Adjust the founder/investor scoring weights in Function nodes to match your ICP
- Test with sample data: Run the workflow with 5 test companies to verify enrichment and classification
- Deploy to production: Set your schedule to daily at 6 AM and activate the workflow
Expected setup time: 2-4 hours for initial configuration, 1-2 hours for customization and testing.
Need help customizing this workflow for your specific needs? Schedule an intro call with Atherial at atherial.ai. We specialize in building custom automation systems for consulting firms, VCs, and B2B companies.
n8n Workflow JSON Template
{
"name": "AI Lead Sourcing & Scoring System",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 6 * * *"
}
]
}
},
"name
Complete N8N Workflow Template
Copy the JSON below and import it into your N8N instance via Workflows → Import from File
{
"name": "Startup Prospect Intelligence Pipeline",
"nodes": [
{
"id": "schedule-trigger-1",
"name": "Schedule Daily Scan",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
240,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 */6 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "http-crunchbase-1",
"name": "Fetch Crunchbase Prospects",
"type": "n8n-nodes-base.httpRequest",
"position": [
460,
300
],
"parameters": {
"url": "https://api.crunchbase.com/api/v4/searches/organizations",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "json",
"sendHeaders": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "field_ids",
"value": "[\"identifier\",\"name\",\"short_description\",\"website_url\",\"founded_on\",\"num_employees_enum\",\"last_funding_type\",\"funding_total\",\"investor_identifiers\",\"founder_identifiers\",\"categories\"]"
},
{
"name": "query",
"value": "[{\"type\":\"predicate\",\"field_id\":\"last_funding_at\",\"operator_id\":\"gte\",\"values\":[\"{{ $now.minus({days: 90}).toFormat('yyyy-MM-dd') }}\"]}]"
},
{
"name": "limit",
"value": "100"
}
]
},
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "X-cb-user-key",
"value": "={{ $credentials.apiKey }}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "code-normalize-1",
"name": "Normalize Crunchbase Data",
"type": "n8n-nodes-base.code",
"position": [
680,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Extract and normalize Crunchbase data\nconst items = $input.all();\nconst prospects = [];\n\nfor (const item of items) {\n const entities = item.json.entities || [];\n \n for (const entity of entities) {\n const properties = entity.properties || {};\n \n prospects.push({\n crunchbase_id: properties.identifier?.value || '',\n company_name: properties.name || '',\n description: properties.short_description || '',\n website: properties.website_url || '',\n founded_date: properties.founded_on?.value || '',\n employee_range: properties.num_employees_enum || '',\n last_funding_type: properties.last_funding_type || '',\n total_funding: properties.funding_total?.value_usd || 0,\n investors: properties.investor_identifiers || [],\n founders: properties.founder_identifiers || [],\n categories: properties.categories || [],\n last_updated: new Date().toISOString(),\n data_source: 'crunchbase'\n });\n }\n}\n\nreturn prospects.map(p => ({ json: p }));",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "http-scrape-1",
"name": "Scrape Company Website",
"type": "n8n-nodes-base.httpRequest",
"position": [
900,
300
],
"parameters": {
"url": "={{ $json.website }}",
"method": "GET",
"options": {
"timeout": 10000
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "code-extract-web-1",
"name": "Extract Website Content",
"type": "n8n-nodes-base.code",
"position": [
1120,
300
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Extract key information from website HTML\nconst html = $input.item.json.body || '';\nconst companyData = $input.item.json;\n\n// Simple text extraction from HTML\nconst textContent = html.replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim();\nconst excerpt = textContent.substring(0, 2000);\n\n// Extract key phrases for enrichment\nconst hasAI = /artificial intelligence|machine learning|AI|ML|deep learning/i.test(textContent);\nconst hasB2B = /B2B|enterprise|business software|SaaS/i.test(textContent);\nconst hasAPI = /API|integration|platform|developer/i.test(textContent);\n\nreturn {\n json: {\n ...companyData,\n website_content_excerpt: excerpt,\n has_ai_focus: hasAI,\n has_b2b_focus: hasB2B,\n has_api_product: hasAPI,\n content_length: textContent.length\n }\n};",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "http-linkedin-1",
"name": "Enrich from LinkedIn",
"type": "n8n-nodes-base.httpRequest",
"position": [
1340,
300
],
"parameters": {
"url": "https://www.linkedin.com/search/results/companies/?keywords={{ encodeURIComponent($json.company_name) }}",
"method": "GET",
"options": {
"timeout": 8000
}
},
"typeVersion": 4.2,
"continueOnFail": true
},
{
"id": "code-prepare-llm-1",
"name": "Prepare LLM Classification",
"type": "n8n-nodes-base.code",
"position": [
1560,
300
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Prepare data for LLM classification\nconst prospect = $input.item.json;\n\nconst promptData = {\n company_name: prospect.company_name,\n description: prospect.description,\n website_excerpt: prospect.website_content_excerpt || '',\n founded_date: prospect.founded_date,\n employee_range: prospect.employee_range,\n funding_type: prospect.last_funding_type,\n total_funding_usd: prospect.total_funding,\n categories: (prospect.categories || []).join(', '),\n investors: (prospect.investors || []).slice(0, 5).join(', '),\n has_ai_focus: prospect.has_ai_focus || false,\n has_b2b_focus: prospect.has_b2b_focus || false\n};\n\nconst prompt = `Analyze this startup and provide a detailed ICP (Ideal Customer Profile) classification:\n\nCompany: ${promptData.company_name}\nDescription: ${promptData.description}\nWebsite Content: ${promptData.website_excerpt.substring(0, 500)}\nFounded: ${promptData.founded_date}\nEmployees: ${promptData.employee_range}\nFunding Stage: ${promptData.funding_type}\nTotal Funding: $${promptData.total_funding_usd}\nCategories: ${promptData.categories}\nTop Investors: ${promptData.investors}\nAI Focus: ${promptData.has_ai_focus}\nB2B Focus: ${promptData.has_b2b_focus}\n\nProvide a structured JSON response with the following fields:\n- icp_tier: \"Tier 1\" (perfect fit), \"Tier 2\" (strong fit), \"Tier 3\" (moderate fit), or \"Not Qualified\"\n- icp_score: number from 0-100\n- product_maturity: \"Early\", \"Growth\", or \"Mature\"\n- tech_stack_signals: array of detected technologies\n- buying_committee_signals: array of decision maker signals\n- pain_points: array of 3-5 likely pain points\n- engagement_triggers: array of 3-5 reasons to reach out now\n- founder_quality_score: number from 0-10\n- investor_quality_score: number from 0-10\n- market_opportunity_score: number from 0-10\n- urgency_score: number from 0-10\n- recommended_action: \"Immediate outreach\", \"Schedule follow-up\", \"Monitor\", or \"Disqualify\"\n- reasoning: brief explanation of classification\n`;\n\nreturn {\n json: {\n ...prospect,\n llm_prompt: prompt\n }\n};",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "structured-output-1",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
1780,
180
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"icp_tier\": { \"type\": \"string\", \"enum\": [\"Tier 1\", \"Tier 2\", \"Tier 3\", \"Not Qualified\"] },\n \"icp_score\": { \"type\": \"number\" },\n \"product_maturity\": { \"type\": \"string\", \"enum\": [\"Early\", \"Growth\", \"Mature\"] },\n \"tech_stack_signals\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"buying_committee_signals\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"pain_points\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"engagement_triggers\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"founder_quality_score\": { \"type\": \"number\" },\n \"investor_quality_score\": { \"type\": \"number\" },\n \"market_opportunity_score\": { \"type\": \"number\" },\n \"urgency_score\": { \"type\": \"number\" },\n \"recommended_action\": { \"type\": \"string\" },\n \"reasoning\": { \"type\": \"string\" }\n },\n \"required\": [\"icp_tier\", \"icp_score\", \"recommended_action\"]\n}"
},
"typeVersion": 1.3
},
{
"id": "openai-model-1",
"name": "OpenAI GPT-4o-mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1780,
380
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {
"maxTokens": 2000,
"temperature": 0.3
}
},
"typeVersion": 1.3
},
{
"id": "llm-chain-1",
"name": "ICP Classification Chain",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1780,
300
],
"parameters": {
"prompt": "={{ $json.llm_prompt }}",
"options": {}
},
"typeVersion": 1.4
},
{
"id": "code-merge-1",
"name": "Merge Classification Results",
"type": "n8n-nodes-base.code",
"position": [
2000,
300
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Merge LLM classification with prospect data\nconst prospectData = $input.first().json;\nconst llmResult = $input.item.json;\n\n// Parse LLM output if it's a string\nlet classification = {};\nif (typeof llmResult.output === 'string') {\n try {\n classification = JSON.parse(llmResult.output);\n } catch (e) {\n classification = llmResult;\n }\n} else {\n classification = llmResult.output || llmResult;\n}\n\nreturn {\n json: {\n // Original prospect data\n crunchbase_id: prospectData.crunchbase_id,\n company_name: prospectData.company_name,\n description: prospectData.description,\n website: prospectData.website,\n founded_date: prospectData.founded_date,\n employee_range: prospectData.employee_range,\n last_funding_type: prospectData.last_funding_type,\n total_funding: prospectData.total_funding,\n categories: (prospectData.categories || []).join(', '),\n \n // Enrichment data\n has_ai_focus: prospectData.has_ai_focus,\n has_b2b_focus: prospectData.has_b2b_focus,\n has_api_product: prospectData.has_api_product,\n \n // LLM Classification\n icp_tier: classification.icp_tier || 'Not Qualified',\n icp_score: classification.icp_score || 0,\n product_maturity: classification.product_maturity || 'Unknown',\n tech_stack_signals: (classification.tech_stack_signals || []).join('; '),\n buying_committee_signals: (classification.buying_committee_signals || []).join('; '),\n pain_points: (classification.pain_points || []).join('; '),\n engagement_triggers: (classification.engagement_triggers || []).join('; '),\n founder_quality_score: classification.founder_quality_score || 0,\n investor_quality_score: classification.investor_quality_score || 0,\n market_opportunity_score: classification.market_opportunity_score || 0,\n urgency_score: classification.urgency_score || 0,\n recommended_action: classification.recommended_action || 'Monitor',\n classification_reasoning: classification.reasoning || '',\n \n // Metadata\n last_analyzed: new Date().toISOString(),\n analysis_version: 'v1.0'\n }\n};",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "airtable-search-1",
"name": "Check Existing in Airtable",
"type": "n8n-nodes-base.airtable",
"position": [
2220,
300
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblProspects"
},
"options": {},
"operation": "search",
"authentication": "airtableTokenApi",
"filterByFormula": "={Crunchbase ID}=\"{{ $json.crunchbase_id }}\""
},
"typeVersion": 2.1
},
{
"id": "if-exists-1",
"name": "Record Exists?",
"type": "n8n-nodes-base.if",
"position": [
2440,
300
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "condition-1",
"operator": {
"type": "string",
"operation": "exists"
},
"leftValue": "={{ $json.id }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "airtable-update-1",
"name": "Update Existing Record",
"type": "n8n-nodes-base.airtable",
"position": [
2660,
200
],
"parameters": {
"id": "={{ $('Check Existing in Airtable').item.json.id }}",
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblProspects"
},
"columns": {
"value": {
"Status": "Re-analyzed",
"ICP Tier": "={{ $('Merge Classification Results').item.json.icp_tier }}",
"ICP Score": "={{ $('Merge Classification Results').item.json.icp_score }}",
"Pain Points": "={{ $('Merge Classification Results').item.json.pain_points }}",
"Last Analyzed": "={{ $('Merge Classification Results').item.json.last_analyzed }}",
"Urgency Score": "={{ $('Merge Classification Results').item.json.urgency_score }}",
"Product Maturity": "={{ $('Merge Classification Results').item.json.product_maturity }}",
"Recommended Action": "={{ $('Merge Classification Results').item.json.recommended_action }}",
"Engagement Triggers": "={{ $('Merge Classification Results').item.json.engagement_triggers }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "update",
"authentication": "airtableTokenApi"
},
"typeVersion": 2.1
},
{
"id": "airtable-create-1",
"name": "Create New Record",
"type": "n8n-nodes-base.airtable",
"position": [
2660,
400
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "appXXXXXXXXXXXXXX"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tblProspects"
},
"columns": {
"value": {
"Status": "New",
"Website": "={{ $('Merge Classification Results').item.json.website }}",
"ICP Tier": "={{ $('Merge Classification Results').item.json.icp_tier }}",
"ICP Score": "={{ $('Merge Classification Results').item.json.icp_score }}",
"Categories": "={{ $('Merge Classification Results').item.json.categories }}",
"Description": "={{ $('Merge Classification Results').item.json.description }}",
"Pain Points": "={{ $('Merge Classification Results').item.json.pain_points }}",
"Company Name": "={{ $('Merge Classification Results').item.json.company_name }}",
"Founded Date": "={{ $('Merge Classification Results').item.json.founded_date }}",
"Crunchbase ID": "={{ $('Merge Classification Results').item.json.crunchbase_id }}",
"Last Analyzed": "={{ $('Merge Classification Results').item.json.last_analyzed }}",
"Total Funding": "={{ $('Merge Classification Results').item.json.total_funding }}",
"Urgency Score": "={{ $('Merge Classification Results').item.json.urgency_score }}",
"Employee Range": "={{ $('Merge Classification Results').item.json.employee_range }}",
"Product Maturity": "={{ $('Merge Classification Results').item.json.product_maturity }}",
"Last Funding Type": "={{ $('Merge Classification Results').item.json.last_funding_type }}",
"Recommended Action": "={{ $('Merge Classification Results').item.json.recommended_action }}",
"Tech Stack Signals": "={{ $('Merge Classification Results').item.json.tech_stack_signals }}",
"Engagement Triggers": "={{ $('Merge Classification Results').item.json.engagement_triggers }}",
"Founder Quality Score": "={{ $('Merge Classification Results').item.json.founder_quality_score }}",
"Investor Quality Score": "={{ $('Merge Classification Results').item.json.investor_quality_score }}",
"Buying Committee Signals": "={{ $('Merge Classification Results').item.json.buying_committee_signals }}",
"Classification Reasoning": "={{ $('Merge Classification Results').item.json.classification_reasoning }}",
"Market Opportunity Score": "={{ $('Merge Classification Results').item.json.market_opportunity_score }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "create",
"authentication": "airtableTokenApi"
},
"typeVersion": 2.1
},
{
"id": "if-tier1-1",
"name": "Is Tier 1 Prospect?",
"type": "n8n-nodes-base.if",
"position": [
2880,
300
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "condition-tier1",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.icp_tier }}",
"rightValue": "Tier 1"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "slack-tier1-1",
"name": "Alert Tier 1 to Slack",
"type": "n8n-nodes-base.slack",
"position": [
3100,
200
],
"parameters": {
"text": "🚀 *NEW TIER 1 PROSPECT DETECTED*\n\n*Company:* {{ $json.company_name }}\n*Website:* {{ $json.website }}\n*ICP Score:* {{ $json.icp_score }}/100\n\n*Funding:* ${{ $json.total_funding }} ({{ $json.last_funding_type }})\n*Employees:* {{ $json.employee_range }}\n\n*Key Signals:*\n• {{ $json.engagement_triggers }}\n\n*Pain Points:*\n{{ $json.pain_points }}\n\n*Urgency Score:* {{ $json.urgency_score }}/10\n*Recommended Action:* {{ $json.recommended_action }}\n\n*Reasoning:*\n{{ $json.classification_reasoning }}",
"select": "channel",
"resource": "message",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C01234ABCDE"
},
"operation": "post",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
},
{
"id": "code-digest-1",
"name": "Generate Daily Digest",
"type": "n8n-nodes-base.code",
"position": [
3100,
400
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "// Aggregate all prospects into tiers for daily digest\nconst items = $input.all();\nconst tiers = {\n 'Tier 1': [],\n 'Tier 2': [],\n 'Tier 3': []\n};\n\nfor (const item of items) {\n const tier = item.json.icp_tier;\n if (tiers[tier]) {\n tiers[tier].push(item.json);\n }\n}\n\nconst tier1Count = tiers['Tier 1'].length;\nconst tier2Count = tiers['Tier 2'].length;\nconst tier3Count = tiers['Tier 3'].length;\n\n// Create digest summary\nconst tier1List = tiers['Tier 1']\n .map(p => `• ${p.company_name} (Score: ${p.icp_score}) - ${p.recommended_action}`)\n .join('\\n');\n\nconst tier2List = tiers['Tier 2']\n .slice(0, 10)\n .map(p => `• ${p.company_name} (Score: ${p.icp_score})`)\n .join('\\n');\n\nconst htmlContent = `\n<html>\n<head>\n <style>\n body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }\n .header { background: #0066cc; color: white; padding: 20px; }\n .section { margin: 20px 0; padding: 15px; border-left: 4px solid #0066cc; }\n .tier1 { background: #e6f3ff; }\n .tier2 { background: #f0f0f0; }\n .stats { display: flex; gap: 20px; margin: 20px 0; }\n .stat-box { padding: 15px; background: white; border: 1px solid #ddd; flex: 1; text-align: center; }\n </style>\n</head>\n<body>\n <div class=\"header\">\n <h1>🎯 Daily Prospect Intelligence Digest</h1>\n <p>Automated scan completed on ${new Date().toLocaleDateString()}</p>\n </div>\n \n <div class=\"stats\">\n <div class=\"stat-box\">\n <h2>${tier1Count}</h2>\n <p>Tier 1 Prospects</p>\n </div>\n <div class=\"stat-box\">\n <h2>${tier2Count}</h2>\n <p>Tier 2 Prospects</p>\n </div>\n <div class=\"stat-box\">\n <h2>${tier3Count}</h2>\n <p>Tier 3 Prospects</p>\n </div>\n </div>\n \n <div class=\"section tier1\">\n <h2>🚀 Tier 1 Prospects (Immediate Action Required)</h2>\n <pre>${tier1List || 'No Tier 1 prospects found'}</pre>\n </div>\n \n <div class=\"section tier2\">\n <h2>⭐ Top Tier 2 Prospects (Schedule Follow-up)</h2>\n <pre>${tier2List || 'No Tier 2 prospects found'}</pre>\n </div>\n \n <p style=\"margin-top: 40px; color: #666; font-size: 12px;\">\n View full details in Airtable. This is an automated report from the Prospect Intelligence Pipeline.\n </p>\n</body>\n</html>\n`;\n\nreturn [{\n json: {\n tier1_count: tier1Count,\n tier2_count: tier2Count,\n tier3_count: tier3Count,\n html_digest: htmlContent,\n total_analyzed: items.length\n }\n}];",
"language": "javaScript"
},
"typeVersion": 2
},
{
"id": "email-digest-1",
"name": "Send Daily Digest Email",
"type": "n8n-nodes-base.emailSend",
"position": [
3320,
400
],
"parameters": {
"message": "={{ $json.html_digest }}",
"options": {},
"subject": "Daily Prospect Intelligence Digest - {{ $now.toFormat('MMM dd, yyyy') }}",
"toEmail": "team@yourcompany.com",
"emailType": "html",
"fromEmail": "intelligence@yourcompany.com",
"operation": "send"
},
"typeVersion": 2.1
},
{
"id": "slack-summary-1",
"name": "Post Summary to Slack",
"type": "n8n-nodes-base.slack",
"position": [
3540,
400
],
"parameters": {
"text": "📊 *Daily Prospect Scan Complete*\n\n*Total Analyzed:* {{ $json.total_analyzed }}\n*Tier 1:* {{ $json.tier1_count }} prospects\n*Tier 2:* {{ $json.tier2_count }} prospects\n*Tier 3:* {{ $json.tier3_count }} prospects\n\nFull digest sent via email. Check Airtable for details.",
"select": "channel",
"resource": "message",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C01234ABCDE"
},
"operation": "post",
"otherOptions": {},
"authentication": "oAuth2"
},
"typeVersion": 2.3
}
],
"pinData": {},
"connections": {
"Record Exists?": {
"main": [
[
{
"node": "Update Existing Record",
"type": "main",
"index": 0
}
],
[
{
"node": "Create New Record",
"type": "main",
"index": 0
}
]
]
},
"Create New Record": {
"main": [
[
{
"node": "Is Tier 1 Prospect?",
"type": "main",
"index": 0
}
]
]
},
"OpenAI GPT-4o-mini": {
"ai_languageModel": [
[
{
"node": "ICP Classification Chain",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Is Tier 1 Prospect?": {
"main": [
[
{
"node": "Alert Tier 1 to Slack",
"type": "main",
"index": 0
},
{
"node": "Generate Daily Digest",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Schedule Daily Scan": {
"main": [
[
{
"node": "Fetch Crunchbase Prospects",
"type": "main",
"index": 0
}
]
]
},
"Enrich from LinkedIn": {
"main": [
[
{
"node": "Prepare LLM Classification",
"type": "main",
"index": 0
}
]
]
},
"Alert Tier 1 to Slack": {
"main": [
[
{
"node": "Generate Daily Digest",
"type": "main",
"index": 0
}
]
]
},
"Generate Daily Digest": {
"main": [
[
{
"node": "Send Daily Digest Email",
"type": "main",
"index": 0
}
]
]
},
"Scrape Company Website": {
"main": [
[
{
"node": "Extract Website Content",
"type": "main",
"index": 0
}
]
]
},
"Update Existing Record": {
"main": [
[
{
"node": "Is Tier 1 Prospect?",
"type": "main",
"index": 0
}
]
]
},
"Extract Website Content": {
"main": [
[
{
"node": "Enrich from LinkedIn",
"type": "main",
"index": 0
}
]
]
},
"Send Daily Digest Email": {
"main": [
[
{
"node": "Post Summary to Slack",
"type": "main",
"index": 0
}
]
]
},
"ICP Classification Chain": {
"main": [
[
{
"node": "Merge Classification Results",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "ICP Classification Chain",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Normalize Crunchbase Data": {
"main": [
[
{
"node": "Scrape Company Website",
"type": "main",
"index": 0
}
]
]
},
"Check Existing in Airtable": {
"main": [
[
{
"node": "Record Exists?",
"type": "main",
"index": 0
}
]
]
},
"Fetch Crunchbase Prospects": {
"main": [
[
{
"node": "Normalize Crunchbase Data",
"type": "main",
"index": 0
}
]
]
},
"Prepare LLM Classification": {
"main": [
[
{
"node": "ICP Classification Chain",
"type": "main",
"index": 0
}
]
]
},
"Merge Classification Results": {
"main": [
[
{
"node": "Check Existing in Airtable",
"type": "main",
"index": 0
}
]
]
}
}
}