How to Add a New Website Module to Holehe: Complete Developer Guide
To add a new website module to Holehe, create an async Python file in holehe/modules/<category>/ with a function matching the filename, register the domain mapping in holehe/core.py, and the core engine will automatically discover and execute it during email verification runs.
Holehe (megadose/holehe) is an open-source email investigation tool that checks whether an address is registered across hundreds of services. Its modular architecture allows you to add a new website module to Holehe without altering the core execution logic, leveraging dynamic module discovery via import_submodules() in holehe/core.py.
Understanding the Module Architecture
Holehe dynamically loads module files from the holehe/modules/ directory tree. The core engine uses pkgutil.walk_packages in holehe/core.py (lines 37-47) to recursively discover all Python modules at runtime. Each module must implement exactly one async function named identically to the file (excluding the .py extension).
The function signature must conform to:
async def <module_name>(email, client, out):
email: The target email address string to investigate.client: Anhttpx.AsyncClientinstance for performing asynchronous HTTP requests.out: A list that collects result dictionaries for aggregation by the main process.
The launch_module() function in holehe/core.py (lines 66-68) contains a data dictionary that maps module names to human-readable domains for output formatting.
Step 1: Create the Module File
Create a new Python file in the appropriate category subdirectory (e.g., holehe/modules/social_media/example.py). The filename determines the function name and module identifier.
Reference the Twitter implementation (holehe/modules/social_media/twitter.py, lines 5-37) for the standard structure:
# holehe/modules/social_media/example.py
from holehe.core import *
from holehe.localuseragent import *
async def example(email, client, out):
"""
Checks whether email is registered on Example.com.
"""
name = "example"
domain = "example.com"
method = "register"
frequent_rate_limit = False
try:
req = await client.get(
"https://api.example.com/check_email",
params={"email": email}
)
if req.json().get("taken"):
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": False,
"exists": True,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
else:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": False,
"exists": False,
"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,
})
Critical requirements:
- Function name must exactly match the filename without the
.pyextension. - Result dictionary must contain all required keys:
name,domain,method,frequent_rate_limit,rateLimit,exists,emailrecovery,phoneNumber, andothers. - Exception handling must catch errors and append a result with
rateLimit: True(or appropriate error state) to prevent the module from crashing the entire scan.
Step 2: Register the Domain Mapping
Locate the data dictionary inside launch_module() in holehe/core.py (approximately lines 66-68). Insert a new key-value pair where the key matches your module filename and the value is the service's domain:
# holehe/core.py - inside launch_module()
data = {
# ... existing entries ...
"example": "example.com",
}
This mapping ensures that when results are displayed, they correctly identify the service associated with your module.
Step 3: Update Project Documentation
While optional, adding the new service to the README.md table of supported services helps users discover the capability and maintains project transparency.
Testing Your New Module
After placing the file in the modules directory:
- Run Holehe with a test email to verify the module imports without errors.
- Check that HTTP requests return the expected status codes and response bodies.
- Validate that the
outlist receives dictionaries with correctexistsboolean values. - Verify the domain mapping appears correctly in the final output.
If the module does not execute, confirm that the function name matches the filename exactly and that all result dictionary keys are present.
Summary
- Place new modules in
holehe/modules/<category>/<servicename>.pywith the function name matching the filename exactly. - Ensure the async function accepts parameters
email,client, andoutin that order. - Append standardized result dictionaries to the
outlist containing all nine required keys. - Register the domain mapping in the
datadictionary insidelaunch_module()inholehe/core.py. - The
import_submodules()function automatically discovers and loads new files at runtime without requiring core code modifications.
Frequently Asked Questions
What happens if my function name does not match the filename?
Holehe will raise an AttributeError during execution. The launch_module() function attempts to call a function with the same name as the module file (without the .py extension). If the names differ, the lookup fails and the module is skipped with an error.
Can I place modules in any subdirectory under holehe/modules/?
Yes. The import_submodules() function uses pkgutil.walk_packages to recursively scan all subdirectories (such as social_media, shopping, or gaming). While you can use any directory structure, organizing modules by service category improves maintainability.
How should I handle API keys or authentication tokens?
Store credentials in environment variables or a separate configuration file excluded from version control. Reference these variables in your module code when constructing HTTP requests. Never hardcode sensitive tokens directly in the module file, especially if contributing back to the megadose/holehe repository.
What is the difference between rateLimit and frequent_rate_limit?
Set frequent_rate_limit=True in the result dictionary if the service consistently imposes strict rate limiting across all requests. Set rateLimit=True when a specific request encounters a temporary block or quota violation during execution. This distinction allows users to differentiate between temporary failures and definitive "email not found" results.
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 →