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

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:

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 demonstrates this interface in practice. It queries https://api.twitter.com/i/users/email_available.json and constructs the result dictionary:

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:

Module Discovery (import_submodules)

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

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:

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:

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:


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

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 →