How Holehe Discovers Online Accounts From an Email Address: A Technical Deep Dive
Holehe discovers online accounts by dynamically loading site-specific modules and executing asynchronous probes against hundreds of services, normalizing the responses into a unified result set.
Holehe is an open-source OSINT tool by megadose/holehe that uncovers where an email address is registered across the internet. Unlike simple search engines, it performs live verification by talking directly to each platform's API or registration flow. This article explains the exact architecture powering this discovery engine, drawn from the holehe source code.
Dynamic Module Discovery: The Plugin Architecture
Holehe's extensibility starts with import_submodules() in holehe/core.py (lines 37-47). This function walks the holehe.modules package tree and imports every submodule it finds.
# From holehe/core.py lines 37-47
def import_submodules(package, recursive=True):
"""Import all submodules of a module, recursively."""
if isinstance(package, str):
package = importlib.import_module(package)
results = {}
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
full_name = package.__name__ + '.' + name
results[full_name] = importlib.import_module(full_name)
if recursive and is_pkg:
results.update(import_submodules(full_name))
return results
This design yields a plug-and-play module system. Each file in holehe/modules/—whether social_media/twitter.py, shopping/amazon.py, or crm/hubspot.py—is automatically discovered without hardcoded registration.
Extracting Callable Check Functions
Once modules are loaded, get_functions() (lines 50-63) extracts the actual probe logic. It inspects each module for a public async function and builds a list of callables ready for execution.
# From holehe/core.py lines 50-63
def get_functions(modules):
"""Extract async check functions from loaded modules."""
for module in modules:
if isinstance(module, types.ModuleType):
for name, obj in inspect.getmembers(module):
if inspect.iscoroutinefunction(obj) and not name.startswith('_'):
yield obj
Each callable represents one service probe. A single module might contain multiple check functions or one primary function that handles the service's specific verification logic.
Asynchronous Execution Engine with Trio
The maincore() function orchestrates concurrent probing using Trio for structured concurrency and httpx for async HTTP. Lines 18-21 and 24-30 establish the execution environment:
# From holehe/core.py lines 18-30
async def maincore():
# ... CLI argument parsing omitted ...
client = httpx.AsyncClient(timeout=10)
async with trio.open_nursery() as nursery:
for website in websites:
nursery.start_soon(launch_module, website, client, email, out)
Key characteristics of this design:
httpx.AsyncClient— Shared connection pool for all probes, with configurable timeouttrio.open_nursery()— Structured task scope; if one probe crashes, others continuenursery.start_soon()— Fire-and-forget scheduling of hundreds of concurrent checks
Site-Specific Probing: How Each Module Works
Every module implements an async function that knows how to test one specific service. The Twitter module in holehe/modules/social_media/twitter.py (lines 5-23) demonstrates the pattern:
# From holehe/modules/social_media/twitter.py lines 5-23
async def twitter(client, email, out):
try:
req = await client.post(
"https://api.twitter.com/i/users/email_available.json",
data={"email": email}
)
data = req.json()
# Response interpretation varies by service
if 'valid' in data and data['valid'] == False:
# Email already in use → account exists
out.append(["Twitter", True, "Email registered"])
else:
out.append(["Twitter", False, "Email not found"])
except Exception as e:
out.append(["Twitter", False, f"Error: {str(e)}"])
Modules employ diverse techniques depending on each platform's defenses:
- Direct API endpoints — Some services expose email availability checkers
- Registration flow analysis — Submitting to signup forms and parsing validation errors
- Login attempt probing — Testing password recovery flows
- Public profile scraping — When usernames are derived from email prefixes
Result Normalization and Error Handling
The launch_module() wrapper (lines 66-78) ensures consistent output regardless of what happens inside each probe:
# From holehe/core.py lines 66-78
async def launch_module(website, client, email, out):
try:
await website(client, email, out)
except Exception as e:
# Normalize any failure into a standard result dict
name = website.__name__
out.append({
"name": name,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
"rateLimit": False,
"error": str(e)
})
This normalization produces a uniform result schema across all services: service name, existence boolean, recovery options, rate-limit status, and error details.
Aggregating and Presenting Results
After all nursery tasks complete, print_result() (lines 6-49) renders the collected data with color-coded symbols:
| Symbol | Meaning |
|---|---|
| + | Account found (email registered) |
| - | Account not found (email available) |
| x | Rate limited by service |
| ! | Error during check |
The same data can be exported to CSV via export_csv() (lines 54-64) when the --csv flag is provided, creating timestamped files for further analysis.
Running Holehe: CLI and Library Usage
Command-Line Interface
# Basic discovery for single email
holehe alice@example.com
# Show only confirmed registrations
holehe alice@example.com --only-used
# Export results for reporting
holehe alice@example.com --csv
Programmatic Usage
from holehe.core import maincore
import trio
import sys
# Override sys.argv for programmatic control
sys.argv = ["holehe", "alice@example.com", "--only-used"]
# Trio runs the async event loop
trio.run(maincore)
For deeper integration, import and adapt the core functions directly—import_submodules(), get_functions(), and custom maincore() variants all accept parameters beyond CLI parsing.
Key Source Files
Understanding Holehe's architecture requires familiarity with these files:
holehe/core.py— Central orchestrator: module discovery, async execution, output formattingholehe/modules/**/*.py— Individual service probes organized by category (social_media, shopping, productivity, etc.)holehe/localuseragent.py— Rotating User-Agent strings to avoid simple fingerprintingholehe/instruments.py— Trio-compatible progress bar for real-time status feedback
Summary
Holehe's email-to-account discovery relies on six architectural pillars:
- Dynamic module loading via
import_submodules()enables unlimited service expansion - Automatic function extraction through
get_functions()converts modules to executable probes - Structured concurrency with Trio nurseries scales to hundreds of simultaneous checks
- Service-specific intelligence in each module handles unique API patterns and defenses
- Unified result normalization through
launch_module()guarantees consistent output schemas - Multiple output formats support both interactive terminal use and automated CSV pipelines
Frequently Asked Questions
How does Holehe avoid being blocked by rate limiting?
Holehe implements per-module error handling that catches HTTP 429 responses and connection failures. The launch_module() wrapper marks these as rate-limited (symbol x) rather than failures, allowing other probes to continue. However, the tool does not implement proxy rotation or sophisticated evasion—aggressive use will trigger defenses.
Can I add my own service modules to Holehe?
Yes. Create a Python file in holehe/modules/ with an async function matching the signature async def servicename(client, email, out). The function receives an httpx.AsyncClient, target email, and result list. Return status by appending to out. import_submodules() discovers your module automatically on next run.
What's the difference between Holehe and similar tools like Sherlock?
Sherlock searches for usernames across platforms by checking public profile URLs. Holehe verifies email registration status through direct API interaction, login flows, and registration checks. Holehe answers "is this email registered here?" while Sherlock answers "does this username exist here?"
How accurate is Holehe's account detection?
Accuracy depends on each module's implementation and the target service's API stability. Services with official email availability endpoints (like Twitter's email_available.json) yield high confidence. Others relying on form submission parsing may produce false negatives when services update their frontend. The rateLimit and error fields in results indicate uncertainty levels.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →