How to Add New Website Support to holehe: A Complete Developer Guide

Create a single async Python function in holehe/modules/ that accepts email, client, and out parameters, following the signature pattern used by existing modules like Facebook and Twitter.

holehe is a modular email reconnaissance tool developed by megadose that checks whether an email address exists across hundreds of web services. Adding new website support requires understanding its module-based architecture, where each service is implemented as a standalone async function discovered automatically at runtime. This guide walks through the exact steps to extend holehe's coverage, referencing the actual source code patterns from the repository.

How holehe's Module System Works

The core discovery engine lives in holehe/core.py. At startup, it uses importlib to iterate over all Python files under holehe/modules/, treating each as a potential service check. No manual registration is required—any valid module following the naming convention is automatically loaded and executed concurrently.

Each module exposes exactly one async function whose name matches the service. The function signature is strictly enforced:

async def <service_name>(email: str, client: httpx.AsyncClient, out: list) -> None:
Parameter Purpose
email The target email address being tested
client Shared httpx.AsyncClient for connection reuse across all modules
out Result list to which modules append outcome dictionaries

The concurrent execution model means modules run in parallel, so your implementation must be fully async—no blocking I/O.

Required Module Structure

Every module must follow the pattern demonstrated in holehe/modules/social_media/facebook.py:

Static Metadata Fields

Define these four variables at the function's start:

  • name — Human-readable service name
  • domain — Primary domain (used for output grouping)
  • method — Check strategy: "register", "password_reset", or "login"
  • frequent_rate_limit — Boolean flag indicating aggressive rate-limiting

Request Flow Pattern

Analyze existing modules to identify the typical implementation path:

  1. Pre-flight (optional) — Fetch CSRF tokens, cookies, or session data via GET
  2. Primary request — Send POST to registration/password endpoint with target email
  3. Response analysis — Parse JSON to determine: account exists, doesn't exist, or rate-limited
  4. Result recording — Append standardized dictionary to out

The result dictionary requires six specific keys:

Key Value Type Description
name str Service name (matches name variable)
domain str Primary domain
method str Check method used
frequent_rate_limit bool Rate-limit flag
rateLimit bool Whether this specific check hit rate limiting
exists bool Whether the email has an existing account
emailrecovery str | None Recovered partial email (if leak reveals it)
phoneNumber str | None Recovered phone number (if leak reveals it)
others dict | None Additional extracted metadata

Step-by-Step Implementation Walkthrough

Step 1: Create the Module File

Place your file under the appropriate category subdirectory:


holehe/modules/
├── social_media/
├── shopping/
├── adult/
├── entertainment/
├── music/
├── transport/
└── [your_category]/
    └── yourservice.py

Create the file as holehe/modules/social_media/examplesite.py for this walkthrough.

Step 2: Import Dependencies

Every module needs random and string for payload generation, plus the user-agent pool:


# holehe/modules/social_media/examplesite.py

import random
import string
from holehe.localuseragent import ua

The ua dictionary in holehe/localuseragent.py provides realistic browser strings to avoid detection.

Step 3: Define the Async Function

Name the function exactly as the file (without .py):

async def examplesite(email, client, out):
    name = "ExampleSite"
    domain = "examplesite.com"
    method = "register"
    frequent_rate_limit = False

The parameter names email, client, and out must match exactly—holehe/core.py passes these positionally.

Step 4: Configure Request Headers

Mimic legitimate browser traffic:

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

Step 5: Handle Pre-flight Requirements

Many services require tokens or cookies before the main request. Wrap in try/except to remain resilient:

    # Optional: fetch CSRF token

    try:
        r = await client.get("https://www.examplesite.com/signup", headers=headers)
        token = r.text.split('name="csrf_token" value="')[1].split('"')[0]
    except Exception:
        token = None

Use the shared client parameter—never create your own httpx.AsyncClient.

Step 6: Build and Send the Check Request

Construct payloads that trigger email validation without completing registration:

    payload = {
        "email": email,
        "username": "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(12)),
        "password": "DummyPass123!",
    }
    if token:
        headers["X-CSRFToken"] = token

    try:
        resp = await client.post(
            "https://api.examplesite.com/v1/users",
            data=payload,
            headers=headers
        )
        data = resp.json()

Step 7: Interpret the Response

Service responses vary—study the target's API behavior to identify existence indicators:

        if data.get("error") == "email_already_used":
            exists = True
        else:
            exists = False
        rate_limited = data.get("error") == "rate_limit_exceeded"
    except Exception:
        exists = False
        rate_limited = True

Common existence signals include: "email taken", "account exists", specific error codes, or redirect to login versus signup flow.

Step 8: Record the Result

Always append to out with the complete dictionary structure:

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

Complete Working Example

Here's the full examplesite.py module combining all steps:


# holehe/modules/social_media/examplesite.py

import random
import string
from holehe.localuseragent import ua


async def examplesite(email, client, out):
    name = "ExampleSite"
    domain = "examplesite.com"
    method = "register"
    frequent_rate_limit = False

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

    # Optional: fetch CSRF token

    try:
        r = await client.get("https://www.examplesite.com/signup", headers=headers)
        token = r.text.split('name="csrf_token" value="')[1].split('"')[0]
    except Exception:
        token = None

    # Build registration payload

    payload = {
        "email": email,
        "username": "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(12)),
        "password": "DummyPass123!",
    }
    if token:
        headers["X-CSRFToken"] = token

    # Send request and evaluate response

    try:
        resp = await client.post(
            "https://api.examplesite.com/v1/users",
            data=payload,
            headers=headers
        )
        data = resp.json()
        if data.get("error") == "email_already_used":
            exists = True
        else:
            exists = False
        rate_limited = data.get("error") == "rate_limit_exceeded"
    except Exception:
        exists = False
        rate_limited = True

    # Store outcome

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

Testing Your New Module

After saving the file, verify integration immediately:


# From repository root

python -m holehe -e test@example.com | grep -i examplesite

No reinstallation or restart is required—holehe/core.py discovers modules dynamically on each invocation.

Key Reference Files

File Purpose
holehe/core.py Module discovery, async orchestration, and result aggregation
holehe/modules/__init__.py Package marker enabling dynamic imports
holehe/modules/social_media/facebook.py Production reference for CSRF handling and response parsing
holehe/localuseragent.py Browser user-agent strings for request mimicry
holehe/instruments.py CLI formatting utilities

Summary

  • Module location: Create .py files under holehe/modules/<category>/
  • Function signature: async def servicename(email, client, out) — exact parameter names required
  • Automatic discovery: No registration needed; importlib finds modules at runtime
  • Result contract: 9-key dictionary with exists, rateLimit, and metadata fields
  • Shared resources: Use the provided client for all HTTP operations; import ua for user-agents
  • Error resilience: Wrap external requests in try/except; assume network failures mean rate-limiting

Frequently Asked Questions

Do I need to modify any configuration files to register a new module?

No. The dynamic loader in holehe/core.py uses importlib to scan all subdirectories under holehe/modules/ at runtime. Simply placing a valid Python file with the correct function signature is sufficient—no __init__.py imports or registry updates required.

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

No. holehe executes all modules concurrently using asyncio.gather(). Blocking calls freeze the entire event loop and degrade performance for other checks. Always use await client.get() or await client.post() with the shared httpx.AsyncClient instance.

How do I handle services that require JavaScript or browser automation?

holehe modules are HTTP-only. Services requiring JavaScript execution (React apps, heavy obfuscation) are typically unsupported—consider tools like Playwright or Selenium in separate projects. For APIs protected by basic obfuscation, study existing modules like facebook.py which handle CSRF tokens and cookie sessions through raw HTTP.

What should I set for frequent_rate_limit versus rateLimit?

frequent_rate_limit is static metadata warning users that this service aggressively limits requests. Set it to True based on the platform's known reputation. rateLimit is dynamic—set it to True when your specific request receives a rate-limit response. Both appear in CLI output to inform users of reliability.

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 →