# How Are Modules Organized in Holehe: A Complete Guide to the Discovery Architecture

> Discover holehe module organization. Explore its hierarchical structure and dynamic discovery in core.py for efficient site checking.

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

---

**Holehe organizes its site-checking modules into a hierarchical `holehe/modules` package grouped by functional domain, with dynamic discovery via `import_submodules` in [`core.py`](https://github.com/megadose/holehe/blob/main/core.py) eliminating the need for manual registration.**

Holehe, the open-source email enumeration tool by megadose (megadose/holehe), uses a modular architecture that scales to hundreds of services without requiring a centralized registry. Understanding how modules are organized in holehe reveals a design pattern that prioritizes automatic discovery and logical categorization. The architecture separates site-specific logic from orchestration, allowing contributors to add new platforms by simply dropping Python files into categorized subdirectories.

## Domain-Based Package Hierarchy

The `holehe/modules` directory acts as the root package, with subdirectories representing distinct functional categories. Each subdirectory contains individual Python files implementing checks for specific services.

### Category Organization

| Directory | Example Modules | Purpose |
|-----------|----------------|---------|
| `social_media/` | [`twitter.py`](https://github.com/megadose/holehe/blob/main/twitter.py), [`instagram.py`](https://github.com/megadose/holehe/blob/main/instagram.py), [`facebook.py`](https://github.com/megadose/holehe/blob/main/facebook.py) | Check email usage on major social networks |
| `software/` | [`docker.py`](https://github.com/megadose/holehe/blob/main/docker.py), [`adobe.py`](https://github.com/megadose/holehe/blob/main/adobe.py), [`lastpass.py`](https://github.com/megadose/holehe/blob/main/lastpass.py) | Query SaaS and desktop software accounts |
| `cms/` | [`wordpress.py`](https://github.com/megadose/holehe/blob/main/wordpress.py), [`gravatar.py`](https://github.com/megadose/holehe/blob/main/gravatar.py), [`atlassian.py`](https://github.com/megadose/holehe/blob/main/atlassian.py) | Verify presence on content management platforms |
| `forum/` | [`mybb.py`](https://github.com/megadose/holehe/blob/main/mybb.py), [`demonforums.py`](https://github.com/megadose/holehe/blob/main/demonforums.py), [`blitzortung.py`](https://github.com/megadose/holehe/blob/main/blitzortung.py) | Test registration on web forums |
| `shopping/` | [`amazon.py`](https://github.com/megadose/holehe/blob/main/amazon.py), [`ebay.py`](https://github.com/megadose/holehe/blob/main/ebay.py), [`vivino.py`](https://github.com/megadose/holehe/blob/main/vivino.py) | Detect e-commerce accounts |
| `payment/` | [`venmo.py`](https://github.com/megadose/holehe/blob/main/venmo.py) | Look for payment service accounts |
| `crm/` | [`hubspot.py`](https://github.com/megadose/holehe/blob/main/hubspot.py), [`zoho.py`](https://github.com/megadose/holehe/blob/main/zoho.py), [`amocrm.py`](https://github.com/megadose/holehe/blob/main/amocrm.py) | Check customer relationship platforms |
| `company/`, `music/`, `sport/`, `real_estate/`, `programing/`, `crowfunding/`, `medias/`, `jobs/`, `transport/` | Various niche modules | Additional specialized services |

Each module file defines a single async function named after the service (e.g., `async def twitter(email, client, out)`) that performs the HTTP request and appends a result dictionary to the shared `out` list.

## Dynamic Module Discovery in core.py

Rather than maintaining a static list of supported services, [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) implements automatic module discovery using Python's `pkgutil` module.

### The import_submodules Function

The `import_submodules` function recursively walks the package tree to load every module dynamically:

```python

# holehe/core.py

def import_submodules(package, recursive=True):
    """Get all the holehe submodules"""
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    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 function returns a dictionary mapping full module paths (e.g., `'holehe.modules.social_media.twitter'`) to imported module objects.

### Filtering to Callable Functions

The `get_functions` method transforms the imported modules into a list of callable check functions:

```python

# holehe/core.py

def get_functions(modules, args=None):
    """Transform the modules objects to functions"""
    websites = []
    for module in modules:
        if len(module.split(".")) > 3:
            modu = modules[module]
            site = module.split(".")[-1]
            # optional filtering (e.g. --no-password-recovery)

            websites.append(modu.__dict__[site])
    return websites

```

The filter `len(module.split(".")) > 3` ensures only modules nested within category directories (not the root `modules` package itself) are processed.

## Standard Module Interface

Every module in the `holehe/modules` tree follows an identical asynchronous interface pattern.

### Required Function Signature

Each module exports an async function matching the filename:

```python

# holehe/modules/social_media/twitter.py (structure example)

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

async def twitter(email, client, out):
    name = "twitter"
    domain = "twitter.com"
    method = "register"
    try:
        resp = await client.get("https://api.twitter.com/...", params={"email": email})
        # ... logic to determine if account exists ...

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": False,
            "rateLimit": False,
            "exists": True,  # or False based on response

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

```

### Output Dictionary Schema

The `out` parameter is a shared list that collects standardized result dictionaries containing:

- `name`: Service identifier
- `domain`: Target domain
- `method`: Verification method used
- `exists`: Boolean indicating if the email is registered
- `rateLimit`: Boolean indicating if the service temporarily blocked the check
- `emailrecovery`: Partial email address if recovery info is exposed
- `phoneNumber`: Partial phone number if exposed
- `others`: Additional metadata

## Execution Flow and Error Handling

Once loaded, modules execute through the `launch_module` wrapper in [`core.py`](https://github.com/megadose/holehe/blob/main/core.py), which provides standardized exception handling and output normalization.

### The launch_module Function

```python

# holehe/core.py

async def launch_module(module, email, client, out):
    try:
        await module(email, client, out)
    except Exception:
        name = str(module).split('<function ')[1].split(' ')[0]
        out.append({
            "name": name,
            "domain": data[name],
            "rateLimit": False,
            "error": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

This wrapper ensures that a crashing module does not interrupt the entire enumeration process. The `client` parameter is typically an `httpx.AsyncClient` instance shared across all modules to enable connection pooling.

## Extending the Module Library

Adding support for a new service requires no changes to [`core.py`](https://github.com/megadose/holehe/blob/main/core.py) or any registry file.

### Creating a New Module

1. Create a new Python file in the appropriate category directory (e.g., [`holehe/modules/social_media/example.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/example.py))
2. Import the standard helpers: `from holehe.core import *` and `from holehe.localuseragent import *`
3. Define the async function with the standard signature
4. Implement the request logic and append results to `out`

The new module is automatically discovered on the next run because `import_submodules` recursively walks the directory tree at startup.

## Summary

- **Hierarchical organization**: Modules reside in `holehe/modules` grouped by functional domains like `social_media`, `software`, and `shopping`
- **Automatic discovery**: The `import_submodules` function in [`core.py`](https://github.com/megadose/holehe/blob/main/core.py) recursively imports all Python files without manual registration
- **Standardized interface**: Each module exposes a single async function named after the service that accepts `(email, client, out)` parameters
- **Fault tolerance**: `launch_module` wraps each execution to prevent individual service failures from crashing the entire enumeration
- **Simple extension**: Adding new services requires only creating a Python file in the appropriate category directory

## Frequently Asked Questions

### How does holehe discover new modules automatically?

Holehe uses the `import_submodules` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) which utilizes `pkgutil.walk_packages` to recursively traverse the `holehe/modules` directory tree. This imports every Python file found in category subdirectories, builds a dictionary of module references, and extracts the callable functions via `get_functions`. No central registry or configuration file needs updating when new modules are added.

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

Every module must define an async function with the signature `async def servicename(email, client, out)` where `email` is the target string, `client` is an `httpx.AsyncClient` for HTTP requests, and `out` is a list where the function appends a result dictionary. The function name must match the filename (e.g., [`twitter.py`](https://github.com/megadose/holehe/blob/main/twitter.py) contains `async def twitter`).

### How are modules categorized in holehe?

Modules are organized into subdirectories under `holehe/modules` based on the service's primary function. Categories include `social_media`, `software`, `cms`, `forum`, `shopping`, `payment`, `crm`, `company`, `music`, `sport`, and others. This grouping allows users to mentally map related services and helps contributors place new modules in logical locations.

### Can I add custom modules to holehe without modifying core files?

Yes. Simply create a new Python file in any subdirectory of `holehe/modules` (or create a new category directory) following the standard async function pattern. The dynamic loader will discover and execute your module automatically on the next run. No changes to [`core.py`](https://github.com/megadose/holehe/blob/main/core.py), [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py), or any registry are required unless you need to add special handling logic.