# How Holehe Checks for Email Registrations on Websites: A Deep Dive into the Open-Source OSINT Tool

> Discover how Holehe checks for email registrations on websites. This OSINT tool uses dynamic modules and concurrent checks for efficient verification. Learn more about its open-source process.

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

---

**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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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:

```python
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:

```python
async def <site_name>(email, client, out)

```

### Example: Twitter Email Verification

The Twitter check in [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) demonstrates the pattern:

```python
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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | Concrete email availability check |

## Using Holehe: Command Line and Programmatic Examples

### Command Line Interface

```bash

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

```python
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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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.