# Holehe Module Interface: The Standard Contract for Async Email Checking Modules

> Discover the standard Holehe module interface an async def function taking email client and out to append results to a shared list Learn how to build async email checking modules with this clear contract

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: api-reference
- Published: 2026-08-31

---

**The standard interface for a Holehe module is an `async def` function accepting three parameters—`email`, `client`, and `out`—that appends a result dictionary with specific keys to a shared list rather than returning a value.**

Holehe (megadose/holehe) is an open-source OSINT tool that discovers accounts linked to an email address by executing modular checks against hundreds of services. Understanding the **Holehe module interface** is essential for anyone extending the tool with new platforms or analyzing its architecture.

## Core Parameters of the Standard Interface

Every Holehe module must implement a function with this exact signature:

```python
async def module_name(email, client, out):

```

| Parameter | Type | Purpose |
|-----------|------|---------|
| `email` | `str` | The target email address to test |
| `client` | `httpx.AsyncClient` | Pre-configured async HTTP client with global timeout settings |
| `out` | `list` | Shared result list that the module **must append to** |

The function **must not return any value**. All output flows through the `out` parameter.

## Required Result Dictionary Format

Module functions populate `out` with a dictionary containing these mandatory keys:

- `name` – Internal identifier, typically matching the module filename
- `domain` – Service domain (e.g., `"twitter.com"`)
- `method` – Verification approach, commonly `"register"`
- `frequent_rate_limit` – Boolean indicating if the service aggressively rate-limits
- `rateLimit` – Boolean set to `True` when the current request was throttled
- `exists` – Boolean indicating whether the email is registered on the service
- `emailrecovery` – Optional recovery email info (commonly `None`)
- `phoneNumber` – Optional phone number data (commonly `None`)
- `others` – Additional service-specific data (commonly `None`)

## Implementation Example: Twitter Module

The production Twitter module in [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) demonstrates this interface in practice. It queries `https://api.twitter.com/i/users/email_available.json` and constructs the result dictionary:

```python
async def twitter(email, client, out):
    name = "twitter"
    domain = "twitter.com"
    method = "register"
    frequent_rate_limit = True
    
    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json",
        "Accept-Language": "en,en-US;q=0.5",
        "Accept-Encoding": "gzip, deflate, br",
        "Referer": "https://twitter.com/",
        "DNT": "1",
        "Connection": "keep-alive",
    }
    
    try:
        r = await client.get(
            "https://api.twitter.com/i/users/email_available.json",
            headers=headers,
            params={"email": email}
        )
        data = r.json()
        
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": data["taken"] if "taken" in data else False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
    except Exception:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

This pattern—wrapping the HTTP request in `try/except` and always appending a result with `rateLimit=True` on failure—is consistent across all Holehe modules.

## How Holehe Loads and Executes Modules

The **Holehe module interface** enables automatic discovery and execution through three functions in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py):

### Module Discovery (`import_submodules`)

Lines 37-47 recursively walk the `holehe.modules` package:

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

```

### Function Extraction (`get_functions`)

Lines 50-63 identify callables matching the standard signature:

```python
def get_functions(modules):
    """Extract module functions that implement the Holehe interface."""
    websites = []
    for module in modules:
        if len(module.split(".")) > 3:
            modu = modules[module]
            site = module.split(".")[-1]
            if hasattr(modu, site):
                websites.append(getattr(modu, site))
    return websites

```

### Concurrent Execution (`launch_module`)

Lines 66-71 invoke each module within a Trio nursery for concurrency:

```python
async def launch_module(module, email, client, out):
    """Execute a single module with standard parameters."""
    try:
        await module(email, client, out)
    except Exception:
        pass  # Modules handle their own error reporting via out.append

```

## Creating a New Holehe Module

Follow this skeleton to implement the **standard Holehe module interface** for a new service:

```python

# holehe/modules/category/example.py

import httpx
from holehe.localuseragent import ua
import random

async def example(email, client, out):
    """
    Check if email is registered on example.com
    Implements the standard Holehe module interface.
    """
    name = "example"
    domain = "example.com"
    method = "register"
    frequent_rate_limit = False

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json",
    }

    try:
        response = await client.get(
            "https://api.example.com/v1/account/check",
            headers=headers,
            params={"email": email},
            timeout=10
        )
        data = response.json()
        
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": data.get("account_exists", False),
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
        
    except httpx.TimeoutException:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,  # Timeout treated as rate limit indicator

            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
        
    except Exception:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

Place this file in `holehe/modules/social_media/` or any subdirectory of `holehe/modules/`. The auto-discovery mechanism in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) will detect and execute it without additional registration.

## Key Design Decisions in the Interface

The **Holehe module interface** reflects several architectural priorities:

1. **Shared state via `out` parameter** – Avoids return value complexity in concurrent execution
2. **Async/await throughout** – Enables efficient I/O-bound HTTP operations across hundreds of services
3. **Consistent error handling** – Every exception path produces a valid result dictionary
4. **Zero configuration registration** – Filesystem placement alone triggers discovery

## Summary

- The **Holehe module interface** requires an `async def` function with parameters `(email, client, out)`
- Modules append result dictionaries to `out` rather than returning values
- Required result keys: `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, `others`
- Core machinery in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) handles discovery via `import_submodules`, filtering via `get_functions`, and execution via `launch_module`
- New modules placed under `holehe/modules/**` are automatically detected and executed

## Frequently Asked Questions

### Can a Holehe module return a value instead of using the out parameter?

No. The `launch_module` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) does not capture or process return values. Results must be appended to the `out` list, which the caller examines after execution completes. This design supports concurrent execution through Trio's nursery pattern.

### What happens if a module raises an unhandled exception?

Unhandled exceptions are caught by `launch_module` and silently suppressed. Modules should implement their own `try/except` blocks to append error-state results to `out` with `rateLimit=True`, following the pattern in existing modules like Twitter.

### Is the `client` parameter pre-configured with proxies or custom settings?

Yes. The `httpx.AsyncClient` is instantiated in Holehe's main execution path with `timeout=args.timeout` and any proxy settings from command-line arguments. Modules should use this `client` directly rather than creating their own HTTP clients to respect the user's configuration.

### Can modules use synchronous HTTP libraries instead of httpx?

No. The entire Holehe architecture depends on `asyncio` and Trio for concurrent execution. Modules must use the provided `httpx.AsyncClient` or other async-compatible HTTP libraries. Synchronous calls would block the event loop and degrade performance across all modules.