How to Enrich Your HubSpot Contacts with Easy Email Finder
Published February 9, 2026
The Missing Email Problem
You have hundreds of contacts in HubSpot, but many are missing email addresses. Maybe they came from trade shows, inbound forms with optional email fields, or manual entry by sales reps. Without emails, these contacts sit in your CRM unused. The Easy Email Finder API can fill in those gaps automatically.
How This Integration Works
The workflow is straightforward:
- Pull contacts from HubSpot that have a company website but no email
- Send those websites to the Easy Email Finder
/enrich-batchendpoint - Update HubSpot contacts with the discovered emails
- Run this on a schedule to catch new contacts
Prerequisites
You need a HubSpot Private App token with crm.objects.contacts.read and crm.objects.contacts.write scopes, plus an Easy Email Finder API key from the Developer Dashboard.
pip install requests python-dotenv
The Complete Script
import os
import time
import requests
from dotenv import load_dotenv
load_dotenv()
HUBSPOT_TOKEN = os.getenv("HUBSPOT_TOKEN")
EEF_KEY = os.getenv("EEF_API_KEY")
EEF_BASE = "https://easyemailfinder.com/api/v1"
def get_contacts_without_email(limit=100):
"""Fetch HubSpot contacts that have a website but no email."""
url = "https://api.hubapi.com/crm/v3/objects/contacts/search"
payload = {
"filterGroups": [{
"filters": [
{
"propertyName": "website",
"operator": "HAS_PROPERTY"
},
{
"propertyName": "email",
"operator": "NOT_HAS_PROPERTY"
}
]
}],
"properties": ["firstname", "lastname", "company", "website", "email"],
"limit": limit
}
resp = requests.post(url, headers={
"Authorization": f"Bearer {HUBSPOT_TOKEN}",
"Content-Type": "application/json"
}, json=payload)
resp.raise_for_status()
return resp.json().get("results", [])
def enrich_websites(websites: list) -> dict:
"""Enrich websites and return a mapping of website -> email."""
results = {}
for i in range(0, len(websites), 20):
batch = websites[i:i+20]
resp = requests.post(f"{EEF_BASE}/enrich-batch", headers={
"Authorization": f"Bearer {EEF_KEY}",
"Content-Type": "application/json"
}, json={"websites": batch})
resp.raise_for_status()
for result in resp.json().get("results", []):
if result.get("email") and result.get("website"):
results[result["website"].lower().rstrip("/")] = result["email"]
if i + 20 < len(websites):
time.sleep(7) # Respect rate limits
return results
def update_hubspot_contact(contact_id: str, email: str):
"""Update a HubSpot contact with the discovered email."""
url = f"https://api.hubapi.com/crm/v3/objects/contacts/{contact_id}"
resp = requests.patch(url, headers={
"Authorization": f"Bearer {HUBSPOT_TOKEN}",
"Content-Type": "application/json"
}, json={"properties": {"email": email}})
return resp.status_code == 200
def run_enrichment():
"""Main enrichment pipeline."""
print("Fetching contacts without emails from HubSpot...")
contacts = get_contacts_without_email()
print(f"Found {len(contacts)} contacts to enrich")
if not contacts:
print("No contacts need enrichment")
return
# Extract websites
website_to_contact = {}
for contact in contacts:
website = contact["properties"].get("website", "").lower().rstrip("/")
if website:
website_to_contact[website] = contact["id"]
print(f"Enriching {len(website_to_contact)} websites...")
email_map = enrich_websites(list(website_to_contact.keys()))
print(f"Found {len(email_map)} emails")
# Update HubSpot contacts
updated = 0
for website, email in email_map.items():
contact_id = website_to_contact.get(website)
if contact_id:
success = update_hubspot_contact(contact_id, email)
if success:
updated += 1
print(f" Updated: {email} -> contact {contact_id}")
else:
print(f" Failed to update contact {contact_id}")
print(f"\nDone! Updated {updated} contacts in HubSpot")
# Check remaining credits
balance = requests.get(f"{EEF_BASE}/balance", headers={
"Authorization": f"Bearer {EEF_KEY}"
}).json()
print(f"Credits remaining: {balance.get('credits', 0)}")
if __name__ == "__main__":
run_enrichment()
Setting Up a Schedule
Run this script daily to catch new contacts added to HubSpot. Use cron on Linux/Mac or Task Scheduler on Windows:
# Run daily at 7 AM
0 7 * * * cd /path/to/project && /path/to/venv/bin/python enrich_hubspot.py >> enrichment.log 2>&1
Handling Edge Cases
Duplicate websites: Multiple HubSpot contacts may share the same company website. The script handles this by mapping websites to contact IDs, so only one enrichment call is made per unique website.
Website format normalization: URLs in HubSpot may be entered inconsistently (with or without https://, trailing slashes, www prefix). The script normalizes URLs before processing to avoid duplicate enrichments.
Rate limiting: The script includes a 7-second delay between batch calls to stay within the Easy Email Finder rate limits. HubSpot has its own rate limits (100 requests per 10 seconds), which this script stays well within.
Cost Estimation
At $0.25 per email, enriching 100 contacts costs $25. Since you only pay for successful enrichments (where an email is actually found), the actual cost depends on the discovery rate. For local businesses with well-maintained websites, discovery rates are typically 60-80%, so 100 contacts would cost $15-$20. For more ways to reduce costs, see our cost optimization tips.
Going Further
This pattern extends naturally to other CRMs. For Salesforce and Pipedrive integrations, see our CRM integration guide. The full API documentation covers all available endpoints and response fields.
Ready to find business emails?
Try Easy Email Finder free — get 5 credits to start.
Start Finding Emails