# Holehe Module Structure: A Complete Developer Guide

> Understand the holehe module structure for async email probing. Learn how self-contained Python files interact with online services. A complete developer guide for megadose/holehe.

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

---

**A holehe module is a self-contained Python file that implements an async function to probe a specific online service for email existence, following a strict contract defined in the megadose/holehe repository.**

Holehe is an open-source OSINT (Open Source Intelligence) tool that checks whether email addresses are registered on hundreds of online platforms. Understanding the holehe module structure is essential for security researchers and developers who want to extend the tool with new services or debug existing email verification probes.

## File Location and Package Organization

All holehe modules reside within the `holehe.modules` package, organized by thematic categories such as `social_media`, `software`, and `shopping`.

The standard file path follows this pattern:

```

holehe/modules/<category>/<service>.py

```

For example, the Instagram probe lives at [`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py), while e-commerce checks reside in `holehe/modules/shopping/`. The [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) file marks this directory as a Python package and enables the dynamic import mechanism used by the core engine to discover probes at runtime.

## Required Imports

Every module must begin with two specific wildcard imports:

- `from holehe.core import *` – Imports shared utilities and base exceptions from the core framework
- `from holehe.localuseragent import *` – Imports the User-Agent rotation dictionary

These imports provide access to `random.choice(ua["browsers"]["chrome"])` for rotating User-Agent strings and the shared exception handling required by the framework.

## The Async Function Contract

### Function Signature

The core of every holehe module is a single async function matching the filename. In [`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py), the function is defined as:

```python
async def instagram(email, client, out):

```

The function accepts three parameters:

- `email` – The target email address as a string
- `client` – A shared `httpx.AsyncClient` instance for concurrent HTTP requests
- `out` – A mutable list where the module appends its results

### Static Metadata Fields

Inside the function, you must define four static variables that describe the service:

```python
name = "instagram"
domain = "instagram.com"
method = "register"
frequent_rate_limit = True

```

- `name` – The service identifier used in reporting
- `domain` – The primary domain of the service
- `method` – The verification approach, typically `"register"` or `"password_reset"`
- `frequent_rate_limit` – Boolean flag indicating whether the service aggressively rate-limits requests

## Implementing the Network Probe

### Crafting Requests

Modules craft HTTP headers and payloads that mimic legitimate browser behavior. Use the random User-Agent helper to avoid detection:

```python
headers = {
    "User-Agent": random.choice(ua["browsers"]["chrome"]),
    "Accept": "application/json, text/plain, */*",
    "Origin": "https://www.instagram.com",
    "DNT": "1",
    "Connection": "keep-alive",
}

```

For services requiring CSRF tokens or session cookies, perform preliminary GET requests to extract these values before submitting the email verification request.

### Executing HTTP Calls

Use the provided `client` parameter for all network operations to ensure proper connection pooling and timeout handling:

```python
freq = await client.get(
    "https://www.instagram.com/accounts/emailsignup/", 
    headers=headers
)
token = freq.text.split('{\\"config\\":{\\"csrf_token\\":\\"')[1].split('\\"')[0]

```

Submit the probe request (typically POST) with the target email embedded in the payload:

```python
data = {
    "email": email,
    "username": "".join(random.choice(string.ascii_lowercase) for _ in range(8)),
    "first_name": "",
    "opt_into_one_tap": "false",
}
headers["x-csrftoken"] = token

check = await client.post(
    "https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
    data=data,
    headers=headers,
)

```

### Handling Responses and Building Output

Parse the HTTP response to determine the email status. Append a standardized dictionary to the `out` list with exactly these nine keys:

```python
out.append({
    "name": name,
    "domain": domain,
    "method": method,
    "frequent_rate_limit": frequent_rate_limit,
    "rateLimit": False,        # True if blocked by rate limiting

    "exists": True,            # True if email is registered

    "emailrecovery": None,     # Recovery email if exposed

    "phoneNumber": None,       # Phone number if exposed

    "others": None             # Dictionary for additional metadata

})

```

## Error Handling and Rate Limits

Wrap all network logic in try-except blocks to prevent individual module failures from crashing the entire scan. When exceptions occur or unexpected HTTP responses are received, append a rate-limit entry:

```python
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
    })

```

This ensures the main engine in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) can continue execution even if a specific service is offline or has changed its API.

## Complete Module Example

Here is the full structure of [`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py), demonstrating the complete implementation pattern:

```python

# holehe/modules/social_media/instagram.py

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

async def instagram(email, client, out):
    name = "instagram"
    domain = "instagram.com"
    method = "register"
    frequent_rate_limit = True

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
        "Origin": "https://www.instagram.com",
        "DNT": "1",
        "Connection": "keep-alive",
    }

    # Obtain CSRF token

    freq = await client.get(
        "https://www.instagram.com/accounts/emailsignup/", headers=headers
    )
    token = freq.text.split('{\\"config\\":{\\"csrf_token\\":\\"')[1].split('\\"')[0]

    data = {
        "email": email,
        "username": "".join(
            random.choice(string.ascii_lowercase + string.digits) 
            for _ in range(random.randint(6, 30))
        ),
        "first_name": "",
        "opt_into_one_tap": "false",
    }
    headers["x-csrftoken"] = token

    # Submit registration attempt

    check = await client.post(
        "https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
        data=data,
        headers=headers,
    )
    check = check.json()

    # Interpret response and record result

    if check["status"] != "fail":
        if "email" in check["errors"]:
            if check["errors"]["email"][0]["code"] == "email_is_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
                })
    else:
        out.append({
            "name": name, 
            "domain": domain, 
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True, 
            "exists": False,
            "emailrecovery": None, 
            "phoneNumber": None, 
            "others": None
        })

```

## How the Core Engine Loads Modules

The orchestration logic in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) discovers and executes modules dynamically using introspection:

```python

# Dynamic module discovery

modules = import_submodules("holehe.modules")
websites = get_functions(modules, args)

# Concurrent execution with shared client

client = httpx.AsyncClient(timeout=args.timeout)
out = []

async with trio.open_nursery() as nursery:
    for website in websites:
        nursery.start_soon(launch_module, website, email, client, out)

```

The `import_submodules("holehe.modules")` function walks the package tree to find all Python files, while `get_functions()` extracts the callable probe functions. The engine uses Trio for structured concurrency, passing the shared `httpx.AsyncClient` to each module to enable efficient connection reuse across hundreds of simultaneous checks.

## Summary

- **File Location**: Place modules in `holehe/modules/<category>/<service>.py` following the existing directory structure
- **Function Signature**: Implement exactly one async function accepting `(email, client, out)` parameters
- **Static Metadata**: Define `name`, `domain`, `method`, and `frequent_rate_limit` variables inside the function
- **Output Contract**: Append dictionaries with nine specific keys (`name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, `others`) to the `out` list
- **Error Handling**: Catch all exceptions and report them as `rateLimit: True` entries to ensure scan continuity
- **Discovery**: The core engine uses `import_submodules()` and `get_functions()` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) to find and execute modules automatically

## Frequently Asked Questions

### What is the exact function signature for a holehe module?

Every holehe module must expose exactly one async function with the signature `async def servicename(email, client, out)`. The function name must match the filename (e.g., [`instagram.py`](https://github.com/megadose/holehe/blob/main/instagram.py) contains `async def instagram(...)`). The `email` parameter receives the target string, `client` provides a shared `httpx.AsyncClient`, and `out` is a list where results are appended.

### How does holehe handle concurrent execution of modules?

The core engine in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) uses the Trio library for structured concurrency. It creates an open nursery with `trio.open_nursery()` and starts each module as a separate task using `nursery.start_soon(launch_module, ...)`. All modules share a single `httpx.AsyncClient` instance passed as the `client` parameter, enabling efficient connection pooling across hundreds of simultaneous probes.

### What fields are required in the holehe module output dictionary?

Each module must append a dictionary to the `out` list containing exactly nine keys: `name` (service identifier), `domain` (service domain), `method` (check type), `frequent_rate_limit` (boolean flag), `rateLimit` (boolean indicating if blocked), `exists` (boolean indicating if email is registered), `emailrecovery` (extracted recovery email or None), `phoneNumber` (extracted phone or None), and `others` (dictionary for additional data or None).

### Where should I place a new custom holehe module?

Create your new module file in `holehe/modules/<category>/your_service.py`, where `<category>` is a thematic folder like `social_media`, `shopping`, or `software`. The [`holehe/modules/__init__.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/__init__.py) file enables Python to treat this as a package, and the dynamic importer in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) will automatically discover your module as long as it follows the standard file structure and function naming conventions.