Required Signature for a Holehe Site Module: Complete Implementation Guide

Every Holehe site module must expose a single asynchronous function accepting three parameters—email, client, and out—and return None while appending a standardized result dictionary to the mutable out list.

Holehe is an open-source email OSINT framework that verifies account existence across hundreds of platforms. To extend its coverage, developers create site modules that plug into the core engine via a strict asynchronous interface. Understanding the required signature for a Holehe site module ensures your code integrates seamlessly with the tool's concurrent execution and result aggregation systems.

The Standardized Asynchronous Signature

Each module in the holehe/modules/ directory must implement a function conforming to this exact signature:

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

This signature enables the orchestration logic in holehe/core.py to uniformly discover and invoke modules using reflection. The core's launch_module function calls each module with await module(email, client, out), passing a shared HTTP client and a mutable list for result collection.

Parameter Breakdown

  • email (str): The target email address to investigate.
  • client (httpx.AsyncClient): A pre-configured asynchronous HTTP client supplied by the core. Modules must use this instance for all requests to ensure proper timeout handling and connection pooling.
  • out (list): A mutable list passed by reference. The module must append a result dictionary to this list; the core aggregates these entries after all modules complete.

Required Result Dictionary Structure

When appending to out, modules must provide a dictionary containing these mandatory keys:

  • name: Internal identifier (typically matches the function name)
  • domain: Target domain (e.g., "twitter.com")
  • method: Verification approach ("register" or "login")
  • frequent_rate_limit: Boolean indicating if the service aggressively rate-limits
  • rateLimit: Boolean indicating if the current request encountered rate limiting
  • exists: Boolean indicating if the email is registered on the service
  • emailrecovery: Optional string containing a password-recovery hint
  • phoneNumber: Optional string with a discovered phone number
  • others: Optional dictionary containing additional metadata (e.g., full name, account creation date)

Real-World Implementation Example

The Twitter module at holehe/modules/social_media/twitter.py demonstrates the canonical implementation:

from holehe.core import *
from holehe.localuseragent import *

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,
            })
    except Exception:
        raise

The function never returns a value; it exclusively mutates out via append. The core's launch_module wrapper catches exceptions and injects standardized error entries, so modules can propagate errors without handling aggregation logic.

Creating a Minimal Custom Module

For a fictional service, implement the same pattern:


# File: holehe/modules/social_media/example.py

from holehe.core import *
from holehe.localuseragent import *

async def example(email, client, out):
    name = "example"
    domain = "example.com"
    method = "register"
    frequent_rate_limit = False

    resp = await client.head(f"https://api.example.com/users/{email}")
    exists = resp.status_code == 200

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

How the Core Engine Invokes Modules

The holehe/core.py file orchestrates execution through several key mechanisms:

  1. Discovery: The import_submodules function dynamically imports all Python files within the holehe/modules package.
  2. Extraction: get_functions retrieves callable objects from imported modules using introspection.
  3. Invocation: launch_module awaits each discovered function with the standard signature, ensuring the shared httpx.AsyncClient and result list propagate correctly.
  4. Concurrency: The framework uses Trio nurseries to execute modules concurrently. The shared out list enables thread-safe result collection without complex synchronization.

This architecture explains why strict adherence to the signature is mandatory—any deviation breaks the reflection-based invocation chain in launch_module.

Summary

  • Site modules must expose an async function accepting email, client, and out parameters exactly as typed.
  • Functions must return None and append standardized result dictionaries to the out list.
  • Required dictionary keys include name, domain, method, exists, rateLimit, and frequent_rate_limit, with optional fields for emailrecovery, phoneNumber, and others.
  • The core engine in holehe/core.py handles module discovery, concurrent execution via Trio, and automatic error handling.
  • Reference holehe/modules/social_media/twitter.py for production-ready implementation patterns.

Frequently Asked Questions

What happens if my module doesn't append to the out list?

The module produces no visible output. Because launch_module in holehe/core.py does not inspect return values, failing to append to out effectively silences the module—the core will not report results for that service.

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

No. Modules must use the injected client parameter to ensure consistent timeout configurations, connection pooling, and integration with the core's rate-limiting instrumentation. Using an independent client bypasses these protections and may trigger IP bans.

How does the core handle exceptions in site modules?

The launch_module function wraps each invocation in a try-except block. When a module raises an exception, the core automatically appends an error entry to the out list and continues execution. This design allows other modules to proceed while preserving diagnostic information for the failed check.

Is the method field limited to specific values?

While the core accepts any string, convention restricts this field to "register" for registration page probes and "login" for authentication endpoint checks. These standardized values help users interpret whether the verification used signup flows or login attempts.

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 →