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 namedomain— 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:
- Pre-flight (optional) — Fetch CSRF tokens, cookies, or session data via
GET - Primary request — Send
POSTto registration/password endpoint with target email - Response analysis — Parse JSON to determine: account exists, doesn't exist, or rate-limited
- 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
.pyfiles underholehe/modules/<category>/ - Function signature:
async def servicename(email, client, out)— exact parameter names required - Automatic discovery: No registration needed;
importlibfinds modules at runtime - Result contract: 9-key dictionary with
exists,rateLimit, and metadata fields - Shared resources: Use the provided
clientfor all HTTP operations; importuafor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →