How to Create a Software Module for Holehe: A Developer’s Guide

To create a software module for holehe, write an async Python function in holehe/modules/<category>/<service>.py that accepts (email, client, out) parameters, performs an HTTP request using the provided httpx.AsyncClient, and appends a standardized result dictionary to the out list.

Holehe is an open-source email investigation tool that checks whether an address is registered across thousands of online services. The tool uses a modular architecture where each service is implemented as an async Python function in the holehe/modules/ directory. When you create a software module for holehe following the established patterns in holehe/core.py, the framework automatically discovers and executes your code without requiring manual registration.

Understanding the Auto-Discovery Mechanism

The core loading mechanism resides in holehe/core.py and handles module discovery dynamically.

The import_submodules() function (lines 37-48) recursively walks the holehe.modules package tree and imports every Python file it encounters. This means any new module you place in the directory structure is automatically loaded at runtime. Subsequently, launch_module() (lines 66-70) executes each discovered function with the standard (email, client, out) signature.

Because of this architecture, you do not need to edit a central registry or configuration file to add a new service. Simply placing your Python file in the correct subdirectory and implementing the required function signature enables immediate integration.

Step-by-Step Guide to Creating a Module

Select a Category and File Location

Modules are organized by domain type within holehe/modules/. For software-as-a-service (SaaS) tools, use the software subpackage.

Create a new file at:

holehe/modules/software/<servicename>.py

For example, holehe/modules/software/examplecloud.py would create a module named examplecloud.

Implement the Required Function Signature

Every module must define an async function matching the filename with this exact signature:

async def examplecloud(email, client, out):
    """Check if email exists on ExampleCloud."""
    name = "ExampleCloud"
    domain = "examplecloud.com"
    method = "register"
    frequent_rate_limit = False

The parameters are:

  • email: The target email address as a string
  • client: An httpx.AsyncClient instance for making HTTP requests (shared across all modules for connection pooling)
  • out: A shared list that collects result dictionaries from all modules

Build HTTP Requests with Utilities

Import the randomized User-Agent list from the core utilities to avoid detection:

import random
from holehe.localuseragent import ua

headers = {
    "User-Agent": random.choice(ua["browsers"]["chrome"]),
    "Accept": "application/json, text/plain, */*",
    "Accept-Language": "en-US,en;q=0.5",
}

Use the provided client to make asynchronous requests:

response = await client.get(
    "https://api.examplecloud.com/v1/users/exists",
    params={"email": email},
    headers=headers,
)

Parse Responses and Append Results

Interpret the HTTP response to determine registration status, then append a standardized dictionary to the out list:

data = response.json()
exists = data.get("registered", 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,
})

Handle errors and rate limiting in an exception block:

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

Complete Minimal Example

Here is a fully functional module for a fictional SaaS platform. Save this as holehe/modules/software/examplecloud.py:

import random
from holehe.localuseragent import ua

async def examplecloud(email, client, out):
    """Check if an email is registered on ExampleCloud."""
    name = "ExampleCloud"
    domain = "examplecloud.com"
    method = "register"
    frequent_rate_limit = False

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
        "Accept-Language": "en-US,en;q=0.5",
    }

    try:
        r = await client.get(
            "https://api.examplecloud.com/v1/users/exists",
            params={"email": email},
            headers=headers,
        )
        data = r.json()
        exists = data.get("registered", 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,
            "error": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

Run the tool to see your module in action:

holehe user@example.com

The output table will include your new service alongside existing checks like facebook and docker, formatted by the print_result() function in holehe/core.py (lines 22-49).

Key Conventions and Result Schema

When you create a software module for holehe, adhere to this result schema for consistency:

Key Type Description
name string Human-readable service name (e.g., "ExampleCloud")
domain string Base domain of the service (e.g., "examplecloud.com")
method string Action performed (e.g., "register", "login")
frequent_rate_limit boolean True if the service commonly returns HTTP 429
rateLimit boolean True if the current request was throttled
exists boolean True if the email is registered, False otherwise
emailrecovery string/null Partially masked recovery email if exposed by the API
phoneNumber string/null Partial phone number if exposed by the API
others any/null Additional metadata extracted from the response

Reference implementations demonstrating these patterns include holehe/modules/software/office365.py (lines 6-15) and holehe/modules/social_media/facebook.py (lines 6-14), which handle CSRF tokens and complex authentication flows while maintaining the same output structure.

Summary

  • Auto-discovery: The import_submodules() function in holehe/core.py automatically loads any Python file placed in holehe/modules/ or its subdirectories.
  • Function signature: Implement async def servicename(email, client, out) using the provided httpx.AsyncClient for all HTTP operations.
  • Result format: Append a dictionary to the out list containing standardized keys: name, domain, exists, rateLimit, and optional fields like emailrecovery.
  • Error handling: Set "rateLimit": True or "error": True in the result dictionary when requests fail or return HTTP 429.
  • Utilities: Use from holehe.localuseragent import ua to access randomized browser User-Agents for request headers.

Frequently Asked Questions

Do I need to register my module manually after creating the file?

No. According to the source code in holehe/core.py, the import_submodules() function (lines 37-48) recursively imports all Python files in the holehe.modules package tree automatically. As long as your file is in the correct location and contains the properly named async function, holehe will discover and execute it without any registry edits.

What HTTP client should I use inside my module?

You must use the client parameter passed to your function, which is an httpx.AsyncClient instance shared across all modules. This client handles connection pooling and proxy settings configured by the user. Do not create your own HTTP client instances.

How do I handle services that implement strict rate limiting?

Set frequent_rate_limit = True at the top of your function to inform users that the service commonly blocks requests. If you encounter an HTTP 429 or connection timeout during execution, catch the exception and append a result with "rateLimit": True instead of crashing. See the Facebook module at holehe/modules/social_media/facebook.py for a robust implementation of this pattern.

Can my module return additional data beyond the boolean exists check?

Yes. While the exists field is required, you can populate emailrecovery, phoneNumber, and others with data extracted from the API response. These fields are displayed in the final output if they contain non-null values, allowing your module to expose recovery information or profile metadata when the target service leaks it.

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 →