Holehe Module Structure: A Complete Developer Guide
A holehe module is a self-contained Python file that implements an async function to probe a specific online service for email existence, following a strict contract defined in the megadose/holehe repository.
Holehe is an open-source OSINT (Open Source Intelligence) tool that checks whether email addresses are registered on hundreds of online platforms. Understanding the holehe module structure is essential for security researchers and developers who want to extend the tool with new services or debug existing email verification probes.
File Location and Package Organization
All holehe modules reside within the holehe.modules package, organized by thematic categories such as social_media, software, and shopping.
The standard file path follows this pattern:
holehe/modules/<category>/<service>.py
For example, the Instagram probe lives at holehe/modules/social_media/instagram.py, while e-commerce checks reside in holehe/modules/shopping/. The holehe/modules/__init__.py file marks this directory as a Python package and enables the dynamic import mechanism used by the core engine to discover probes at runtime.
Required Imports
Every module must begin with two specific wildcard imports:
from holehe.core import *– Imports shared utilities and base exceptions from the core frameworkfrom holehe.localuseragent import *– Imports the User-Agent rotation dictionary
These imports provide access to random.choice(ua["browsers"]["chrome"]) for rotating User-Agent strings and the shared exception handling required by the framework.
The Async Function Contract
Function Signature
The core of every holehe module is a single async function matching the filename. In holehe/modules/social_media/instagram.py, the function is defined as:
async def instagram(email, client, out):
The function accepts three parameters:
email– The target email address as a stringclient– A sharedhttpx.AsyncClientinstance for concurrent HTTP requestsout– A mutable list where the module appends its results
Static Metadata Fields
Inside the function, you must define four static variables that describe the service:
name = "instagram"
domain = "instagram.com"
method = "register"
frequent_rate_limit = True
name– The service identifier used in reportingdomain– The primary domain of the servicemethod– The verification approach, typically"register"or"password_reset"frequent_rate_limit– Boolean flag indicating whether the service aggressively rate-limits requests
Implementing the Network Probe
Crafting Requests
Modules craft HTTP headers and payloads that mimic legitimate browser behavior. Use the random User-Agent helper to avoid detection:
headers = {
"User-Agent": random.choice(ua["browsers"]["chrome"]),
"Accept": "application/json, text/plain, */*",
"Origin": "https://www.instagram.com",
"DNT": "1",
"Connection": "keep-alive",
}
For services requiring CSRF tokens or session cookies, perform preliminary GET requests to extract these values before submitting the email verification request.
Executing HTTP Calls
Use the provided client parameter for all network operations to ensure proper connection pooling and timeout handling:
freq = await client.get(
"https://www.instagram.com/accounts/emailsignup/",
headers=headers
)
token = freq.text.split('{\\"config\\":{\\"csrf_token\\":\\"')[1].split('\\"')[0]
Submit the probe request (typically POST) with the target email embedded in the payload:
data = {
"email": email,
"username": "".join(random.choice(string.ascii_lowercase) for _ in range(8)),
"first_name": "",
"opt_into_one_tap": "false",
}
headers["x-csrftoken"] = token
check = await client.post(
"https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
data=data,
headers=headers,
)
Handling Responses and Building Output
Parse the HTTP response to determine the email status. Append a standardized dictionary to the out list with exactly these nine keys:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": False, # True if blocked by rate limiting
"exists": True, # True if email is registered
"emailrecovery": None, # Recovery email if exposed
"phoneNumber": None, # Phone number if exposed
"others": None # Dictionary for additional metadata
})
Error Handling and Rate Limits
Wrap all network logic in try-except blocks to prevent individual module failures from crashing the entire scan. When exceptions occur or unexpected HTTP responses are received, append a rate-limit entry:
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
})
This ensures the main engine in holehe/core.py can continue execution even if a specific service is offline or has changed its API.
Complete Module Example
Here is the full structure of holehe/modules/social_media/instagram.py, demonstrating the complete implementation pattern:
# holehe/modules/social_media/instagram.py
from holehe.core import *
from holehe.localuseragent import *
async def instagram(email, client, out):
name = "instagram"
domain = "instagram.com"
method = "register"
frequent_rate_limit = True
headers = {
"User-Agent": random.choice(ua["browsers"]["chrome"]),
"Accept": "application/json, text/plain, */*",
"Origin": "https://www.instagram.com",
"DNT": "1",
"Connection": "keep-alive",
}
# Obtain CSRF token
freq = await client.get(
"https://www.instagram.com/accounts/emailsignup/", headers=headers
)
token = freq.text.split('{\\"config\\":{\\"csrf_token\\":\\"')[1].split('\\"')[0]
data = {
"email": email,
"username": "".join(
random.choice(string.ascii_lowercase + string.digits)
for _ in range(random.randint(6, 30))
),
"first_name": "",
"opt_into_one_tap": "false",
}
headers["x-csrftoken"] = token
# Submit registration attempt
check = await client.post(
"https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
data=data,
headers=headers,
)
check = check.json()
# Interpret response and record result
if check["status"] != "fail":
if "email" in check["errors"]:
if check["errors"]["email"][0]["code"] == "email_is_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
})
else:
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None
})
How the Core Engine Loads Modules
The orchestration logic in holehe/core.py discovers and executes modules dynamically using introspection:
# Dynamic module discovery
modules = import_submodules("holehe.modules")
websites = get_functions(modules, args)
# Concurrent execution with shared client
client = httpx.AsyncClient(timeout=args.timeout)
out = []
async with trio.open_nursery() as nursery:
for website in websites:
nursery.start_soon(launch_module, website, email, client, out)
The import_submodules("holehe.modules") function walks the package tree to find all Python files, while get_functions() extracts the callable probe functions. The engine uses Trio for structured concurrency, passing the shared httpx.AsyncClient to each module to enable efficient connection reuse across hundreds of simultaneous checks.
Summary
- File Location: Place modules in
holehe/modules/<category>/<service>.pyfollowing the existing directory structure - Function Signature: Implement exactly one async function accepting
(email, client, out)parameters - Static Metadata: Define
name,domain,method, andfrequent_rate_limitvariables inside the function - Output Contract: Append dictionaries with nine specific keys (
name,domain,method,frequent_rate_limit,rateLimit,exists,emailrecovery,phoneNumber,others) to theoutlist - Error Handling: Catch all exceptions and report them as
rateLimit: Trueentries to ensure scan continuity - Discovery: The core engine uses
import_submodules()andget_functions()inholehe/core.pyto find and execute modules automatically
Frequently Asked Questions
What is the exact function signature for a holehe module?
Every holehe module must expose exactly one async function with the signature async def servicename(email, client, out). The function name must match the filename (e.g., instagram.py contains async def instagram(...)). The email parameter receives the target string, client provides a shared httpx.AsyncClient, and out is a list where results are appended.
How does holehe handle concurrent execution of modules?
The core engine in holehe/core.py uses the Trio library for structured concurrency. It creates an open nursery with trio.open_nursery() and starts each module as a separate task using nursery.start_soon(launch_module, ...). All modules share a single httpx.AsyncClient instance passed as the client parameter, enabling efficient connection pooling across hundreds of simultaneous probes.
What fields are required in the holehe module output dictionary?
Each module must append a dictionary to the out list containing exactly nine keys: name (service identifier), domain (service domain), method (check type), frequent_rate_limit (boolean flag), rateLimit (boolean indicating if blocked), exists (boolean indicating if email is registered), emailrecovery (extracted recovery email or None), phoneNumber (extracted phone or None), and others (dictionary for additional data or None).
Where should I place a new custom holehe module?
Create your new module file in holehe/modules/<category>/your_service.py, where <category> is a thematic folder like social_media, shopping, or software. The holehe/modules/__init__.py file enables Python to treat this as a package, and the dynamic importer in holehe/core.py will automatically discover your module as long as it follows the standard file structure and function naming conventions.
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 →