How Holehe Checks for Email Registrations on Websites: A Deep Dive into the Open-Source OSINT Tool
Holehe discovers email registrations across thousands of websites by dynamically loading site-specific modules that each implement custom verification logic, then running all checks concurrently through an async HTTP client.
Holehe is an open-source OSINT tool by megadose that checks whether an email address is registered on hundreds of online services. This article explains how Holehe performs email registration checks based on the actual source code, walking through the validation, modular architecture, and concurrent execution that makes large-scale email reconnaissance possible.
Email Validation and Input Sanitization
Before any network requests are made, Holehe validates the input format. In holehe/core.py at lines 94-105, the is_email function applies a regular expression to ensure the provided string resembles a valid email address. This early validation prevents wasted HTTP calls on malformed inputs and provides immediate user feedback.
The validation layer is lightweight—if the regex fails, Holehe exits with an error before the heavy lifting begins.
Dynamic Module Loading for Site-Specific Checks
The core innovation behind Holehe's extensibility is its dynamic module system. Rather than hardcoding site checks, Holehe discovers and loads verification logic at runtime.
Importing All Site Modules
In holehe/core.py at lines 106-108, import_submodules("holehe.modules") walks the holehe/modules package directory and imports every Python file found. This function (defined at lines 37-48) recursively explores subdirectories, treating each .py file as a potential site check.
Extracting Callable Functions
Once modules are loaded, get_functions (lines 50-64) extracts the actual verification function from each module. The convention is simple: the function name must match the filename. A file named twitter.py contains a function async def twitter(...). This naming convention enables automatic discovery without configuration files.
The function also respects CLI flags like --no-password-recovery, filtering out modules that rely on password reset flows when users prefer less intrusive checks.
Concurrent Execution with Async HTTP
Holehe achieves speed through massive concurrency using httpx.AsyncClient and structured concurrency via Trio nurseries.
Creating the Async Client
At lines 13-14, Holehe instantiates a shared httpx.AsyncClient that all site checks reuse. This connection pooling eliminates the overhead of creating and destroying sockets for hundreds of requests.
Launching Parallel Site Checks
The execution happens in maincore (line 80 onwards). A Trio nursery spawns launch_module for every discovered site function. This pattern appears at lines 19-21:
async with trio.open_nursery() as nursery:
for module in modules:
nursery.start_soon(launch_module, module, email, client, out)
Each launch_module (lines 66-78) awaits the site-specific async function and traps any exception. If a site raises an error or returns a rate-limit response, the result is recorded without crashing other checks. This isolation ensures that one flaky service doesn't compromise the entire scan.
How Individual Site Checks Work
Every site module follows an identical contract. The required signature is:
async def <site_name>(email, client, out)
Example: Twitter Email Verification
The Twitter check in holehe/modules/social_media/twitter.py demonstrates the pattern:
async def twitter(email, client, out):
name = "twitter"
domain = "twitter.com"
method = "register"
frequent_rate_limit = False
try:
req = await client.get(
"https://api.twitter.com/i/users/email_available.json",
params={"email": email}
)
if req.json()["taken"]:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": False,
"exists": True,
"emailrecovery": None,
"phoneNumber": None,
"others": None
})
else:
out.append({... "exists": False ...})
except Exception:
out.append({... "rateLimit": True ...})
The function queries Twitter's public email_available.json endpoint. The response contains a taken boolean—True means registered, False means available. Results are appended to the shared out list, which all modules write to concurrently.
Variations Across Site Modules
Not all services expose clean APIs. Other modules in holehe/modules/ implement diverse techniques:
- HTML scraping with BeautifulSoup for registration forms that leak existence through error messages
- Undocumented API endpoints found through reverse engineering mobile apps
- Password recovery flows that confirm accounts exist when they send reset emails
- Login form timing analysis where existing accounts behave differently
Despite these differences, all modules return the same dictionary structure. This uniformity lets the core engine treat every service identically for result collection and display.
Result Collection and Output Formatting
After all nursery tasks complete, Holehe processes the accumulated results.
Colored Terminal Output
print_result (lines 6-49) sorts results and prints them with color coding. Accounts that exist appear in one color, non-existent in another, rate-limited in a third. This visual hierarchy lets operators scan hundreds of results instantly.
Optional CSV Export
When the -C flag is passed, export_csv (lines 54-64) writes structured data to disk for further analysis or reporting.
| Component | File Path | Responsibility |
|---|---|---|
| Core orchestration | holehe/core.py:maincore |
Argument parsing, module loading, async coordination |
| Module discovery | holehe/core.py:import_submodules |
Dynamic import of holehe/modules subpackages |
| Function extraction | holehe/core.py:get_functions |
Converts modules to callable site functions |
| Isolated execution | holehe/core.py:launch_module |
Exception-safe wrapper for each site check |
| Twitter example | holehe/modules/social_media/twitter.py |
Concrete email availability check |
Using Holehe: Command Line and Programmatic Examples
Command Line Interface
# Check all supported sites
holehe email@example.com
# Show only successful registrations
holehe email@example.com --only-used
# Export to CSV for reporting
holehe email@example.com -C
Programmatic Usage
For integration into larger Python workflows:
import asyncio
import httpx
from holehe.core import import_submodules, get_functions
async def check_email(email):
modules = import_submodules("holehe.modules")
sites = get_functions(modules)
client = httpx.AsyncClient()
results = []
for site in sites:
await site(email, client, results)
await client.aclose()
return results
# Run the check
if __name__ == "__main__":
findings = asyncio.run(check_email("target@example.com"))
for site in findings:
if site.get("exists"):
print(f"Found: {site['name']} at {site['domain']}")
Architectural Strengths of Holehe's Design
The email registration checking approach in Holehe succeeds because of three structural decisions made in the megadose/holehe codebase:
-
Plugin architecture: Adding a new service requires only creating one Python file with the standard async signature. No core changes needed.
-
Structured concurrency: Trio's nursery pattern ensures all tasks complete or fail cleanly, with proper exception isolation between sites.
-
Uniform result contract: Every module speaks the same output language, enabling generic result processing regardless of how exotic the verification technique.
Summary
- Holehe validates email format with regex before making any network requests
- Site checks live as independent modules under
holehe/modules, discovered dynamically at runtime - The
get_functionsextractor finds callables by matching function names to filenames - An
httpx.AsyncClientand Trio nursery execute hundreds of checks concurrently - Each site function receives
(email, client, out)and appends a standardized result dictionary - Results are sorted, color-printed via
print_result, and optionally exported throughexport_csv
Frequently Asked Questions
How does Holehe avoid getting rate-limited when checking hundreds of sites?
The launch_module wrapper in holehe/core.py catches exceptions and marks results with "rateLimit": True when services reject requests. Some modules set frequent_rate_limit=True to warn users about sensitive services. However, Holehe does not implement global rate-limiting delays—the tool assumes users will respect target sites or accept partial results.
Can I add my own custom site check to Holehe?
Yes. Create a Python file in holehe/modules/ (or any subdirectory) with an async function whose name matches the filename exactly. The function must accept (email, client, out) and append a result dictionary to out. The core loader will discover and execute it automatically on the next run.
Why does Holehe use Trio instead of asyncio directly?
The source shows Trio usage for structured concurrency through nurseries, which provides clearer failure semantics than raw asyncio. The instruments.py file implements a progress bar specifically for Trio's task system, giving users visibility into completion status across hundreds of concurrent checks.
Does Holehe store or transmit the checked email addresses anywhere?
No. According to the source code, all checks happen locally through your own HTTP client. The email address is only sent to the target services you're investigating, not to any Holehe infrastructure. Review individual site modules if you have concerns about which third parties receive the address.
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 →