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

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 dynamically loads every module without hardcoded lists. Two functions make this possible:

  • import_submodules (lines 37–48) traverses holehe.modules using pkgutil.walk_packages
  • get_functions (lines 50–63) extracts callable coroutines from imported modules

Modules run inside a Trio nursery via launch_module (lines 66–78), 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.

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

Mandatory Imports

Start with these imports to access core constants and utilities:

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 expects this pattern):

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

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`
phoneNumber str ` None`
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


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

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 import_submodules (L37–48), get_functions (L50–63), launch_module (L66–78)
holehe/modules/__init__.py Package marker enabling pkgutil.walk_packages traversal
holehe/localuseragent.py get_random_user_agent() for request headers
holehe/modules/social_media/twitter.py Reference implementation showing minimal valid module
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

Frequently Asked Questions

What happens if my module raises an uncaught exception?

The launch_module wrapper (core.py L66–78) 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:

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →