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

> Learn to create a software module for holehe by writing async Python functions that perform HTTP requests and return standardized results. Follow this developer guide.

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

---

**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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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:

```text
holehe/modules/software/<servicename>.py

```

For example, [`holehe/modules/software/examplecloud.py`](https://github.com/megadose/holehe/blob/main/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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
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`](https://github.com/megadose/holehe/blob/main/holehe/modules/software/examplecloud.py):

```python
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:

```bash
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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/holehe/modules/software/office365.py)** (lines 6-15) and **[`holehe/modules/social_media/facebook.py`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/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.