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 timeout
  • trio.open_nursery() — Structured task scope; if one probe crashes, others continue
  • nursery.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 formatting
  • holehe/modules/**/*.py — Individual service probes organized by category (social_media, shopping, productivity, etc.)
  • holehe/localuseragent.py — Rotating User-Agent strings to avoid simple fingerprinting
  • holehe/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:

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 →