Find and verify emails for HubSpot prospects using Apollo and Hunter with an agent skill

low complexityCost: Usage-based

Prerequisites

Prerequisites
  • Claude Code, Cursor, or another AI coding agent that supports skills
  • HubSpot private app token stored as HUBSPOT_TOKEN (scopes: crm.objects.contacts.read, crm.objects.contacts.write)
  • Apollo API key stored as APOLLO_API_KEY
  • Hunter.io API key stored as HUNTER_API_KEY

Overview

Create an agent skill that finds and verifies work emails for HubSpot prospects on demand. Run /find-emails and it searches for contacts missing email addresses, looks them up via Apollo, verifies uncertain results with Hunter, and writes verified emails back to HubSpot.

Step 1: Create the skill directory

mkdir -p .claude/skills/find-emails/scripts

Step 2: Write the SKILL.md file

Create .claude/skills/find-emails/SKILL.md:

---
name: find-emails
description: Finds and verifies work emails for HubSpot contacts missing email addresses. Uses Apollo for email lookup and Hunter for deliverability verification. Writes verified emails back to HubSpot.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
 
Find and verify emails for HubSpot prospects:
 
1. Run: `python $SKILL_DIR/scripts/find_emails.py`
2. Review the per-contact results
3. Check the summary for verified, risky, and not-found counts

Step 3: Write the email finder script

Create .claude/skills/find-emails/scripts/find_emails.py:

#!/usr/bin/env python3
"""
Email Finder: HubSpot → Apollo (find) → Hunter (verify) → HubSpot (update)
Finds contacts missing emails, looks up via Apollo, verifies with Hunter.
"""
import os
import sys
import time
 
try:
    import requests
except ImportError:
    os.system("pip install requests -q")
    import requests
 
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
APOLLO_API_KEY = os.environ.get("APOLLO_API_KEY")
HUNTER_API_KEY = os.environ.get("HUNTER_API_KEY")
 
if not all([HUBSPOT_TOKEN, APOLLO_API_KEY, HUNTER_API_KEY]):
    print("ERROR: Set HUBSPOT_TOKEN, APOLLO_API_KEY, and HUNTER_API_KEY")
    sys.exit(1)
 
HS_HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
 
# --- Find contacts without emails ---
print("Searching for contacts without email addresses...")
contacts = []
after = 0
while True:
    resp = requests.post(
        "https://api.hubapi.com/crm/v3/objects/contacts/search",
        headers=HS_HEADERS,
        json={
            "filterGroups": [{"filters": [
                {"propertyName": "email", "operator": "NOT_HAS_PROPERTY"},
                {"propertyName": "firstname", "operator": "HAS_PROPERTY"},
                {"propertyName": "company", "operator": "HAS_PROPERTY"},
            ]}],
            "properties": ["firstname", "lastname", "company"],
            "limit": 100,
            "after": after,
        }
    )
    resp.raise_for_status()
    data = resp.json()
    contacts.extend(data["results"])
    if data.get("paging", {}).get("next"):
        after = data["paging"]["next"]["after"]
    else:
        break
 
print(f"Found {len(contacts)} contacts without emails\n")
 
# --- Process each contact ---
stats = {"verified": 0, "risky": 0, "not_found": 0}
 
for contact in contacts:
    props = contact["properties"]
    name = f"{props.get('firstname', '')} {props.get('lastname', '')}".strip()
    company = props.get("company", "")
 
    # Find email via Apollo
    apollo_resp = requests.post(
        "https://api.apollo.io/api/v1/people/match",
        headers={"x-api-key": APOLLO_API_KEY, "Content-Type": "application/json"},
        json={
            "first_name": props.get("firstname", ""),
            "last_name": props.get("lastname", ""),
            "organization_name": company,
        }
    )
    apollo_resp.raise_for_status()
    person = apollo_resp.json().get("person")
 
    if not person or not person.get("email"):
        stats["not_found"] += 1
        print(f"  {name} @ {company} -> not found")
        time.sleep(0.5)
        continue
 
    email = person["email"]
    email_status = person.get("email_status", "unknown")
 
    # Skip Hunter if Apollo already verified
    if email_status == "verified":
        verification = "verified"
        source = "apollo"
    else:
        # Verify with Hunter
        hunter_resp = requests.get(
            "https://api.hunter.io/v2/email-verifier",
            params={"email": email, "api_key": HUNTER_API_KEY}
        )
        hunter_resp.raise_for_status()
        verification = hunter_resp.json()["data"]["result"]
        source = "apollo+hunter"
 
    # Update HubSpot
    update_props = {"email_source": source, "email_verification_status": verification}
    if verification in ("deliverable", "verified"):
        update_props["email"] = email
        stats["verified"] += 1
        print(f"  {name} @ {company} -> {email} ({source})")
    elif verification == "risky":
        update_props["email"] = email  # store but flag
        stats["risky"] += 1
        print(f"  {name} @ {company} -> {email} (RISKY)")
    else:
        stats["not_found"] += 1
        print(f"  {name} @ {company} -> {email} (UNDELIVERABLE, skipped)")
 
    requests.patch(
        f"https://api.hubapi.com/crm/v3/objects/contacts/{contact['id']}",
        headers=HS_HEADERS,
        json={"properties": update_props}
    ).raise_for_status()
 
    time.sleep(0.5)
 
print(f"\nDone. Verified: {stats['verified']}, Risky: {stats['risky']}, Not found: {stats['not_found']}")

Step 4: Run the skill

# Via Claude Code
/find-emails
 
# Or directly
python .claude/skills/find-emails/scripts/find_emails.py

Step 5: Schedule it (optional)

# .github/workflows/find-emails.yml
name: Find and Verify Emails
on:
  schedule:
    - cron: '0 11 * * *'  # Daily at 6 AM ET
  workflow_dispatch: {}
jobs:
  find:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install requests
      - run: python .claude/skills/find-emails/scripts/find_emails.py
        env:
          HUBSPOT_TOKEN: ${{ secrets.HUBSPOT_TOKEN }}
          APOLLO_API_KEY: ${{ secrets.APOLLO_API_KEY }}
          HUNTER_API_KEY: ${{ secrets.HUNTER_API_KEY }}

Cost

  • Apollo: 1 credit per people match lookup (even for no-match results). Basic plan ($49/mo) = 900 credits.
  • Hunter: 1 credit per verification. Starter plan ($49/mo) = 1,000 verifications. Apollo-verified emails skip Hunter.
  • Per 50 contacts: ~50 Apollo credits + ~30 Hunter credits (after skipping Apollo-verified ones).
No-match credits

Apollo charges 1 credit even when no person is found for a name + company combination. Clean your contact data (remove test records, fix misspelled names) before running to avoid wasting credits.

When to use this approach

  • You have a batch of prospects from an event or import that need emails
  • You want to run email finding on-demand, not on a schedule
  • You want to see results in real-time as each contact is processed
  • You're evaluating Apollo + Hunter before committing to a platform automation

When to move to a dedicated tool

  • You need email finding to run automatically on every new contact
  • Multiple team members need to trigger and monitor email finding
  • You want visual dashboards showing find rates and verification results

Need help implementing this?

We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.