How to Create a Software Module for Holehe: A Developer’s Guide
To create a software module for holehe, write an async Python function in holehe/modules/<category>/<service>.py that accepts (email, client, out) parameters, performs an HTTP request using the provided httpx.AsyncClient, and appends a standardized result dictionary to the out list.
Holehe is an open-source email investigation tool that checks whether an address is registered across thousands of online services. The tool uses a modular architecture where each service is implemented as an async Python function in the holehe/modules/ directory. When you create a software module for holehe following the established patterns in holehe/core.py, the framework automatically discovers and executes your code without requiring manual registration.
Understanding the Auto-Discovery Mechanism
The core loading mechanism resides in holehe/core.py and handles module discovery dynamically.
The import_submodules() function (lines 37-48) recursively walks the holehe.modules package tree and imports every Python file it encounters. This means any new module you place in the directory structure is automatically loaded at runtime. Subsequently, launch_module() (lines 66-70) executes each discovered function with the standard (email, client, out) signature.
Because of this architecture, you do not need to edit a central registry or configuration file to add a new service. Simply placing your Python file in the correct subdirectory and implementing the required function signature enables immediate integration.
Step-by-Step Guide to Creating a Module
Select a Category and File Location
Modules are organized by domain type within holehe/modules/. For software-as-a-service (SaaS) tools, use the software subpackage.
Create a new file at:
holehe/modules/software/<servicename>.py
For example, holehe/modules/software/examplecloud.py would create a module named examplecloud.
Implement the Required Function Signature
Every module must define an async function matching the filename with this exact signature:
async def examplecloud(email, client, out):
"""Check if email exists on ExampleCloud."""
name = "ExampleCloud"
domain = "examplecloud.com"
method = "register"
frequent_rate_limit = False
The parameters are:
- email: The target email address as a string
- client: An
httpx.AsyncClientinstance for making HTTP requests (shared across all modules for connection pooling) - out: A shared list that collects result dictionaries from all modules
Build HTTP Requests with Utilities
Import the randomized User-Agent list from the core utilities to avoid detection:
import random
from holehe.localuseragent import ua
headers = {
"User-Agent": random.choice(ua["browsers"]["chrome"]),
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.5",
}
Use the provided client to make asynchronous requests:
response = await client.get(
"https://api.examplecloud.com/v1/users/exists",
params={"email": email},
headers=headers,
)
Parse Responses and Append Results
Interpret the HTTP response to determine registration status, then append a standardized dictionary to the out list:
data = response.json()
exists = data.get("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,
})
Handle errors and rate limiting in an exception block:
except Exception:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"error": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
Complete Minimal Example
Here is a fully functional module for a fictional SaaS platform. Save this as holehe/modules/software/examplecloud.py:
import random
from holehe.localuseragent import ua
async def examplecloud(email, client, out):
"""Check if an email is registered on ExampleCloud."""
name = "ExampleCloud"
domain = "examplecloud.com"
method = "register"
frequent_rate_limit = False
headers = {
"User-Agent": random.choice(ua["browsers"]["chrome"]),
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.5",
}
try:
r = await client.get(
"https://api.examplecloud.com/v1/users/exists",
params={"email": email},
headers=headers,
)
data = r.json()
exists = data.get("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:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"error": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
Run the tool to see your module in action:
holehe user@example.com
The output table will include your new service alongside existing checks like facebook and docker, formatted by the print_result() function in holehe/core.py (lines 22-49).
Key Conventions and Result Schema
When you create a software module for holehe, adhere to this result schema for consistency:
| Key | Type | Description |
|---|---|---|
| name | string | Human-readable service name (e.g., "ExampleCloud") |
| domain | string | Base domain of the service (e.g., "examplecloud.com") |
| method | string | Action performed (e.g., "register", "login") |
| frequent_rate_limit | boolean | True if the service commonly returns HTTP 429 |
| rateLimit | boolean | True if the current request was throttled |
| exists | boolean | True if the email is registered, False otherwise |
| emailrecovery | string/null | Partially masked recovery email if exposed by the API |
| phoneNumber | string/null | Partial phone number if exposed by the API |
| others | any/null | Additional metadata extracted from the response |
Reference implementations demonstrating these patterns include holehe/modules/software/office365.py (lines 6-15) and holehe/modules/social_media/facebook.py (lines 6-14), which handle CSRF tokens and complex authentication flows while maintaining the same output structure.
Summary
- Auto-discovery: The
import_submodules()function inholehe/core.pyautomatically loads any Python file placed inholehe/modules/or its subdirectories. - Function signature: Implement
async def servicename(email, client, out)using the providedhttpx.AsyncClientfor all HTTP operations. - Result format: Append a dictionary to the
outlist containing standardized keys:name,domain,exists,rateLimit, and optional fields likeemailrecovery. - Error handling: Set
"rateLimit": Trueor"error": Truein the result dictionary when requests fail or return HTTP 429. - Utilities: Use
from holehe.localuseragent import uato access randomized browser User-Agents for request headers.
Frequently Asked Questions
Do I need to register my module manually after creating the file?
No. According to the source code in holehe/core.py, the import_submodules() function (lines 37-48) recursively imports all Python files in the holehe.modules package tree automatically. As long as your file is in the correct location and contains the properly named async function, holehe will discover and execute it without any registry edits.
What HTTP client should I use inside my module?
You must use the client parameter passed to your function, which is an httpx.AsyncClient instance shared across all modules. This client handles connection pooling and proxy settings configured by the user. Do not create your own HTTP client instances.
How do I handle services that implement strict rate limiting?
Set frequent_rate_limit = True at the top of your function to inform users that the service commonly blocks requests. If you encounter an HTTP 429 or connection timeout during execution, catch the exception and append a result with "rateLimit": True instead of crashing. See the Facebook module at holehe/modules/social_media/facebook.py for a robust implementation of this pattern.
Can my module return additional data beyond the boolean exists check?
Yes. While the exists field is required, you can populate emailrecovery, phoneNumber, and others with data extracted from the API response. These fields are displayed in the final output if they contain non-null values, allowing your module to expose recovery information or profile metadata when the target service leaks it.
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 →