# How Holehe Discovers Online Accounts From an Email Address: A Technical Deep Dive

> Learn how Holehe discovers online accounts from an email address by dynamically loading modules and executing probes against hundreds of services. Get unified results.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: deep-dive
- Published: 2026-08-30

---

**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`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 37-47). This function walks the `holehe.modules` package tree and imports every submodule it finds.

```python

# 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`](https://github.com/megadose/holehe/blob/main/social_media/twitter.py), [`shopping/amazon.py`](https://github.com/megadose/holehe/blob/main/shopping/amazon.py), or [`crm/hubspot.py`](https://github.com/megadose/holehe/blob/main/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.

```python

# 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:

```python

# 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`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) (lines 5-23) demonstrates the pattern:

```python

# 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:

```python

# 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

```bash

# 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

```python
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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py)** — Rotating User-Agent strings to avoid simple fingerprinting
- **[`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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.