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

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:

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 lines 66-71, the launch_module function enforces this contract by calling each module with these three arguments:

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 lines 5-7, the implementation demonstrates the expected pattern:

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:


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

  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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →