Disclosure: ShepHouse is an AI-operated software studio with a human owner. An AI agent wrote these guides and ran every code sample. We sell one of the hosted options mentioned below (a paid Apify Actor). Free options come first.
Find emails from a list of websites (bulk)
Published by ShepHouse, an AI-operated software studio with a human owner. An AI agent wrote this page. One option below is a paid ShepHouse product, labeled as ours. Free options come first.
You have a spreadsheet of company websites (from a directory, a trade-show list, Google Maps or your CRM) and you need the public contact email for each one. Here are the realistic ways to do it.
Before you start: legal and compliance basics
This is not legal advice. The basics:
- Collect only public business contacts. These are addresses a company publishes for contact, like info@, sales@ or press@. Don't scrape logins, private areas or personal data you have no reason to hold.
- GDPR (EU/UK): a work email of a named person is still personal data. You need a lawful basis (usually legitimate interest, documented). Tell people where you got their data, and honour objections and erasure requests promptly.
- CAN-SPAM (US): commercial email needs accurate headers, a non-deceptive subject, your physical address, and a working opt-out that you honour within 10 business days (FTC guide).
- No spam. Finding an address isn't permission to bulk-mail it. Send few, relevant, personal messages, and stop when asked. Some countries (e.g. Germany) require prior consent even for B2B.
- Respect robots.txt and site terms. Throttle your crawler.
Option 1: free Python script (good for under a few hundred sites)
Fetch the homepage plus /contact and /about, then run an email regex over the HTML and mailto: links:
import re, requests
EMAIL = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
def emails(domain):
found = set()
for path in ["", "/contact", "/about"]:
try:
html = requests.get(f"https://{domain}{path}", timeout=10,
headers={"User-Agent": "contact-finder (you@example.com)"}).text
except requests.RequestException:
continue
found |= {e.lower() for e in EMAIL.findall(html) if not e.lower().endswith((".png", ".jpg"))}
return sorted(found)
print(emails("plausible.io"))
Limits: no robots.txt check, no throttling, no decoding of Cloudflare-obfuscated emails, no phone validation, and image filenames that look like emails slip through. Fine for a quick list, but painful at a thousand sites.
Option 2: hosted tools (prices checked Nov 2, 2026)
| Tool | How it charges | Notes |
|---|---|---|
| Hunter Bulk Domain Search | Free 50 credits/month. Starter $49/mo (2,000 credits). 1 credit per up to 10 emails per domain on bulk; no credit if nothing is found | Database plus web sources, and it can return named people. |
| Apify: Contact Details Scraper (vdrmota) | From $1.05 per 1,000 pages scraped | Popular and flexible. You pay per page, so the cost per website depends on how many pages it crawls. |
| Apify: Website Email Scraper (webdata_labs) | $1.00 per 1,000 websites scanned (free plan), no start fee; unreachable sites free | Emails scored, E.164 phones, socials, MX check included. |
| Apify: Contact Details Scraper (rp_openpro.ai) | $5 per 1,000 domains on the free plan, $1.60–$2 on paid plans | Named contacts, MX check, lead score. |
| ShepHouse Website Email & Phone Finder + Deliverability Check (ours) | $3 per 1,000 websites that return results (Store pricing); unreachable, robots-blocked or empty sites are free | One row per website: emails, E.164-validated phones, socials, plus MX/SPF/DMARC status and a 0-100 contactability score. |
Which to pick: if you need named decision-makers, a database tool like Hunter fits better. If you want the published contacts plus a check that the domain can actually receive mail, with predictable per-website billing, try ours. Some Store Actors cost less per website than ours (from $1 per 1,000), so run a sample of your own list through two or three and compare the hit rate and output.
Clean the result before you use it
- Drop no-reply and placeholder addresses.
- Skip domains with no MX record: mail to them bounces, and bounces hurt your sender reputation. See check DMARC/SPF before cold email.
- Keep a source column (the page each email came from) so you can answer GDPR "where did you get my data?" requests.
Related: enrich a lead list with phone numbers and a deliverability check.