How to Create a Custom Module in Holehe: Understanding the Plug-In Architecture
Holehe uses a plug-in style module architecture where each email-checking service is an independent Python file in holehe/modules/, automatically discovered at runtime via pkgutil.walk_packages() and executed concurrently using Trio.
The open-source OSINT tool Holehe (megadose/holehe) checks if an email address is registered on hundreds of sites. Its extensible design allows you to add new services without modifying core logic. Understanding the module architecture in Holehe reveals how the tool dynamically imports, orchestrates, and standardizes results from distributed service checks.
How the Module Architecture Works
Holehe’s core engine (holehe/core.py) treats every service as a standalone module. The architecture relies on four distinct phases to discover and execute checks.
Dynamic Discovery via import_submodules
The import_submodules() function traverses the package tree using pkgutil.walk_packages() to find every Python file under holehe/modules/【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L37-L47). This occurs at runtime, meaning any new .py file dropped into the modules directory is automatically imported without explicit registration.
Function Extraction and Registration
Once imported, get_functions() inspects each module and extracts the callable whose name matches the file name—for example, twitter from twitter.py【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L50-L63). Only functions matching their filename are collected into the execution list, enforcing a strict naming convention.
Async Execution with Trio
The launch_module() function executes each discovered callable concurrently using Trio, passing three arguments: the target email, a shared httpx.AsyncClient instance, and a shared out list where results are appended【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L66-L71). This asynchronous pattern allows Holehe to check hundreds of services simultaneously without blocking.
Standardized Result Schema
Every module must append a dictionary to the out list with a fixed set of keys: name, domain, method, frequent_rate_limit, rateLimit, exists, emailrecovery, phoneNumber, and others. The core printer (print_result) consumes this schema to format terminal output and CSV exports【source】(https://github.com/megadose/holehe/blob/master/holehe/core.py#L106-L149).
Creating a Custom Holehe Module
To add a new service, create a Python file in any sub-directory of holehe/modules/ (e.g., holehe/modules/social_media/myservice.py). The folder structure organizes code by category but does not affect execution logic.
Required Function Signature
Define an async function whose name matches the filename exactly. It must accept three parameters:
async def myservice(email: str, client, out: list):
email: The target string to investigateclient: A pre-configuredhttpx.AsyncClientfor HTTP requestsout: A shared list to which you append the result dictionary
Implementing the Check
Use the shared client for all HTTP requests and import ua from holehe.localuseragent for a realistic User-Agent header. Handle exceptions gracefully to indicate rate limits or errors.
# holehe/modules/social_media/myservice.py
from holehe.core import *
from holehe.localuseragent import *
async def myservice(email: str, client, out: list):
"""
Checks whether email is registered on MyService.
"""
name = "myservice"
domain = "myservice.com"
method = "register"
frequent_rate_limit = False
try:
resp = await client.get(
"https://api.myservice.com/v1/users/exists",
params={"email": email},
headers={"User-Agent": ua}
)
exists = resp.json().get("taken", 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:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
Key Implementation Rules
- File location: Place in any
holehe/modules/subfolder (e.g.,products/,social_media/) - Naming: Function name must match filename (e.g.,
myservice()inmyservice.py) - Async requirement: Function must be declared with
async def - Result schema: Dictionary must include all required keys; set
rateLimittoTruewhen the service blocks requests - No registration:
import_submodules()automatically discovers the file at startup
After saving, run holehe email@example.com to see your custom module appear in the output and optional CSV export.
Summary
- Holehe employs a plug-in architecture where service checks reside as independent Python modules under
holehe/modules/ - Dynamic discovery via
pkgutil.walk_packages()inholehe/core.pyeliminates manual registration - Modules must expose an async function matching the filename that accepts
(email, client, out)arguments - A strict result schema (
name,domain,exists,rateLimit, etc.) ensures consistent output formatting across all services - Trio-based concurrency executes all modules in parallel, appending results to a shared list for the core printer to process
Frequently Asked Questions
What is the module architecture in Holehe?
Holehe implements a dynamic plug-in system where each service check is a standalone Python file. The import_submodules() function in core.py uses pkgutil.walk_packages() to auto-discover every module under holehe/modules/, while get_functions() extracts callables matching their filenames for execution.
How do I create a custom module in Holehe?
Create a .py file in any subfolder of holehe/modules/ containing an async function with the same name as the file. The function must accept (email, client, out) parameters and append a result dictionary with the required schema to the out list. No manual registration is necessary.
What is the required result schema for Holehe modules?
Every module must append a dictionary containing: name, domain, method, frequent_rate_limit, rateLimit, exists, emailrecovery, phoneNumber, and others. Boolean flags like rateLimit indicate if the service blocked the request, while exists shows registration status.
Do I need to register my custom module manually?
No. Holehe performs runtime discovery of modules using pkgutil.walk_packages(). As long as your file is in holehe/modules/ and your function name matches the filename, holehe/core.py will automatically import and execute it alongside built-in modules.
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 →