Sales teams hate CRMs full of email addresses and nothing else.
"Who is this person? What company? Where are they located? Should I call them or is it 3am there?"
The data exists. It's just not in your system yet. And every hour your sales team spends manually researching leads is an hour they're not selling.
This is what lead enrichment solves. Someone signs up with just an email, and by the time the page finishes loading, you know their name, company, location, social profiles, and whether they're worth calling.
Let me show you how to build this.
What's Actually Possible
From a single email address and IP (which you already have from the signup request), you can automatically discover:
- Full name — From their Gravatar profile
- Profile photo — Also Gravatar
- Company name — From the email domain (jane@acme.com = Acme)
- Job title and bio — Gravatar profiles often include this
- Social profiles — LinkedIn, Twitter, GitHub links
- Physical location — From their IP address
- Timezone — So you know when to call
Not everyone has all this data public, but a surprising number of tech and business professionals do. And those are exactly the leads you want.
The "Aha" Moment
Here's what happens when you add lead enrichment to a signup flow. Before, the sales dashboard shows:
New Lead: jane.doe@company.com
Location: Unknown
Company: Unknown
After:
New Lead: Jane Doe
Company: Company Inc (Software, 50-200 employees)
Location: Austin, TX (CST)
LinkedIn: linkedin.com/in/janedoe
Photo: [actual headshot]
Best time to call: 9am-5pm CST (currently 10:30am)
Their sales reps went from "who is this?" to "calling Jane now" in seconds instead of minutes. Meeting booking rate went up 40%.
The Technical Implementation
You need three data sources:
- Email validation — Confirm it's real and extract the domain
- IP geolocation — Get location from their signup request
- Gravatar lookup — Fetch public profile data
Here's each piece:
Email Validation (First Filter)
Don't waste resources enriching fake emails.
async function validateAndExtract(email) {
const res = await fetch(
`https://api.apiverve.com/v1/emailvalidator?email=${encodeURIComponent(email)}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
if (!data.isValid) {
return { valid: false };
}
return {
valid: true,
domain: data.domain,
isCompanyEmail: data.isCompanyEmail,
isFreeEmail: data.isFreeEmail,
// Already gives us a company signal!
companyGuess: data.isCompanyEmail
? formatCompanyName(data.domain)
: null
};
}
function formatCompanyName(domain) {
// acme-corp.com -> Acme Corp
return domain
.split('.')[0]
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
Right away you know: Is this a company email? If so, you've got the company name from the domain. jane@slack.com probably works at Slack.
IP Geolocation
Every HTTP request includes the client's IP. Turn it into a location.
async function getLocationFromIP(ip) {
const res = await fetch(
`https://api.apiverve.com/v1/iplookup?ip=${encodeURIComponent(ip)}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data } = await res.json();
return {
city: data.city,
region: data.region,
country: data.country,
timezone: data.timezone,
coordinates: data.coordinates,
// Calculate business hours
localTime: getLocalTime(data.timezone),
isBusinessHours: isBusinessHours(data.timezone)
};
}
function getLocalTime(timezone) {
return new Date().toLocaleString('en-US', {
timeZone: timezone,
hour: 'numeric',
minute: '2-digit',
hour12: true
});
}
function isBusinessHours(timezone) {
const localHour = new Date().toLocaleString('en-US', {
timeZone: timezone,
hour: 'numeric',
hour12: false
});
const hour = parseInt(localHour);
return hour >= 9 && hour < 17;
}
Now you know where they are and whether it's a good time to call. No more accidentally calling London at 3am because you forgot the timezone.
Gravatar Profile Lookup
Here's where the magic happens. Gravatar profiles are public and often include rich data.
async function getGravatarProfile(email) {
const res = await fetch(
`https://api.apiverve.com/v1/gravatarlookup?email=${encodeURIComponent(email)}`,
{ headers: { 'x-api-key': process.env.APIVERVE_KEY } }
);
const { data, status } = await res.json();
// Not everyone has a Gravatar
if (status !== 'ok' || !data) {
return null;
}
// Parse social accounts into a cleaner format
const socials = {};
if (data.accounts) {
data.accounts.forEach(account => {
socials[account.name.toLowerCase()] = {
url: account.url,
username: account.username
};
});
}
return {
displayName: data.displayName,
photo: data.thumbnailUrl,
bio: data.aboutMe,
location: data.currentLocation,
company: data.company,
website: data.profileUrl,
socials,
// Extract specific valuable links
linkedin: socials.linkedin?.url,
twitter: socials.twitter?.url || socials.x?.url,
github: socials.github?.url
};
}
Developers especially love Gravatar. Their profiles often include GitHub, LinkedIn, Twitter, and a bio. You're getting data that would take a human 10 minutes to Google.
Putting It All Together
Here's the complete enrichment pipeline:
async function enrichLead(email, ip) {
const startTime = Date.now();
// Step 1: Validate email
const emailData = await validateAndExtract(email);
if (!emailData.valid) {
return {
success: false,
reason: 'invalid_email',
enrichmentTime: Date.now() - startTime
};
}
// Step 2: Run location and Gravatar lookups in parallel
const [location, gravatar] = await Promise.all([
getLocationFromIP(ip),
getGravatarProfile(email)
]);
// Step 3: Build the enriched profile
const enriched = {
// Core info
email,
emailDomain: emailData.domain,
isCompanyEmail: emailData.isCompanyEmail,
// Name (from Gravatar or derived from email)
name: gravatar?.displayName || deriveNameFromEmail(email),
photo: gravatar?.photo,
// Company info
company: gravatar?.company || emailData.companyGuess,
// Location
location: gravatar?.location || formatLocation(location),
timezone: location.timezone,
localTime: location.localTime,
isBusinessHours: location.isBusinessHours,
// Social links
linkedin: gravatar?.linkedin,
twitter: gravatar?.twitter,
github: gravatar?.github,
bio: gravatar?.bio,
// Metadata
enrichmentTime: Date.now() - startTime,
enrichmentLevel: calculateEnrichmentLevel(gravatar, emailData),
enrichedAt: new Date().toISOString()
};
return {
success: true,
data: enriched
};
}
function deriveNameFromEmail(email) {
// john.doe@company.com -> John Doe
const username = email.split('@')[0];
return username
.replace(/[._]/g, ' ')
.replace(/\d+/g, '')
.split(' ')
.filter(Boolean)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ') || null;
}
function formatLocation(loc) {
if (loc.city && loc.region) {
return `${loc.city}, ${loc.region}`;
}
return loc.country;
}
function calculateEnrichmentLevel(gravatar, emailData) {
// How much data did we get?
if (gravatar?.displayName && gravatar?.linkedin) return 'full';
if (gravatar?.displayName || emailData.isCompanyEmail) return 'partial';
return 'minimal';
}
Three API calls (one for validation, one for IP, one for Gravatar). Run the last two in parallel. Total time: usually under 500ms.
What You Get
Here's a real example of enriched output:
{
"success": true,
"data": {
"email": "alex.johnson@techstartup.io",
"emailDomain": "techstartup.io",
"isCompanyEmail": true,
"name": "Alex Johnson",
"photo": "https://gravatar.com/avatar/abc123...",
"company": "TechStartup",
"location": "San Francisco, CA",
"timezone": "America/Los_Angeles",
"localTime": "10:30 AM",
"isBusinessHours": true,
"linkedin": "https://linkedin.com/in/alexjohnson",
"twitter": "https://twitter.com/alexj",
"github": "https://github.com/alexjohnson",
"bio": "Engineering lead at TechStartup. Building developer tools.",
"enrichmentTime": 423,
"enrichmentLevel": "full",
"enrichedAt": "2025-12-09T18:30:00.000Z"
}
}
Your sales rep sees this 2 seconds after the signup. They know it's an engineering lead at a tech startup in SF, it's 10:30am there (good time to call), and they can connect on LinkedIn first if they want to warm up the lead.
Integration Patterns
Real-Time (Recommended for Sales-Led Products)
Enrich during signup, display immediately in admin dashboard:
app.post('/signup', async (req, res) => {
const { email, password } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
// Create account
const user = await createUser(email, password);
// Enrich in background, don't slow down signup
enrichLead(email, ip)
.then(result => {
if (result.success) {
saveEnrichment(user.id, result.data);
// Notify sales if it's a hot lead
if (result.data.isCompanyEmail && result.data.linkedin) {
notifySalesSlack(result.data);
}
}
})
.catch(console.error);
// Return immediately
res.json({ success: true });
});
Batch (For Existing Lists)
Already have emails without enrichment? Process them:
async function enrichExistingLeads() {
const unenrichedLeads = await db.query(`
SELECT id, email, signup_ip
FROM leads
WHERE enrichment_data IS NULL
LIMIT 100
`);
for (const lead of unenrichedLeads) {
const result = await enrichLead(lead.email, lead.signup_ip);
await db.query(`
UPDATE leads
SET enrichment_data = $1, enriched_at = NOW()
WHERE id = $2
`, [JSON.stringify(result.data), lead.id]);
// Rate limit ourselves
await sleep(100);
}
}
Run this as a cron job to slowly enrich your backlog.
Lead Scoring Based on Enrichment
Once you have this data, you can score leads automatically:
function scoreLead(enrichment) {
let score = 0;
const signals = [];
// Company email = serious buyer
if (enrichment.isCompanyEmail) {
score += 25;
signals.push('company_email');
}
// Has LinkedIn = findable and professional
if (enrichment.linkedin) {
score += 20;
signals.push('has_linkedin');
}
// Full Gravatar profile = engaged professional
if (enrichment.enrichmentLevel === 'full') {
score += 15;
signals.push('rich_profile');
}
// Bio mentions relevant keywords
if (enrichment.bio) {
const relevantTerms = ['engineer', 'developer', 'lead', 'manager', 'director', 'founder', 'cto', 'ceo'];
if (relevantTerms.some(term => enrichment.bio.toLowerCase().includes(term))) {
score += 20;
signals.push('decision_maker');
}
}
// US-based (adjust for your market)
if (enrichment.timezone?.startsWith('America/')) {
score += 10;
signals.push('us_timezone');
}
return { score, signals, priority: score >= 50 ? 'high' : score >= 25 ? 'medium' : 'low' };
}
High-scoring leads get immediate sales attention. Low-scoring leads go to nurture campaigns. No manual sorting required.
What If There's No Gravatar?
Not everyone has a Gravatar profile. Your enrichment pipeline still provides value:
- Email domain tells you the company
- IP location tells you where they are
- Timezone tells you when to call
- isCompanyEmail tells you if it's a B2B lead
That's still more than "email address and nothing else."
For leads without Gravatar data, you might:
- Trigger a LinkedIn search task for your SDRs
- Use additional enrichment services
- Add to a "needs research" queue
The point is automation handles the 60% of cases that have public data, freeing humans for the rest.
Privacy Considerations
Everything in this pipeline uses publicly available data:
- IP geolocation is based on network routing information
- Gravatar profiles are explicitly public (users opt-in)
- Email domain inference is... just reading the email
You're not scraping anything. You're not buying shady data lists. You're aggregating information that leads have made public.
That said, be transparent. Your privacy policy should mention you collect location data and may look up public profile information. Most B2B buyers expect this and don't mind.
The Cost Math
Let's price this out:
- Email validation: 1 credit
- IP lookup: 1 credit
- Gravatar lookup: 1 credit
- Total: 3 credits per lead
On APIVerve's Starter plan ({{plan.starter.price}}/month), you get {{plan.starter.calls}} credits. That's thousands of leads fully enriched.
Compare to dedicated lead enrichment services (Clearbit, ZoomInfo) charging $0.10-$0.50+ per enrichment. You're looking at 5-15x cost savings, depending on the service.
And you're not locked into anyone's data format. You control the pipeline.
Lead enrichment isn't magic. It's just connecting dots that already exist publicly. The email tells you the company. The IP tells you the location. Gravatar tells you who they are.
Three API calls. One sales team that actually has context. Deals that close faster because nobody's wasting time researching.
The Email Validator, IP Lookup, and Gravatar Lookup APIs are all available with the same API key. Start enriching your leads today.
Originally published at APIVerve Blog
Top comments (0)