Find and verify emails for HubSpot prospects using Apollo and Hunter in n8n
Install this workflow
Download the n8n workflow JSON and import it into your n8n instance.
email-finder.n8n.jsonPrerequisites
- n8n instance — n8n cloud or self-hosted
- HubSpot private app token with
crm.objects.contacts.readandcrm.objects.contacts.writescopes - Apollo API key with email finder credits (Settings → Integrations → API)
- Hunter.io API key with verification credits (Dashboard → API)
- n8n credential configured for HubSpot
Why n8n?
n8n's branching and HTTP Request nodes make it ideal for the find→verify→update chain. You can visually see the Apollo-verified path vs. the Hunter-verification path, and IF nodes handle the routing logic without code. Self-hosted n8n is free with unlimited executions, so the only variable cost is Apollo and Hunter credits.
The trade-off is setup complexity. You'll configure 6-8 nodes with HTTP headers, JSON bodies, and conditional logic. If you want a single script you can read top-to-bottom, the Code approach is simpler to understand.
Step 1: Schedule the workflow
Add a Schedule Trigger node:
- Trigger interval: Days
- Days between triggers: 1
- Hour: 6
- Timezone: Your team's timezone
Running daily catches new prospects imported the previous day.
Step 2: Search HubSpot for contacts without emails
Add an HTTP Request node to find contacts that have a name and company but no email:
- Method: POST
- URL:
https://api.hubapi.com/crm/v3/objects/contacts/search - Authentication: HubSpot credential
- Body:
{
"filterGroups": [
{
"filters": [
{
"propertyName": "email",
"operator": "NOT_HAS_PROPERTY"
},
{
"propertyName": "firstname",
"operator": "HAS_PROPERTY"
},
{
"propertyName": "company",
"operator": "HAS_PROPERTY"
}
]
}
],
"properties": ["firstname", "lastname", "company", "domain", "email"],
"limit": 50
}Apollo's People Match works best with an email or a domain + name combination. If your contacts have a domain property (e.g., acme.com), use that. If they only have company (e.g., "Acme Inc"), Apollo will try to resolve the domain, but results are less reliable.
Step 3: Loop through contacts and call Apollo
Add a Split In Batches node to iterate over the search results (set batch size to 1).
Add an HTTP Request node to find the email via Apollo:
- Method: POST
- URL:
https://api.apollo.io/api/v1/people/match - Headers:
x-api-key: Your Apollo API keyContent-Type:application/json
- Body:
{
"first_name": "{{ $json.properties.firstname }}",
"last_name": "{{ $json.properties.lastname }}",
"organization_name": "{{ $json.properties.company }}"
}Apollo returns the person's email in person.email along with a confidence indicator. If no match is found, person will be null.
Step 4: Check confidence and decide whether to verify
Add an IF node to evaluate the Apollo result:
- Condition:
{{ $json.person.email }}is not empty
On the true branch, add a second IF node to check confidence:
- High confidence (skip verification): Check if
{{ $json.person.email_status }}equalsverified - Lower confidence (needs verification): Everything else goes to Hunter
Apollo marks some emails as verified — these have already been checked by Apollo's internal systems. Skip Hunter verification for these to save credits. Only verify emails marked as guessed or likely.
Step 5: Verify with Hunter
On the "needs verification" branch, add an HTTP Request node:
- Method: GET
- URL:
https://api.hunter.io/v2/email-verifier - Query Parameters:
email:{{ $json.person.email }}api_key: Your Hunter API key
Hunter returns a data.result field with one of these values:
deliverable— safe to userisky— might bounceundeliverable— definitely badunknown— Hunter couldn't determine
Add an IF node:
- Condition:
{{ $json.data.result }}equalsdeliverable
Hunter's free plan allows 25 verifications/month. Paid plans start at $49/mo for 1,000 verifications. If you're verifying more than a handful of emails, you'll need a paid plan. The API rate limit is 15 requests/second.
Step 6: Update HubSpot with the verified email
On the verified branch, add an HTTP Request node:
- Method: PATCH
- URL:
https://api.hubapi.com/crm/v3/objects/contacts/{{ $('Search Contacts').item.json.id }} - Authentication: HubSpot credential
- Body:
{
"properties": {
"email": "{{ $json.person.email }}",
"email_verification_status": "verified",
"email_source": "apollo+hunter"
}
}For contacts where the email was risky or undeliverable, route to a separate update that sets a flag:
{
"properties": {
"email_verification_status": "unverified",
"email_source": "apollo"
}
}Step 7: Add a Wait node for rate limiting
Add a Wait node (set to 1 second) after the Apollo HTTP Request to stay within Apollo's rate limits (5 req/sec on Basic plans).
Step 8: Test and activate
- Click Execute Workflow to run against your prospect list
- Check each node — verify Apollo returns emails and Hunter verifies them
- Open a few contacts in HubSpot to confirm the email was written
- Toggle the workflow to Active
Troubleshooting
Cost
- n8n cloud: Starts at $24/mo. Each prospect uses ~5-7 executions depending on branch.
- Apollo: 1 credit per people match lookup. Basic plan ($49/mo) = 900 credits.
- Hunter: 1 credit per verification. Starter plan ($49/mo) = 1,000 verifications. Free plan = 25/month.
- Per prospect (typical): 1 Apollo credit + 0-1 Hunter credit = 1-2 credits total. High-confidence Apollo results skip Hunter entirely.
Common questions
How many Hunter credits does the Apollo-verified shortcut save?
Typically 30-40% of Apollo results come pre-verified. For a batch of 50 contacts, you'd skip Hunter for 15-20 of them, saving 15-20 Hunter credits per run. Over a month of daily runs, that's 450-600 credits saved.
Can I use n8n's built-in HubSpot node for the search?
The built-in HubSpot node supports basic contact operations but not the Search API with complex filters (NOT_HAS_PROPERTY + HAS_PROPERTY). Use an HTTP Request node for the search and the built-in node for simple updates.
What happens when both Apollo and Hunter can't find or verify an email?
The contact stays without an email in HubSpot, and you can set an email_verification_status of "not_found" to exclude it from future runs. Route these contacts to a HubSpot list for manual research.
Next steps
- Add company domain resolution — if contacts only have a company name, add a step to find the domain first using Hunter's Domain Search endpoint
- Flag for manual review — route contacts with
riskyemails to a HubSpot list for a human to decide - Add a completion notification — send a Slack summary with counts: found, verified, failed
Looking to scale your AI operations?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.