Lead Scoring
Quick Access: Automatically score leads based on engagement behavior, demographics, and custom criteria to identify your hottest prospects.
Overview
Lead Scoring assigns numeric values to contacts based on their actions, characteristics, and engagement patterns. Focus your efforts on high-value leads and personalize outreach based on interest level.
Key Benefits
-
Prioritization: Focus on highest-potential leads
-
Automation: Auto-score based on behavior and attributes
-
Segmentation: Create score-based segments
-
Sales Alignment: Pass qualified leads to sales at threshold
-
Personalization: Tailor messaging by score range
Level 1: Quick Start Guide
Understanding Lead Scores
Score Range: 0-100
-
0-25: Cold/Unengaged
-
26-50: Warming Up
-
51-75: Interested/Engaged
-
76-100: Hot/Ready to Buy
Recalculation: Real-time as contacts take actions
Your First Scoring Model
Default Scoring Rules
Email Engagement:
รขลโ Email Opened: +5 points
รขลโ Link Clicked: +10 points
รขลโ Replied to Email: +15 points
Negative Actions:
รขลโ Email Bounced: -10 points
รขลโ Marked as Spam: -50 points
รขลโ Unsubscribed: -100 points (auto-set to 0)
Demographics:
รขลโ Job Title (Decision Maker): +20 points
รขลโ Company Size (51-200): +10 points
รขลโ Company Size (200+): +15 points
Time Decay:
- Score decreases 5% every 30 days of inactivity
View Lead Scores
Contacts โ View All
Sort by: Lead Score (High to Low)
Contact Score Last Activity
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Sarah Johnson 92 2 hours ago
Michael Chen 87 1 day ago
Emily Rodriguez 76 3 days ago
David Kim 68 1 week ago
...
Use Scores in Campaigns
Score-Based Segments
Hot Leads (Score 76-100):
Campaign: "Book a Demo" (Aggressive CTA)
Warm Leads (Score 51-75):
Campaign: "Feature Deep Dive" (Educational)
Cold Leads (Score 0-50):
Campaign: "Getting Started Guide" (Nurture)
Level 2: Advanced Scoring Configuration
Custom Scoring Rules
Behavioral Scoring
Engagement Actions
email_actions:
opened_email:
points: 5
decay: true # Points decay over time
clicked_link:
points: 10
multiplier: 1.5 # 15 points if clicked multiple times
clicked_pricing_page:
points: 25
description: "High intent action"
downloaded_resource:
points: 15
specific_resources:
whitepaper: 20
case_study: 15
ebook: 10
watched_demo_video:
points: 30
threshold: 75% # Must watch 75% to count
replied_to_email:
points: 20
forwarded_email:
points: 12
Website Activity (if integrated)
website_actions:
visited_pricing_page:
points: 20
visited_features_page:
points: 10
started_trial:
points: 50
requested_demo:
points: 60
added_to_cart:
points: 40
time_on_site:
gt_5_minutes: 5
gt_15_minutes: 10
Negative Actions
negative_actions:
unsubscribed:
points: -100
set_score_to: 0
marked_spam:
points: -50
email_bounced_hard:
points: -25
set_score_to: 0
email_bounced_soft:
points: -5
inactive_90_days:
points: -30
inactive_180_days:
points: -50
Demographic Scoring
Firmographic Data
company_attributes:
company_size:
1-10: 0
11-50: 5
51-200: 10
201-1000: 15
1001+: 20
industry:
saas: 20
technology: 15
ecommerce: 15
healthcare: 10
finance: 10
other: 0
revenue:
lt_1m: 0
1m_10m: 10
10m_50m: 15
50m_plus: 20
Role-Based Scoring
job_title_keywords:
decision_makers:
ceo: 25
cto: 25
vp: 20
director: 15
head_of: 15
influencers:
manager: 10
lead: 8
senior: 8
end_users:
specialist: 3
coordinator: 3
analyst: 5
Geographic Scoring
location:
tier_1_markets: # US, UK, Canada, Australia
points: 10
tier_2_markets: # Western Europe
points: 5
tier_3_markets: # Rest of world
points: 0
Score Decay & Recency
Time-Based Decay
decay_rules:
engagement_decay:
enabled: true
rate: 5_percent
interval: 30_days
min_score: 0
example:
initial_score: 80
after_30_days: 76 # -5%
after_60_days: 72 # -5% again
after_90_days: 68
Recency Boosting
recency_multipliers:
action_within_24h:
multiplier: 2.0
action_within_7d:
multiplier: 1.5
action_within_30d:
multiplier: 1.0
action_older_than_30d:
multiplier: 0.5
Multi-Dimensional Scoring
Separate Scores for Different Aspects
scoring_dimensions:
engagement_score: # 0-40 points
email_opens: 5
email_clicks: 10
email_replies: 15
fit_score: # 0-40 points
company_size: 15
industry: 15
job_title: 10
intent_score: # 0-20 points
pricing_page_visit: 10
demo_request: 20
trial_started: 20
composite_score: # Total 0-100
formula: engagement + fit + intent
Score-Based Automation
Auto-Segmentation
When lead score reaches 75:
โ Add to "Hot Leads" segment
โ Trigger "Sales Qualified Lead" workflow
โ Notify sales team
โ Send "Book a Demo" campaign
Lead Lifecycle Stages
Score 0-25: Status = "Cold Lead"
Score 26-50: Status = "Nurture"
Score 51-75: Status = "Marketing Qualified Lead (MQL)"
Score 76-100: Status = "Sales Qualified Lead (SQL)"
CRM Sync
When lead score >= 75:
โ Create lead in Salesforce
โ Assign to sales rep (round-robin)
โ Set priority = "High"
โ Add to sales follow-up queue
Score Analytics
Score Distribution:
Lead Score Distribution:
0-25: รขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโห 45% (2,250 contacts)
26-50: รขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโหรขโห 28% (1,400 contacts)
51-75: รขโหรขโหรขโหรขโหรขโหรขโห 18% (900 contacts)
76-100: รขโหรขโหรขโห 9% (450 contacts)
Average Score: 38
Median Score: 32
Score Trends:
Score Movement (Last 30 Days):
Increased Score: 1,200 contacts (+15%)
Decreased Score: 800 contacts (-10%)
No Change: 3,000 contacts
Top Scoring Actions:
1. Demo Requested: +60 pts (120 actions)
2. Pricing Page Click: +25 pts (450 actions)
3. Email Reply: +20 pts (230 actions)
Level 3: Technical Implementation
Database Schema
-- Lead scoring configuration
CREATE TABLE lead_scoring_models (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL REFERENCES tenants(id),
name VARCHAR(255) NOT NULL,
description TEXT,
-- Scoring rules (JSON configuration)
scoring_rules JSONB NOT NULL,
-- Decay settings
decay_enabled BOOLEAN DEFAULT true,
decay_rate DECIMAL(5,2) DEFAULT 5.0, -- Percentage
decay_interval_days INTEGER DEFAULT 30,
-- Status
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Contact scores
CREATE TABLE contact_scores (
id UUID PRIMARY KEY,
contact_id UUID NOT NULL REFERENCES contacts(id),
scoring_model_id UUID REFERENCES lead_scoring_models(id),
-- Scores
total_score INTEGER DEFAULT 0,
engagement_score INTEGER DEFAULT 0,
fit_score INTEGER DEFAULT 0,
intent_score INTEGER DEFAULT 0,
-- Metadata
last_calculated_at TIMESTAMP DEFAULT NOW(),
last_activity_at TIMESTAMP,
previous_score INTEGER DEFAULT 0, -- For tracking changes
UNIQUE(contact_id, scoring_model_id)
);
CREATE INDEX idx_contact_scores_score ON contact_scores(total_score DESC);
CREATE INDEX idx_contact_scores_contact ON contact_scores(contact_id);
-- Score history (for analytics)
CREATE TABLE score_events (
id UUID PRIMARY KEY,
contact_id UUID NOT NULL REFERENCES contacts(id),
-- Event details
event_type VARCHAR(100), -- email_opened, clicked_link, etc.
event_data JSONB,
-- Score change
points_added INTEGER,
score_before INTEGER,
score_after INTEGER,
-- Timestamp
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_score_events_contact ON score_events(contact_id, created_at);
CREATE INDEX idx_score_events_type ON score_events(event_type);
Scoring Engine
interface ScoringRule {
action: string;
points: number;
conditions?: Record<string, any>;
multiplier?: number;
decays?: boolean;
}
interface ScoringModel {
id: string;
name: string;
rules: ScoringRule[];
decayEnabled: boolean;
decayRate: number;
decayIntervalDays: number;
}
class LeadScoringEngine {
async scoreAction(
contactId: string,
action: string,
metadata: Record<string, any> = {}
): Promise<number> {
const contact = await db.contacts.findById(contactId);
const model = await this.getScoringModel(contact.tenantId);
const currentScore = await this.getCurrentScore(contactId);
// Find matching rule
const rule = model.rules.find(r => r.action === action);
if (!rule) {
return currentScore.totalScore;
}
// Calculate points
let points = rule.points;
// Apply conditions
if (rule.conditions) {
const meetsConditions = this.evaluateConditions(rule.conditions, metadata);
if (!meetsConditions) {
return currentScore.totalScore;
}
}
// Apply multiplier for repeated actions
if (rule.multiplier) {
const recentActions = await this.countRecentActions(contactId, action, 30);
if (recentActions > 1) {
points *= rule.multiplier;
}
}
// Apply recency boost
points = this.applyRecencyBoost(points, new Date());
// Calculate new score
const newScore = Math.max(0, Math.min(100, currentScore.totalScore + points));
// Update score
await this.updateScore(contactId, newScore, action, points);
// Log event
await db.scoreEvents.create({
contactId,
eventType: action,
eventData: metadata,
pointsAdded: points,
scoreBefore: currentScore.totalScore,
scoreAfter: newScore,
});
// Check for automation triggers
await this.checkAutomationTriggers(contactId, currentScore.totalScore, newScore);
return newScore;
}
private applyRecencyBoost(basePoints: number, actionDate: Date): number {
const hoursSinceAction = differenceInHours(new Date(), actionDate);
if (hoursSinceAction < 24) {
return basePoints * 2.0;
} else if (hoursSinceAction < 168) { // 7 days
return basePoints * 1.5;
} else if (hoursSinceAction < 720) { // 30 days
return basePoints * 1.0;
} else {
return basePoints * 0.5;
}
}
async applyDecay(contactId: string): Promise<void> {
const contact = await db.contacts.findById(contactId);
const model = await this.getScoringModel(contact.tenantId);
if (!model.decayEnabled) {
return;
}
const currentScore = await this.getCurrentScore(contactId);
const daysSinceLastActivity = differenceInDays(
new Date(),
currentScore.lastActivityAt
);
if (daysSinceLastActivity < model.decayIntervalDays) {
return;
}
// Calculate number of decay periods
const decayPeriods = Math.floor(daysSinceLastActivity / model.decayIntervalDays);
// Apply exponential decay
let newScore = currentScore.totalScore;
for (let i = 0; i < decayPeriods; i++) {
newScore = newScore * (1 - model.decayRate / 100);
}
newScore = Math.max(0, Math.floor(newScore));
if (newScore !== currentScore.totalScore) {
await this.updateScore(
contactId,
newScore,
'score_decay',
newScore - currentScore.totalScore
);
}
}
private async checkAutomationTriggers(
contactId: string,
oldScore: number,
newScore: number
): Promise<void> {
// Check if score crossed threshold
const thresholds = [25, 50, 75];
for (const threshold of thresholds) {
if (oldScore < threshold && newScore >= threshold) {
await this.triggerScoreAutomation(contactId, threshold);
}
}
}
private async triggerScoreAutomation(
contactId: string,
threshold: number
): Promise<void> {
switch (threshold) {
case 75:
// Sales Qualified Lead
await this.markAsSQLead(contactId);
await this.notifySalesTeam(contactId);
await this.addToSegment(contactId, 'hot-leads');
break;
case 50:
// Marketing Qualified Lead
await this.markAsMQLead(contactId);
await this.addToSegment(contactId, 'warm-leads');
break;
case 25:
// Warming up
await this.addToSegment(contactId, 'nurture-leads');
break;
}
}
}
Background Jobs
// Apply decay to all contacts daily
cron.schedule('0 3 * * *', async () => { // 3 AM daily
const contacts = await db.contacts.findAll({
where: {
isActive: true,
},
});
const scoringEngine = new LeadScoringEngine();
for (const contact of contacts) {
await scoringQueue.add('apply-decay', {
contactId: contact.id,
});
}
});
// Queue worker
scoringQueue.process('apply-decay', async (job) => {
const { contactId } = job.data;
const engine = new LeadScoringEngine();
await engine.applyDecay(contactId);
});
// Recalculate scores for contacts with new custom field data
async function recalculateScoresForContact(contactId: string): Promise<void> {
const engine = new LeadScoringEngine();
// Recalculate fit score based on demographics
const contact = await db.contacts.findById(contactId);
const fitScore = await engine.calculateFitScore(contact);
await db.contactScores.update(
{ where: { contactId } },
{ fitScore }
);
}
Event Listeners
// Listen for email events and update scores
eventEmitter.on('email.opened', async (event) => {
const engine = new LeadScoringEngine();
await engine.scoreAction(event.contactId, 'email_opened', {
emailId: event.emailId,
campaignId: event.campaignId,
});
});
eventEmitter.on('email.clicked', async (event) => {
const engine = new LeadScoringEngine();
// Check if clicked URL is high-intent (pricing, demo)
const isHighIntent = event.url.includes('/pricing') ||
event.url.includes('/demo');
const action = isHighIntent ? 'clicked_high_intent_link' : 'clicked_link';
await engine.scoreAction(event.contactId, action, {
url: event.url,
emailId: event.emailId,
});
});
eventEmitter.on('contact.updated', async (event) => {
// Recalculate fit score when demographics change
await recalculateScoresForContact(event.contactId);
});
Related Documentation
-
Leads Management - Contact database
-
Contact Segmentation - Score-based segments
-
Campaign Management - Score-based targeting
-
Analytics - Score analytics
Last Updated: November 25, 2025 Status: Planned - High Priority (Level 2) Target Release: Q1 2026 Owner: Leads Team