# What Is the Standard Async Interface for Holehe Modules?

> Discover the standard async interface for Holehe modules. Learn the required function signature and how to integrate your modules effectively with the Holehe framework.

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

---

**Holehe modules must expose an asynchronous function with the exact signature `async def <module_name>(email: str, client: httpx.AsyncClient, out: list) -> None`, performing HTTP requests via the shared client and appending standardized results to the mutable `out` list.**

Holehe is an open-source email OSINT tool that checks if an address is registered across hundreds of websites. Understanding the **standard async interface for Holehe modules** is essential for contributing new service checkers or integrating the library into custom Python workflows. Every module in `holehe/modules/` adheres to a strict three-parameter coroutine contract that enables concurrent execution via Trio.

## The Three-Parameter Coroutine Signature

Every Holehe module exposes a single top-level coroutine that accepts three positional arguments and returns `None`:

- **email** (`str`) – The target email address to investigate.
- **client** (`httpx.AsyncClient`) – A shared async HTTP client configured with user-defined timeouts.
- **out** (`list`) – A mutable list that collects result dictionaries.

The core orchestrator in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) enforces this contract through the `launch_module` helper at lines 66-71, which calls each module with these arguments and catches exceptions to prevent individual failures from crashing the scan.

### Shared HTTP Client for Connection Reuse

Modules must reuse the provided **client** rather than instantiating their own `httpx.AsyncClient`. This design allows the orchestrator to share connection pools across all modules, significantly reducing TCP overhead when running dozens of checks concurrently.

### Output List Mutation Pattern

Instead of returning values directly, modules append a standardized dictionary to the **out** list. This dictionary must include keys such as `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, and `others`.

## Core Orchestration and Concurrent Execution

The execution logic resides in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). To achieve high concurrency, Holehe uses **Trio** to schedule all modules simultaneously. The main event loop opens a nursery and starts each module via `launch_module`:

```python
async with trio.open_nursery() as nursery:
    for website in websites:
        nursery.start_soon(launch_module, website, email, client, out)

```

This pattern appears at lines 19-21 of [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). The uniform interface allows the orchestrator to treat every module identically regardless of the specific service being checked.

## Example Implementation in Existing Modules

Concrete implementations follow this pattern exactly. For example, the Blablacar checker in [`holehe/modules/transport/blablacar.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/transport/blablacar.py) declares its function at line 5 as:

```python
async def blablacar(email, client, out):
    # implementation details...

```

This matches the required signature precisely, allowing the core to discover and execute it automatically.

## Creating a Custom Holehe Module

To add a new service checker, create a Python file in the appropriate subdirectory of `holehe/modules/`:

```python

# holehe/modules/social_media/myservice.py

from holehe.core import *

async def myservice(email, client, out):
    name = "myservice"
    domain = "myservice.com"
    
    resp = await client.get(f"https://api.myservice.com/users/{email}")
    exists = resp.status_code == 200
    
    out.append({
        "name": name,
        "domain": domain,
        "method": "query",
        "frequent_rate_limit": False,
        "rateLimit": False,
        "exists": exists,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

The function name must match the module filename for automatic discovery by the `import_submodules` logic.

## Manual Module Invocation

You can bypass the CLI and invoke modules directly by manually constructing the three required arguments:

```python
import httpx
import trio
import importlib

async def run_one():
    client = httpx.AsyncClient(timeout=10)
    out = []
    
    mod = importlib.import_module("holehe.modules.social_media.twitter")
    await mod.twitter("example@example.com", client, out)
    
    await client.aclose()
    print(out)

trio.run(run_one)

```

This demonstrates how the async interface functions independently of the core orchestrator's nursery management.

## Summary

- Holehe modules implement `async def name(email, client, out)` returning `None`.
- The `client` parameter is a shared `httpx.AsyncClient` that enables connection reuse across concurrent checks.
- Results are appended to the mutable `out` list using a standardized dictionary schema containing fields like `exists`, `rateLimit`, and `emailrecovery`.
- The core orchestrator in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) enforces this interface via `launch_module` (lines 66-71) and schedules concurrent execution using Trio (lines 19-21).
- All modules in `holehe/modules/`, such as [`blablacar.py`](https://github.com/megadose/holehe/blob/main/blablacar.py) at line 5, follow this pattern.

## Frequently Asked Questions

### What happens if a module doesn't follow the standard async interface?

The `launch_module` helper in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) catches exceptions and inserts a standardized error entry into the output list. This prevents one malformed or failing module from crashing the entire scan operation.

### Can I use a different HTTP client instead of httpx.AsyncClient?

No. The interface specifically requires accepting an `httpx.AsyncClient` instance as the second argument. Modules must use this shared client to ensure proper connection pooling and timeout handling as configured by the user.

### Why does the interface use a mutable list instead of return values?

The mutable `out` list design allows the Trio-based orchestrator to collect results from concurrently running tasks without complex synchronization primitives. Each module appends its result dictionary directly, enabling safe aggregation from multiple parallel executions.

### What fields are required in the result dictionary?

The dictionary appended to `out` must include: `name` (service identifier), `domain`, `method` (check type), `frequent_rate_limit` (boolean), `rateLimit` (boolean), `exists` (boolean or None), `emailrecovery`, `phoneNumber`, and `others` (additional metadata).