How to Embed Holehe in a Python Application and Call Individual Modules Like `snapchat`

You can embed holehe in a Python application by importing async module functions directly from holehe.modules and calling them with an httpx.AsyncClient and a mutable results list, bypassing the CLI entirely.

Holehe is an email reconnaissance tool that checks whether an address exists on hundreds of platforms. While the command-line interface runs all checks by default, you can embed specific modules—such as snapchat—into your own Python codebase for targeted, asynchronous lookups.

How Holehe's Module System Works

The megadose/holehe codebase is architected around dynamic module discovery and uniform async function signatures. Understanding this structure lets you call individual services programmatically.

Dynamic Loading Architecture

Holehe discovers services through two key functions in holehe/core.py:

  • import_submodules() (lines 37–48): Uses pkgutil.walk_packages to traverse holehe.modules and returns a dictionary of all loaded submodules.
  • get_functions() (lines 50–60): Extracts callable service functions from those modules, producing the list that the CLI iterates over.

Each service module contains an async function matching this signature:

async def <service_name>(email: str, client: httpx.AsyncClient, out: list) -> None

The function performs HTTP requests and appends a standardized result dictionary to out. No return value—the result is always appended to the mutable list.

Embedding Individual Modules

Import and Call a Single Service

The simplest approach imports a specific module—like snapchat—and invokes it directly:

import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat

async def check_snapchat(email: str):
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        await snapchat(email, client, out)
        return out[0]

result = asyncio.run(check_snapchat("example@example.com"))
print(result)

Key requirements:

  • Import the concrete module from its path (e.g., holehe.modules.social_media.snapchat).
  • Pass an httpx.AsyncClient—synchronous clients will fail.
  • Provide a list (out) for the result; the module always appends exactly one dictionary.

Constraining Multiple Services with Shared Client

Reuse the same httpx.AsyncClient across multiple modules for efficient connection pooling:

import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat
from holehe.modules.social_media.instagram import instagram
from holehe.modules.social_media.twitter import twitter

async def check_multiple(email: str, targets: set):
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        
        # Map target names to imported functions

        module_map = {
            "snapchat": snapchat,
            "instagram": instagram,
            "twitter": twitter,
        }
        
        selected = [module_map[t] for t in targets if t in module_map]
        await asyncio.gather(*(
            fn(email, client, out) for fn in selected
        ))
        
        return {r["name"]: r for r in out}

results = asyncio.run(check_multiple(
    "example@example.com",
    {"snapchat", "instagram"}
))

This pattern enables concurrent execution with asyncio.gather, reducing total latency versus sequential calls.

Bridging to Synchronous Code

Wrapper for Blocking Contexts

When your application isn't async-native, use asyncio.run() as a bridge:

import asyncio
import httpx
from holehe.modules.social_media.snapchat import snapchat

def check_snapchat_sync(email: str, timeout: float = 10.0):
    """Synchronous wrapper for the snapchat module."""
    async def _run():
        async with httpx.AsyncClient(timeout=timeout) as client:
            out = []
            await snapchat(email, client, out)
            return out[0] if out else None
    
    return asyncio.run(_run())

# Use from ordinary Python code

result = check_snapchat_sync("example@example.com")
print(f"Snapchat: {result.get('exists') if result else 'error'}")

Performance note: Creating a new event loop per call adds overhead. For high-throughput scenarios, consider restructuring to async-native code or using an executor pool.

Leveraging Holehe's Discovery for Filtered Scans

If you want holehe's automatic module discovery but need to restrict execution, combine import_submodules and get_functions:

import asyncio
import httpx
from holehe.core import import_submodules, get_functions

async def filtered_scan(email: str, allowlist: set[str]):
    """
    Run only specified modules using holehe's built-in discovery.
    """
    modules = import_submodules("holehe.modules")
    all_functions = get_functions(modules)
    
    selected = [
        fn for fn in all_functions 
        if fn.__name__ in allowlist
    ]
    
    async with httpx.AsyncClient(timeout=10) as client:
        out = []
        await asyncio.gather(*(
            fn(email, client, out) for fn in selected
        ))
        return out

# Check only snapchat through discovery

results = asyncio.run(filtered_scan(
    "example@example.com",
    {"snapchat"}
))

This approach future-proofs your code: new modules appear automatically, but you control which execute.

Understanding the Result Format

Every holehe module returns a dictionary with consistent fields:

Field Type Description
name str Service identifier (e.g., "snapchat")
domain str Service domain
method str HTTP method used for verification
frequent_rate_limit bool Whether the service aggressively rate-limits
rateLimit bool Whether this call hit a rate limit
exists bool Whether the email is registered
emailrecovery str | None Partial recovery email if exposed
phoneNumber str | None Partial phone if exposed
others dict | None Additional metadata

Access fields directly: result["exists"] indicates registration status.

Source Code References

Critical files in megadose/holehe for embedded usage:

Summary

  • Import directly from holehe.modules.<category>.<service> to bypass CLI overhead.
  • Use httpx.AsyncClient—modules expect async HTTP clients exclusively.
  • Pass a mutable list as out; results are appended, not returned.
  • Share clients across multiple module calls for connection efficiency.
  • Bridge with asyncio.run() when embedding in synchronous codebases.
  • Reuse discovery logic via import_submodules and get_functions for dynamic but filtered execution.

Frequently Asked Questions

Can I use requests instead of httpx with holehe modules?

No. All holehe modules are implemented as async def functions expecting httpx.AsyncClient. The source code in files like holehe/modules/social_media/snapchat.py uses client.get() and client.post() methods specific to httpx. To use requests, you would need to rewrite the module or run it in a separate process.

Why does the module append to a list instead of returning a value?

This design enables concurrent execution with asyncio.gather. Since gather collects return values in order, but modules run at unpredictable speeds, the shared out list allows thread-safe result collection without complex synchronization. The CLI in holehe/core.py relies on this same pattern.

How do I handle rate limits when embedding holehe?

Check the rateLimit and frequent_rate_limit fields in the result dictionary. Services marked frequent_rate_limit=True (visible in each module's source) require careful throttling. Implement delays between calls or use asyncio.Semaphore to limit concurrency: semaphore = asyncio.Semaphore(5) then async with semaphore: around each module call.

Can I modify holehe modules to add proxy support?

Yes. The httpx.AsyncClient accepts a proxies parameter. When embedding, instantiate your client with httpx.AsyncClient(proxies={"https://": "http://proxy:8080"}) and pass it to the module. The module will use your configured client for all requests without source modification.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →