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

> Easily add a new service module to Holehe by creating a Python file implementing an async function and returning a standard result. No manual registration needed. Get the step-by-step guide.

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

---

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

```python

# 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:

```python

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

```python

# 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:

```bash
touch holehe/modules/social_media/example.py

```

The filename [`example.py`](https://github.com/megadose/holehe/blob/main/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:

```python

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

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

```python

# 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`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | 37-47 | `import_submodules()` — recursively imports all modules under `holehe.modules` |
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | 50-63 | `get_functions()` — extracts functions matching module names |
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | 66-71 | `launch_module()` — executes each function with `(email, client, out)` signature |
| [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | — | Canonical reference implementation showing real-world patterns |

## Summary

- Holehe uses **dynamic import** in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/holehe/core.py) (lines 50-63) specifically looks for an attribute matching the final segment of the module path. A file named [`reddit.py`](https://github.com/megadose/holehe/blob/main/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`](https://github.com/megadose/holehe/blob/main/__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`](https://github.com/megadose/holehe/blob/main/twitter.py) reference and the example above. Check your request URL, response parsing, and exception handling logic to identify the actual failure.