# Expected Module Function Signature in Holehe: A Developer's Guide

> Understand the expected module function signature in Holehe. Learn how modules accept email, client, and out arguments for asynchronous operations. A developer's guide.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: internals
- Published: 2026-08-29

---

**Every holehe module must expose an asynchronous function named after the service that accepts three arguments—`email`, `client`, and `out`—and appends a result dictionary to the mutable `out` list rather than returning a value.**

The megadose/holehe repository relies on a strict contract between its core engine and service-specific modules to perform concurrent email verification checks. Understanding the expected module function signature in holehe is essential for developers extending the tool with custom services or debugging existing modules.

## The Three-Argument Contract

Holehe dynamically discovers every service-checking script inside `holehe/modules/` using `import_submodules`. Each script must expose a single **asynchronous** function that the core engine invokes with a specific signature:

```python
async def <module_name>(email: str, client: httpx.AsyncClient, out: list) -> None

```

The function must accept exactly three parameters:

- **`email`** – The target email address to test as a string.
- **`client`** – A shared `httpx.AsyncClient` instance configured with CLI-defined timeouts for all HTTP operations.
- **`out`** – A mutable list that the module populates with result dictionaries.

In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 66-71, the `launch_module` function enforces this contract by calling each module with these three arguments:

```python
async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)
    except Exception:
        # fallback handling …

```

## The Result Dictionary Structure

Modules must **append** a dictionary to the `out` list rather than returning values. The dictionary must contain specific keys to ensure consistent output formatting across all services:

- `name`: The service name.
- `domain`: The service domain.
- `method`: The verification method used (typically "register").
- `frequent_rate_limit`: Boolean indicating if the service frequently rate limits.
- `rateLimit`: Boolean indicating if the check encountered rate limiting.
- `exists`: Boolean indicating if the email exists on the platform.
- `emailrecovery`: Exposed recovery email, if any.
- `phoneNumber`: Exposed phone number, if any.
- `others`: Additional metadata or null.

## Real-World Implementation Example

All built-in modules follow this contract. In [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) lines 5-7, the implementation demonstrates the expected pattern:

```python
async def twitter(email, client, out):
    # ... perform request with client ...

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

```

You can implement a minimal compliant custom module using the same structure:

```python

# myservice.py (placed under holehe/modules/custom/)

async def myservice(email, client, out):
    name = "myservice"
    domain = "myservice.com"
    
    # Perform HTTP request using the shared client

    response = await client.get(f"https://{domain}/check?email={email}")
    
    out.append({
        "name": name,
        "domain": domain,
        "method": "register",
        "frequent_rate_limit": False,
        "rateLimit": False,
        "exists": response.status_code == 200,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

## Critical Implementation Requirements

Deviating from the expected module function signature in holehe causes the scanning process to fail or silently skip services. Ensure your implementation adheres to these strict rules:

1. **Use `async def`** – The function must be asynchronous since the core engine uses `await` when invoking modules in `launch_module`.

2. **Accept exactly three parameters** – The signature must match `(email, client, out)` to align with the positional arguments passed by `launch_module` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).

3. **Append to `out`, do not return** – The function signature specifies `-> None`. Populate results by calling `out.append()` with the standardized dictionary.

4. **Use the shared client** – Never instantiate separate HTTP clients. Reuse the provided `httpx.AsyncClient` to respect timeout configurations and enable connection pooling across hundreds of concurrent checks.

## Summary

- The expected module function signature in holehe requires: `async def <module_name>(email, client, out)`.
- The core runner in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) lines 66-71 passes an `httpx.AsyncClient` and mutable list to each module.
- Modules must append result dictionaries to `out` containing keys: `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, and `others`.
- All built-in modules in `holehe/modules/` follow this contract, enabling the core engine to execute hundreds of service checks concurrently.

## Frequently Asked Questions

### What happens if a holehe module returns a value instead of appending to `out`?

The holehe core engine ignores return values from module functions. If a module returns a dictionary rather than appending it to the `out` list, the result will not appear in the final output. The `launch_module` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) awaits the coroutine but only checks the contents of the `out` list after execution completes.

### Can I add additional parameters to the module function signature?

No. The core engine invokes every module with exactly three positional arguments: `email`, `client`, and `out`. Adding extra parameters to your function signature will raise a `TypeError` when `launch_module` attempts to call it. If you need additional configuration, use module-level constants or closures rather than extra function arguments.

### Is the `client` parameter always an `httpx.AsyncClient` instance?

Yes. The CLI initialization creates a single `httpx.AsyncClient` configured with the timeout specified by the user, then passes this same instance to every module. This design allows connection reuse across hundreds of concurrent checks while respecting global rate limiting and timeout settings.

### Where should I place new modules to ensure holehe discovers them?

Place new Python files under `holehe/modules/` or any subdirectory within it. The `import_submodules` utility recursively imports all Python files in this directory structure. Each file must expose an async function named identically to the filename (without the .py extension) for the core engine to recognize and execute it during scanning operations.