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:

  1. Plugin architecture: Adding a new service requires only creating one Python file with the standard async signature. No core changes needed.

  2. Structured concurrency: Trio's nursery pattern ensures all tasks complete or fail cleanly, with proper exception isolation between sites.

  3. 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_functions extractor finds callables by matching function names to filenames
  • An httpx.AsyncClient and 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 through export_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →