What Is the Standard Async Interface for Holehe Modules?
Holehe modules must expose an asynchronous function with the exact signature async def <module_name>(email: str, client: httpx.AsyncClient, out: list) -> None, performing HTTP requests via the shared client and appending standardized results to the mutable out list.
Holehe is an open-source email OSINT tool that checks if an address is registered across hundreds of websites. Understanding the standard async interface for Holehe modules is essential for contributing new service checkers or integrating the library into custom Python workflows. Every module in holehe/modules/ adheres to a strict three-parameter coroutine contract that enables concurrent execution via Trio.
The Three-Parameter Coroutine Signature
Every Holehe module exposes a single top-level coroutine that accepts three positional arguments and returns None:
- email (
str) – The target email address to investigate. - client (
httpx.AsyncClient) – A shared async HTTP client configured with user-defined timeouts. - out (
list) – A mutable list that collects result dictionaries.
The core orchestrator in holehe/core.py enforces this contract through the launch_module helper at lines 66-71, which calls each module with these arguments and catches exceptions to prevent individual failures from crashing the scan.
Shared HTTP Client for Connection Reuse
Modules must reuse the provided client rather than instantiating their own httpx.AsyncClient. This design allows the orchestrator to share connection pools across all modules, significantly reducing TCP overhead when running dozens of checks concurrently.
Output List Mutation Pattern
Instead of returning values directly, modules append a standardized dictionary to the out list. This dictionary must include keys such as name, domain, method, frequent_rate_limit, rateLimit, exists, emailrecovery, phoneNumber, and others.
Core Orchestration and Concurrent Execution
The execution logic resides in holehe/core.py. To achieve high concurrency, Holehe uses Trio to schedule all modules simultaneously. The main event loop opens a nursery and starts each module via launch_module:
async with trio.open_nursery() as nursery:
for website in websites:
nursery.start_soon(launch_module, website, email, client, out)
This pattern appears at lines 19-21 of holehe/core.py. The uniform interface allows the orchestrator to treat every module identically regardless of the specific service being checked.
Example Implementation in Existing Modules
Concrete implementations follow this pattern exactly. For example, the Blablacar checker in holehe/modules/transport/blablacar.py declares its function at line 5 as:
async def blablacar(email, client, out):
# implementation details...
This matches the required signature precisely, allowing the core to discover and execute it automatically.
Creating a Custom Holehe Module
To add a new service checker, create a Python file in the appropriate subdirectory of holehe/modules/:
# holehe/modules/social_media/myservice.py
from holehe.core import *
async def myservice(email, client, out):
name = "myservice"
domain = "myservice.com"
resp = await client.get(f"https://api.myservice.com/users/{email}")
exists = resp.status_code == 200
out.append({
"name": name,
"domain": domain,
"method": "query",
"frequent_rate_limit": False,
"rateLimit": False,
"exists": exists,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
The function name must match the module filename for automatic discovery by the import_submodules logic.
Manual Module Invocation
You can bypass the CLI and invoke modules directly by manually constructing the three required arguments:
import httpx
import trio
import importlib
async def run_one():
client = httpx.AsyncClient(timeout=10)
out = []
mod = importlib.import_module("holehe.modules.social_media.twitter")
await mod.twitter("example@example.com", client, out)
await client.aclose()
print(out)
trio.run(run_one)
This demonstrates how the async interface functions independently of the core orchestrator's nursery management.
Summary
- Holehe modules implement
async def name(email, client, out)returningNone. - The
clientparameter is a sharedhttpx.AsyncClientthat enables connection reuse across concurrent checks. - Results are appended to the mutable
outlist using a standardized dictionary schema containing fields likeexists,rateLimit, andemailrecovery. - The core orchestrator in
holehe/core.pyenforces this interface vialaunch_module(lines 66-71) and schedules concurrent execution using Trio (lines 19-21). - All modules in
holehe/modules/, such asblablacar.pyat line 5, follow this pattern.
Frequently Asked Questions
What happens if a module doesn't follow the standard async interface?
The launch_module helper in holehe/core.py catches exceptions and inserts a standardized error entry into the output list. This prevents one malformed or failing module from crashing the entire scan operation.
Can I use a different HTTP client instead of httpx.AsyncClient?
No. The interface specifically requires accepting an httpx.AsyncClient instance as the second argument. Modules must use this shared client to ensure proper connection pooling and timeout handling as configured by the user.
Why does the interface use a mutable list instead of return values?
The mutable out list design allows the Trio-based orchestrator to collect results from concurrently running tasks without complex synchronization primitives. Each module appends its result dictionary directly, enabling safe aggregation from multiple parallel executions.
What fields are required in the result dictionary?
The dictionary appended to out must include: name (service identifier), domain, method (check type), frequent_rate_limit (boolean), rateLimit (boolean), exists (boolean or None), emailrecovery, phoneNumber, and others (additional metadata).
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →