How to Add a New Service Module to Holehe: A Step-by-Step Guide

To add a new service module to Holehe, create a Python file in the appropriate category under holehe/modules/, implement an async function matching the filename, and return a standardized result dictionary—no manual registration is required.

Holehe discovers and executes all service checks through dynamic module importing. The framework automatically walks the holehe.modules package tree, extracts functions from each file, and runs them against target emails. This architecture means you can extend Holehe's capabilities without modifying core code.

How Holehe Discovers Service Modules

Understanding the discovery mechanism helps you implement modules correctly. The process happens in three stages, all defined in holehe/core.py.

Dynamic Import via import_submodules

The import_submodules function (lines 37-47) recursively walks the holehe.modules package and imports every Python file it finds:


# From holehe/core.py

def import_submodules(package, recursive=True):
    """Import all submodules of a module, recursively."""
    results = {}
    # Walks package.__path__ and imports each submodule

    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

This means any .py file you add under holehe/modules/ or its subdirectories gets loaded automatically.

Function Extraction via get_functions

After import, get_functions (lines 50-63) extracts the callable that matches the module's filename:


# From holehe/core.py

def get_functions(modules):
    """Extract functions matching module names from imported modules."""
    functions = []
    for module_name, module in modules.items():
        # Extracts 'twitter' from 'holehe.modules.social_media.twitter'

        name = module_name.split('.')[-1]
        if hasattr(module, name):
            functions.append(getattr(module, name))
    return functions

Critical requirement: The function name must exactly match the filename (without .py). A file named reddit.py must define async def reddit(...).

Async Execution via launch_module

The collected functions are invoked with a fixed signature in launch_module (lines 66-71):


# Expected signature: (email, client, out)

await function(email, client, out)
  • email: The target email string
  • client: An httpx.AsyncClient instance for HTTP requests
  • out: A list to which you append your result dictionary

Step-by-Step: Creating a New Service Module

Follow these six steps to add a service check to Holehe.

1. Choose the Appropriate Category

Review the existing structure under holehe/modules/:


holehe/modules/
├── social_media/
├── shopping/
├── forum/
├── productivity/
└── ...

Select the category that best fits your target service. For a new social platform, use social_media/.

2. Create the Module File

Add a new file with a lowercase, no-space name matching the service:

touch holehe/modules/social_media/example.py

The filename example.py dictates that your function must be named example.

3. Implement the Async Function

Define async def example(email, client, out) with the exact signature expected by launch_module. Import required dependencies from Holehe's core:


# holehe/modules/social_media/example.py

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

4. Query the Service Endpoint

Use the provided httpx.AsyncClient to make requests. Pattern your implementation after existing modules like holehe/modules/social_media/twitter.py.

5. Build and Append the Result Dictionary

Your function must append a dictionary with all required keys to the out list. Missing keys will cause the output printer to fail.

6. Verify Automatic Discovery

Run Holehe against any email. Your new service appears in the output without any configuration changes:

holehe test@example.com

Required Result Dictionary Structure

Every service module must return a dictionary with exactly these keys:

Key Type Description
name str Service identifier displayed in output
domain str Service domain (e.g., "twitter.com")
method str Detection method used (e.g., "register", "old_profile")
frequent_rate_limit bool Whether the service commonly rate-limits requests
rateLimit bool True if this specific request hit a rate limit
exists bool True if the email is registered on the service
emailrecovery str or None Recovery email if exposed by the service
phoneNumber str or None Phone number if exposed by the service
others any Additional metadata the service returns

Complete Example: Adding a Dummy Service

This implementation demonstrates the full pattern. Replace the URL and parsing logic with your target service's actual API:


# 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

    try:
        headers = {
            "User-Agent": random.choice(ua["browsers"]["chrome"]),
            "Accept": "application/json"
        }
        
        resp = await client.get(
            "https://api.example.com/v1/users/check_email",
            headers=headers,
            params={"email": email},
            timeout=10
        )
        
        data = resp.json()
        exists = data.get("user_exists", False)

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": exists,
            "emailrecovery": data.get("recovery_email"),
            "phoneNumber": data.get("verified_phone"),
            "others": {"user_id": data.get("id")}
        })
        
    except Exception as e:
        # On any exception, report rate limit and unknown existence

        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 Guidelines

  • Function naming: Must match filename exactly (twitter.py → def twitter()). Case-sensitive.
  • Exception handling: Always wrap requests in try/except. On failure, set rateLimit=True and exists=False.
  • No manual imports: Never add import example statements anywhere. The dynamic loader handles registration.
  • Async required: All service functions must be async—the framework uses await to invoke them.

Reference: Core Files in Holehe

File Lines Purpose
holehe/core.py 37-47 import_submodules() — recursively imports all modules under holehe.modules
holehe/core.py 50-63 get_functions() — extracts functions matching module names
holehe/core.py 66-71 launch_module() — executes each function with (email, client, out) signature
holehe/modules/social_media/twitter.py — Canonical reference implementation showing real-world patterns

Summary

  • Holehe uses dynamic import in holehe/core.py to discover all modules under holehe.modules automatically
  • Create new service files in the appropriate category directory with matching function and filename names
  • Implement async def servicename(email, client, out) using the fixed three-parameter signature
  • Return a complete result dictionary with all nine required keys to avoid output errors
  • No registration step exists—save the file and run Holehe to test immediately

Frequently Asked Questions

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

Holehe will not detect your service. The get_functions implementation in holehe/core.py (lines 50-63) specifically looks for an attribute matching the final segment of the module path. A file named reddit.py must contain async def reddit(...), or it will be silently skipped during discovery.

Can I add a new category directory, or must I use existing ones?

You can create new category directories. The import_submodules function recursively walks all subpackages under holehe.modules. Simply create a new folder (e.g., holehe/modules/gaming/) with an __init__.py file and add your service modules there. The dynamic loader will find them automatically.

How should I handle services that require authentication or API keys?

Holehe's architecture passes the same httpx.AsyncClient to all modules, so you can configure custom headers or authentication in your module. However, there's no built-in credential management system. Store API keys as module-level constants or use environment variables, following the pattern in existing modules that require special headers.

Why does my new module show rateLimit=True even when the service isn't rate-limiting?

This indicates an uncaught exception in your implementation. The standard error-handling pattern sets rateLimit=True and exists=False for any exception, as shown in the twitter.py reference and the example above. Check your request URL, response parsing, and exception handling logic to identify the actual failure.

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 →