How to Create a New Custom Module for Holehe: A Step-by-Step Developer's Guide

To create a custom module for Holehe, add an async function with the signature async def <service_name>(email, client, out): to a new Python file under holehe/modules/<category>/, following the standardized result dictionary schema used by existing modules.

Holehe discovers whether an email address has been used on various online services through a modular architecture. Each service is implemented as a module that Holehe dynamically loads at runtime. According to the megadose/holehe source code, you can extend this email reconnaissance tool by creating properly structured modules that the framework automatically discovers and executes.

Understanding Holehe's Module System

Holehe's dynamic loading mechanism is implemented in holehe/core.py. When the tool starts, it executes two key functions:

modules = import_submodules("holehe.modules")        # holehe/core.py

websites = get_functions(modules, args)               # holehe/core.py

The import_submodules function uses pkgutil.walk_packages to traverse the package tree and load every Python file. Then get_functions extracts the callable (async function) from each module. These callables are scheduled for concurrent execution using Trio.

This architecture means no registration step is required—simply placing a correctly structured file in the modules directory makes your service immediately available.

Module Requirements and Structure

File Location

Place your module in the appropriate category folder under holehe/modules/:

  • holehe/modules/social_media/ — platforms like Instagram, Twitter, Facebook
  • holehe/modules/shopping/ — e-commerce sites
  • holehe/modules/programing/ — developer-focused services
  • holehe/modules/forum/ — community forums
  • holehe/modules/mail/ — email services
  • holehe/modules/porn/ — adult content platforms

The folder name becomes part of the import path (e.g., holehe.modules.social_media.yourservice).

Required Function Signature

Every module must define exactly one async function matching the filename:

async def <service_name>(email, client, out):
Parameter Type Description
email str The target email address to check
client httpx.AsyncClient Shared HTTP client for making requests
out list List to which you append the result dictionary

Standardized Result Dictionary

Your function must append a dictionary to out with these exact keys (as used by print_result in holehe/core.py):

Key Type Purpose
name str Service identifier (matching function name)
domain str The service's primary domain
method str Optional endpoint description (e.g., "register", "login")
frequent_rate_limit bool True if the service often throttles requests
rateLimit bool True if this specific request was rate-limited
exists bool True if the email is registered on the service
emailrecovery str or None Recovered recovery email (if exposed)
phoneNumber str or None Recovered phone number (if exposed)
others dict or None Additional extracted metadata

Complete Custom Module Example

This template mirrors the structure of the built-in Instagram module (holehe/modules/social_media/instagram.py):


# holehe/modules/social_media/example_service.py

from holehe.core import *          # Optional: imports helpers

from holehe.localuseragent import *  # Random user-agent list

async def example_service(email, client, out):
    """Check if <email> is registered on ExampleService."""
    name = "example_service"
    domain = "example.com"
    method = "register"
    frequent_rate_limit = False

    # Build request headers with randomized browser fingerprint

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

    # Handle network failures gracefully

    try:
        resp = await client.get(
            f"https://api.example.com/users/exists?email={email}",
            headers=headers,
        )
    except Exception:
        # Network error → mark as rate-limited/unknown

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

    # Parse response and populate result

    if resp.status_code == 200 and resp.json().get("exists"):
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": True,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
    else:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

POST-Based Module Example

For services requiring form submission:


# holehe/modules/forum/myforum.py

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

async def myforum(email, client, out):
    name = "myforum"
    domain = "myforum.org"
    method = "login"
    frequent_rate_limit = False

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Content-Type": "application/x-www-form-urlencoded",
    }

    payload = {"login": email, "password": "random"}

    try:
        resp = await client.post(
            "https://myforum.org/api/check_user",
            data=payload,
            headers=headers,
        )
    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,
        })
        return

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

Essential Implementation Guidelines

Use the shared HTTP client. Always use the provided client parameter (an httpx.AsyncClient instance) rather than creating your own. This ensures proper connection pooling and Trio compatibility.

Randomize user agents. Import from holehe/localuseragent.py and select randomly: random.choice(ua["browsers"]["chrome"]). This mimics real browser traffic and reduces blocking.

Handle all exceptions. Wrap network calls in try/except and append a result with "rateLimit": True on failure. See lines 71-78 of instagram.py for the pattern used when unexpected exceptions occur.

Avoid import side effects. Your module should not execute code during import—only the async function runs during Holehe's execution phase.

Match function and filename names exactly. The get_functions loader extracts module.__dict__[site] where site is the filename without extension. Mismatched names cause the module to be silently skipped.

Verifying Your Custom Module

After creating your file, run Holehe with your target email:

holehe target@example.com

Your service appears automatically—no rebuild or configuration required. The dynamic loader in holehe/core.py discovers it on the next execution.

Key Source Files Reference

File Role
holehe/core.py Entry point with import_submodules, get_functions, and result printing
holehe/modules/social_media/instagram.py Reference implementation showing error handling and response parsing
holehe/localuseragent.py Random user-agent strings for request headers

Summary

  • Location matters: Place files under holehe/modules/<category>/ with matching function and filename names.
  • Signature is strict: Use async def <name>(email, client, out): exactly.
  • Result dict is standardized: Include all required keys, especially rateLimit, exists, and frequent_rate_limit.
  • No registration needed: The dynamic importer in holehe/core.py discovers modules automatically.
  • Defensive programming: Handle network exceptions, randomize headers, and set appropriate boolean flags for UI display.

Frequently Asked Questions

What happens if my function name doesn't match the filename?

Holehe will not detect your module. The get_functions function in holehe/core.py looks up module.__dict__[site] where site is derived from the filename. A mismatch causes silent skipping—your module simply won't appear in results.

Can I use synchronous HTTP libraries like requests?

No. Holehe uses Trio for async concurrency, so your module must use the provided httpx.AsyncClient via await client.get() or await client.post(). Synchronous calls would block the entire event loop and break concurrent execution.

How do I indicate that a service frequently rate-limits requests?

Set "frequent_rate_limit": True in your result dictionary. This flag appears in Holehe's output to warn users that results may be unreliable. Distinguish this from "rateLimit": True, which indicates the current specific request was throttled.

Why does my module need to append to out instead of returning a value?

Holehe schedules modules concurrently using Trio nurseries. The out list acts as a thread-safe collector for results from all running checks. Appending ensures your result is captured even when multiple services execute simultaneously.

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 →