How to Create a Custom Module in Holehe: Understanding the Plug-In Architecture

Holehe uses a plug-in style module architecture where each email-checking service is an independent Python file in holehe/modules/, automatically discovered at runtime via pkgutil.walk_packages() and executed concurrently using Trio.

The open-source OSINT tool Holehe (megadose/holehe) checks if an email address is registered on hundreds of sites. Its extensible design allows you to add new services without modifying core logic. Understanding the module architecture in Holehe reveals how the tool dynamically imports, orchestrates, and standardizes results from distributed service checks.

How the Module Architecture Works

Holehe’s core engine (holehe/core.py) treats every service as a standalone module. The architecture relies on four distinct phases to discover and execute checks.

Dynamic Discovery via import_submodules

The import_submodules() function traverses the package tree using pkgutil.walk_packages() to find every Python file under holehe/modules/【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L37-L47). This occurs at runtime, meaning any new .py file dropped into the modules directory is automatically imported without explicit registration.

Function Extraction and Registration

Once imported, get_functions() inspects each module and extracts the callable whose name matches the file name—for example, twitter from twitter.py【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L50-L63). Only functions matching their filename are collected into the execution list, enforcing a strict naming convention.

Async Execution with Trio

The launch_module() function executes each discovered callable concurrently using Trio, passing three arguments: the target email, a shared httpx.AsyncClient instance, and a shared out list where results are appended【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L66-L71). This asynchronous pattern allows Holehe to check hundreds of services simultaneously without blocking.

Standardized Result Schema

Every module must append a dictionary to the out list with a fixed set of keys: name, domain, method, frequent_rate_limit, rateLimit, exists, emailrecovery, phoneNumber, and others. The core printer (print_result) consumes this schema to format terminal output and CSV exports【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L106-L149).

Creating a Custom Holehe Module

To add a new service, create a Python file in any sub-directory of holehe/modules/ (e.g., holehe/modules/social_media/myservice.py). The folder structure organizes code by category but does not affect execution logic.

Required Function Signature

Define an async function whose name matches the filename exactly. It must accept three parameters:

async def myservice(email: str, client, out: list):
  • email: The target string to investigate
  • client: A pre-configured httpx.AsyncClient for HTTP requests
  • out: A shared list to which you append the result dictionary

Implementing the Check

Use the shared client for all HTTP requests and import ua from holehe.localuseragent for a realistic User-Agent header. Handle exceptions gracefully to indicate rate limits or errors.


# holehe/modules/social_media/myservice.py

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

async def myservice(email: str, client, out: list):
    """
    Checks whether email is registered on MyService.
    """
    name = "myservice"
    domain = "myservice.com"
    method = "register"               
    frequent_rate_limit = False

    try:
        resp = await client.get(
            "https://api.myservice.com/v1/users/exists",
            params={"email": email},
            headers={"User-Agent": ua}
        )
        exists = resp.json().get("taken", False)

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": exists,
            "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,
        })

Key Implementation Rules

  • File location: Place in any holehe/modules/ subfolder (e.g., products/, social_media/)
  • Naming: Function name must match filename (e.g., myservice() in myservice.py)
  • Async requirement: Function must be declared with async def
  • Result schema: Dictionary must include all required keys; set rateLimit to True when the service blocks requests
  • No registration: import_submodules() automatically discovers the file at startup

After saving, run holehe email@example.com to see your custom module appear in the output and optional CSV export.

Summary

  • Holehe employs a plug-in architecture where service checks reside as independent Python modules under holehe/modules/
  • Dynamic discovery via pkgutil.walk_packages() in holehe/core.py eliminates manual registration
  • Modules must expose an async function matching the filename that accepts (email, client, out) arguments
  • A strict result schema (name, domain, exists, rateLimit, etc.) ensures consistent output formatting across all services
  • Trio-based concurrency executes all modules in parallel, appending results to a shared list for the core printer to process

Frequently Asked Questions

What is the module architecture in Holehe?

Holehe implements a dynamic plug-in system where each service check is a standalone Python file. The import_submodules() function in core.py uses pkgutil.walk_packages() to auto-discover every module under holehe/modules/, while get_functions() extracts callables matching their filenames for execution.

How do I create a custom module in Holehe?

Create a .py file in any subfolder of holehe/modules/ containing an async function with the same name as the file. The function must accept (email, client, out) parameters and append a result dictionary with the required schema to the out list. No manual registration is necessary.

What is the required result schema for Holehe modules?

Every module must append a dictionary containing: name, domain, method, frequent_rate_limit, rateLimit, exists, emailrecovery, phoneNumber, and others. Boolean flags like rateLimit indicate if the service blocked the request, while exists shows registration status.

Do I need to register my custom module manually?

No. Holehe performs runtime discovery of modules using pkgutil.walk_packages(). As long as your file is in holehe/modules/ and your function name matches the filename, holehe/core.py will automatically import and execute it alongside built-in 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 →