# How to Add a New Website Module to Holehe: Complete Developer Guide

> Learn to add a new website module to Holehe. Follow this developer guide to create modules, register mappings, and enhance email verification for your projects.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-08-28

---

**To add a new website module to Holehe, create an async Python file in `holehe/modules/<category>/` with a function matching the filename, register the domain mapping in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), and the core engine will automatically discover and execute it during email verification runs.**

Holehe (megadose/holehe) is an open-source email investigation tool that checks whether an address is registered across hundreds of services. Its modular architecture allows you to add a new website module to Holehe without altering the core execution logic, leveraging dynamic module discovery via `import_submodules()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).

## Understanding the Module Architecture

Holehe dynamically loads module files from the `holehe/modules/` directory tree. The core engine uses `pkgutil.walk_packages` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 37-47) to recursively discover all Python modules at runtime. Each module must implement exactly one async function named identically to the file (excluding the `.py` extension).

The function signature must conform to:

```python
async def <module_name>(email, client, out):

```

- **`email`**: The target email address string to investigate.
- **`client`**: An `httpx.AsyncClient` instance for performing asynchronous HTTP requests.
- **`out`**: A list that collects result dictionaries for aggregation by the main process.

The `launch_module()` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 66-68) contains a `data` dictionary that maps module names to human-readable domains for output formatting.

## Step 1: Create the Module File

Create a new Python file in the appropriate category subdirectory (e.g., [`holehe/modules/social_media/example.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/example.py)). The filename determines the function name and module identifier.

Reference the **Twitter** implementation ([`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py), lines 5-37) for the standard structure:

```python

# holehe/modules/social_media/example.py

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

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

    try:
        req = await client.get(
            "https://api.example.com/check_email",
            params={"email": email}
        )
        
        if req.json().get("taken"):
            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,
            })
    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,
        })

```

**Critical requirements:**

- **Function name** must exactly match the filename without the `.py` extension.
- **Result dictionary** must contain all required keys: `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, and `others`.
- **Exception handling** must catch errors and append a result with `rateLimit: True` (or appropriate error state) to prevent the module from crashing the entire scan.

## Step 2: Register the Domain Mapping

Locate the `data` dictionary inside `launch_module()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (approximately lines 66-68). Insert a new key-value pair where the key matches your module filename and the value is the service's domain:

```python

# holehe/core.py - inside launch_module()

data = {
    # ... existing entries ...

    "example": "example.com",
}

```

This mapping ensures that when results are displayed, they correctly identify the service associated with your module.

## Step 3: Update Project Documentation

While optional, adding the new service to the README.md table of supported services helps users discover the capability and maintains project transparency.

## Testing Your New Module

After placing the file in the modules directory:

1. Run Holehe with a test email to verify the module imports without errors.
2. Check that HTTP requests return the expected status codes and response bodies.
3. Validate that the `out` list receives dictionaries with correct `exists` boolean values.
4. Verify the domain mapping appears correctly in the final output.

If the module does not execute, confirm that the function name matches the filename exactly and that all result dictionary keys are present.

## Summary

- Place new modules in `holehe/modules/<category>/<servicename>.py` with the function name matching the filename exactly.
- Ensure the async function accepts parameters `email`, `client`, and `out` in that order.
- Append standardized result dictionaries to the `out` list containing all nine required keys.
- Register the domain mapping in the `data` dictionary inside `launch_module()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).
- The `import_submodules()` function automatically discovers and loads new files at runtime without requiring core code modifications.

## Frequently Asked Questions

### What happens if my function name does not match the filename?

Holehe will raise an **AttributeError** during execution. The `launch_module()` function attempts to call a function with the same name as the module file (without the `.py` extension). If the names differ, the lookup fails and the module is skipped with an error.

### Can I place modules in any subdirectory under `holehe/modules/`?

Yes. The `import_submodules()` function uses `pkgutil.walk_packages` to recursively scan all subdirectories (such as `social_media`, `shopping`, or `gaming`). While you can use any directory structure, organizing modules by service category improves maintainability.

### How should I handle API keys or authentication tokens?

Store credentials in environment variables or a separate configuration file excluded from version control. Reference these variables in your module code when constructing HTTP requests. Never hardcode sensitive tokens directly in the module file, especially if contributing back to the megadose/holehe repository.

### What is the difference between `rateLimit` and `frequent_rate_limit`?

Set `frequent_rate_limit=True` in the result dictionary if the service consistently imposes strict rate limiting across all requests. Set `rateLimit=True` when a specific request encounters a temporary block or quota violation during execution. This distinction allows users to differentiate between temporary failures and definitive "email not found" results.