# How to Implement the Standard Module Interface for a New Website in Holehe

> Learn to implement the standard module interface for your new website in Holehe. Discover how to create a Holehe module with a single async function, httpx client, and standardized results.

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

---

**A Holehe module is a single async function placed in `holehe/modules/` that receives an email, an httpx client, and an output list, then appends a standardized result dictionary.**

Holehe is an OSINT tool that discovers where an email address has been registered across hundreds of websites. Adding support for new services requires understanding its **standard module interface**—a strict contract between your code and the core runner. This guide walks through implementing that interface based on the actual source code in `megadose/holehe`.

## How Holehe Discovers and Runs Modules

The core runner in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) dynamically loads every module without hardcoded lists. Two functions make this possible:

- `import_submodules` ([lines 37–48](https://github.com/megadose/holehe/blob/master/holehe/core.py#L37)) traverses `holehe.modules` using `pkgutil.walk_packages`
- `get_functions` ([lines 50–63](https://github.com/megadose/holehe/blob/master/holehe/core.py#L50)) extracts callable coroutines from imported modules

Modules run inside a Trio nursery via `launch_module` ([lines 66–78](https://github.com/megadose/holehe/blob/master/holehe/core.py#L66)), which handles exceptions by converting them to rate-limit entries.

## Required Elements of the Standard Module Interface

### File Location and Structure

Place your module in an appropriate subpackage under `holehe/modules/`. Common categories include `social_media`, `shopping`, `programming`, and `products`. The file name becomes the function name.

```bash
holehe/
└── modules/
    └── social_media/
        └── example.py   # Defines async def example(...)

```

### Mandatory Imports

Start with these imports to access core constants and utilities:

```python
from holehe.core import *
from holehe.localuseragent import *  # Only if you need random User-Agents

```

### Function Signature

Define exactly one coroutine with this signature ([core.py](https://github.com/github.com/megadose/holehe/blob/master/holehe/core.py) expects this pattern):

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

```

| Parameter | Type | Purpose |
|-----------|------|---------|
| `email` | `str` | Target email address to check |
| `client` | `httpx.AsyncClient` | Preconfigured async HTTP client from the core runner |
| `out` | `list` | Mutable list where you **must** append the result dictionary |

### Result Dictionary Schema

Append a dictionary with these **exact keys** ([twitter.py lines 17–23](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/twitter.py#L17)):

| Key | Type | Description |
|-----|------|-------------|
| `name` | `str` | Site identifier (usually matches function name) |
| `domain` | `str` | Domain of the service (e.g., `"twitter.com"`) |
| `method` | `str` | HTTP usage pattern: `"register"`, `"login"`, `"recovery"`, etc. |
| `frequent_rate_limit` | `bool` | Hint that this service aggressively rate-limits |
| `rateLimit` | `bool` | `True` if current request was blocked/errored |
| `exists` | `bool` | `True` if email is registered on this service |
| `emailrecovery` | `str` `| None` | Partial email from recovery flow, if exposed |
| `phoneNumber` | `str` `| None` | Phone number hint, if exposed |
| `others` | `any` | Additional metadata from the response |

## Complete Implementation Steps

1. **Create the file** at `holehe/modules/<category>/<sitename>.py`
2. **Copy a working template** from Twitter or Instagram
3. **Define `async def <sitename>(email, client, out)`** with exact parameter names
4. **Implement HTTP logic** using the supplied `client` (GET/POST as needed)
5. **Parse the response** to determine registration status
6. **Append to `out`** with all required dictionary keys
7. **Handle exceptions** by setting `rateLimit=True` and `exists=False`

## Minimal Working Example

```python

# 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:
        resp = await client.get(
            "https://api.example.com/check_email",
            params={"email": email},
            headers={"User-Agent": await get_random_user_agent()},
        )
        data = resp.json()
        
        # Adjust condition to match service's response format

        exists = data.get("email_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:
        # Any failure becomes a rate-limit entry

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

```

## Advanced Pattern: Handling CSRF Tokens and Sessions

For services requiring session state, examine the Instagram module ([instagram.py lines 11–74](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/instagram.py)):

```python
async def instagram(email, client, out):
    # ... setup constants ...

    
    headers = {
        "User-Agent": random.choice(ua),
        "Accept": "*/*",
        # ... additional headers

    }
    
    # First request: fetch CSRF token

    try:
        await client.get("https://www.instagram.com/", headers=headers)
        # Extract csrftoken from cookies...

        
        # Second request: actual check with token

        data = {
            "email": email,
            # ... other fields

        }
        response = await client.post(
            "https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
            data=data,
            headers=headers,
        )
        # Parse response branches for exists/rateLimit cases

    except Exception:
        # Return rate-limit structure

        pass

```

## Key Implementation Rules

- **Never print or write files** — output only via `out.append()`
- **Never modify global state** — the function must be reentrant
- **Always catch exceptions** — uncaught errors break the Trio nursery
- **Use `client`, don't create your own** — the core manages connection pooling and limits
- **Match the function name to the file name** — `get_functions` relies on this convention

## Core Source Files Reference

| File | Purpose in Module Interface |
|------|----------------------------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | `import_submodules` (L37–48), `get_functions` (L50–63), `launch_module` (L66–78) |
| [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) | Package marker enabling `pkgutil.walk_packages` traversal |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | `get_random_user_agent()` for request headers |
| [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | Reference implementation showing minimal valid module |
| [`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py) | Complex example with CSRF tokens and POST handling |

## Summary

- The **standard module interface** requires one async function with signature `(email, client, out)`
- Place files in `holehe/modules/<category>/<name>.py` and name the function `<name>`
- **Must append** a dictionary with 9 specific keys: `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, `others`
- Handle all exceptions by setting `rateLimit=True` and `exists=False`
- No side effects: use only the `out` list for output, never print or write files
- The core automatically discovers new modules via `import_submodules` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)

## Frequently Asked Questions

### What happens if my module raises an uncaught exception?

The `launch_module` wrapper ([core.py L66–78](https://github.com/megadose/holehe/blob/master/holehe/core.py#L66)) catches all exceptions and converts them to a rate-limit result. Your module won't crash the runner, but you lose diagnostic information. Always wrap HTTP logic in try/except blocks to distinguish true rate limits from parsing errors.

### Can I use synchronous libraries like requests in my module?

No. The core runner uses Trio for concurrency and passes an `httpx.AsyncClient`. Blocking calls would freeze the entire nursery. Use `await client.get()`/`await client.post()` exclusively. For CPU-bound parsing, consider `trio.to_thread.run_sync`.

### How do I test my module without running the full Holehe suite?

Import your function directly and mock the dependencies:

```python
import httpx
import trio

async def test_example():
    out = []
    async with httpx.AsyncClient() as client:
        await your_module.example("test@example.com", client, out)
    print(out[0])

trio.run(test_example)

```

This validates your interface compliance before submitting.