Self-Hosting Guide
August 28, 2026 · View on GitHub
Self-Hosting Guide
CLI-only install (no Docker needed)
pip install mailaccess
# Investigations auto-start the backend when needed.
mailaccess investigate you@example.com
# The server starts automatically, runs the investigation, and stops when done.
# For multiple investigations, keep one persistent server running:
mailaccess serve
Requirements
- Docker and Docker Compose v2 (for the container path)
- Python 3.11+ and Node 18+ (for the manual path)
- 512 MB RAM minimum; 1 GB recommended when running all modules concurrently
Docker Compose — Development
cp .env.example .env
# Edit .env to add any API keys you want
docker compose up
- Backend: http://localhost:8000
- Frontend: http://localhost:3000
- Hot-reload is enabled on both services in development mode
- The
./data/directory is mounted into the container for SQLite persistence
Docker Compose — Production
cp .env.example .env
# Set MAILACCESS_API_KEY and any module keys
docker compose -f docker-compose.prod.yml up -d
Differences from the dev compose file:
- Frontend is built and served by nginx on port 80
- Backend runs without
--reload - Healthchecks on all services — frontend waits for backend to be healthy before starting
restart: alwayson all services
.env Reference
Every setting is optional unless marked required.
Database
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | sqlite+aiosqlite:///./data/mailaccess.db | Full SQLAlchemy async connection URL. Leave blank for SQLite. |
Application
| Variable | Default | Description |
|---|---|---|
DEBUG | false | Enable FastAPI debug mode and verbose tracebacks |
LOG_LEVEL | INFO | Python logging level: DEBUG, INFO, WARNING, ERROR |
CORS_ORIGINS | http://localhost:5173 | Comma-separated list of allowed CORS origins |
MAILACCESS_API_KEY | (unset) | When set, all /api/ routes require X-API-Key: <value>. Leave blank for open access. |
SHOW_DEFENDERS_BRIEF | true | Show Defender's Brief in CLI output. Set false to suppress for all investigations. |
Worker
| Variable | Default | Description |
|---|---|---|
MAX_CONCURRENT_MODULES | 10 | Maximum number of modules that run in parallel per investigation |
MODULE_TIMEOUT_SECONDS | 30 | Per-module timeout; modules that exceed this are cancelled and marked failed |
MODULE_TIMEOUT_OVERRIDES | {} | Per-module timeout overrides as a JSON object (values in seconds). Example: {"whatsmyname": 120, "account_discovery": 90} |
ENABLE_INVESTIGATION_CACHE | true | Cache complete investigation results; repeated queries within the window return instantly |
INVESTIGATION_CACHE_WINDOW_MINUTES | 30 | How long a cached result is considered fresh (minutes) |
Modules
| Variable | Default | Description |
|---|---|---|
ENABLE_BREACH_DEEP | false | Opt-in deep breach probe |
BREACH_DEEP_LIMIT | 100 | Sites to probe; max 750 |
BREACH_DEEP_FULL | false | Probe all 750 HIBP sites |
ENABLE_EMAIL_DISCOVERY | true | Name-to-email dorks |
ENABLE_MAIGRET_PLATFORMS | true | Default true. Set to false to disable 2500+ platform sweep and reduce investigation time by ~35-90s. |
ENABLE_MAIGRET_WAVE2 | false | Optional. Enable Wave 2 slow/fragile platform sweep. Requires ENABLE_MAIGRET_PLATFORMS=true. Adds ~90-150s. |
MAILACCESS_DISABLE_HEALTH | 0 | Set to 1 to bypass platform-health skip decisions without deleting the SQLite probe history. |
MAIGRET_FORCE_{PLATFORM} | (unset) | Per-platform demotion override. Replace {PLATFORM} with the uppercase, non-alphanumeric-stripped name. Example: MAIGRET_FORCE_GITHUBCOM=true. Any truthy value (true, 1, yes, on) wins. |
MAILACCESS_SHARE_HEALTH | false | Phase 6D.3 documentation-only flag for mailaccess platform-health --share. The CLI requires the explicit --share flag — this env var is documentation only and never triggers sharing. |
DOMAIN_CLUSTER_CAP | 20 | Maximum platform domains checked for infrastructure clustering. |
enable_domain_cluster | true | Enable or disable domain infrastructure clustering. |
GITHUB_TOKEN | (unset) | Optional. Required for GitHub commit author-email search. Without it, github_commits runs user profile search only. Get at: github.com/settings/tokens |
COMPANIES_HOUSE_API_KEY | (unset) | Optional. UK Companies House officer/address lookup. Free, no CC. Get at: developer.company-information.service.gov.uk |
ENABLE_COMMONCRAWL_EMAIL | true | Master kill switch for the Common Crawl email module (domain harvest mode only). |
CC_MAX_RECORDS | 100 | Maximum Common Crawl URL Index records fetched per harvest run. |
CC_FETCH_CONCURRENCY | 10 | Concurrent page fetches (WARC range + direct GET). Keeps well under S3 and target-site limits. |
~/.mailaccess/cache/commoncrawl_collections.json | (automatic) | Last known Common Crawl collection list used when the public index service is temporarily unavailable. |
ENABLE_EMAIL_SEARCH_DORK | true | Master kill switch for the search-engine dork module (DuckDuckGo + Bing, domain harvest mode only). |
DORK_MAX_QUERIES_PER_ENGINE | 5 | Maximum dork queries per engine per harvest. 5 covers all core patterns while staying below CAPTCHA thresholds. |
DORK_LITE_MODE | false | Lite mode runs only the first two dork patterns per engine. Faster, lower yield. |
ENABLE_CODE_AND_CERT_EMAIL | true | Master kill switch for the GitHub + certificate-transparency module (domain harvest mode only). |
GITHUB_EMAIL_MAX_RESULTS | 30 | Maximum GitHub code-search results per harvest. 30 is the default per research. |
ENABLE_EMPLOYEE_NAME_DISCOVERY | true | Master kill switch for the multi-source employee name discovery module. Feeds pattern generation. |
EMPLOYEE_NAME_MAX_COMPANY_PAGES | 5 | Maximum company about / team / leadership pages probed per harvest. |
ENABLE_SMTP_VERIFICATION | false | OPT-IN ONLY. Documentation flag for SMTP RCPT TO probing. The only path that actually enables SMTP is the --verify-smtp CLI flag — this env var is never read as an enable switch. |
SMTP_MAX_PROBES_PER_DOMAIN | 100 | Hard cap on SMTP probes per domain per run. Cannot be raised higher than 100 in source. |
SMTP_PROBE_DELAY_SECONDS | 2.5 | Per-request delay between SMTP probes. 2.5s = ~24 probes/min, well under the 30/min ceiling. |
ENABLE_NPM_EMAIL | true | Master kill switch for the npm registry package-author module. |
ENABLE_PYPI_EMAIL | true | Master kill switch for the PyPI registry package-author module. |
ENABLE_PGP_DOMAIN_EMAIL | true | Master kill switch for the PGP keyserver UID module. Lowest yield (1–5%) but highest source weight when it hits. |
Domain Email Harvesting Runtime
mailaccess harvest-emails is a separate, heavier pipeline than
mailaccess investigate. Wall time scales with the target domain's
size and whether SMTP verification is enabled:
| Mode | Typical runtime |
|---|---|
| Default (no SMTP) | 30 seconds – 2 minutes |
With --verify-smtp | 2 – 5 minutes |
Large domains with --verify-smtp | 5 – 25 minutes |
Results are emitted on completion (not streamed), although per-module
progress is shown live as each source finishes. For large domains or
batch harvesting across many targets, run with --lite to reduce the
number of dork queries per engine and cut total runtime.
See docs/harvest-emails.md for the full flag list, the eight source modules, and confidence-scoring details.
MailAccess fetches the HIBP breach corpus on startup and caches it at data/cache/breach_corpus.json for 24h. No API key required for this fetch.
The Maigret platform database (~3 MB JSON) is fetched automatically from GitHub on first use and cached at ~/.mailaccess/cache/maigret-data.json. It refreshes every 24 hours. No manual setup is needed.
To add custom platforms, edit data/mailaccess-extra-sites.json using the same format as Maigret's data.json. These custom additions are merged at runtime and are never overwritten by auto-refresh.
Enabling ENABLE_MAIGRET_PLATFORMS adds 35-90 seconds to investigation time for Wave 1. Wave 2 adds a further 90-150 seconds. For automated or batch use, consider whether the extended coverage is worth the runtime cost for your use case.
The Defender's Brief is generated automatically for every investigation. Suppress it with the CLI --no-brief flag or by setting SHOW_DEFENDERS_BRIEF=false in .env.
Platform Health Self-Healing (Phase 6D)
Note:
mailaccess platform-auditshows platforms that have been probed in your local investigations. This number grows over time. The full platform database (2500+) is checked during every investigation regardless of how many appear in the health DB.
After every investigation, MailAccess automatically adjusts which platforms it probes based on the rolling health statistics:
- Auto-skip: a platform with > 70% inconclusive probes over the last 30 days AND at least 50 probes AND a probe within the last 14 days is excluded from the next investigation's probe queue. This is not a permanent quarantine — the platform is re-evaluated next time.
- Auto-demote: a Wave-1 platform with > 40% inconclusive probes over the last 30 days AND at least 30 probes is moved to Wave 2 for the next investigation.
- Auto-upgrade: a Wave-2 platform with < 10% inconclusive probes over the last 30 days AND at least 30 probes AND a probe within the last 30 days is promoted to Wave 1 for the next investigation.
Stale probe data (older than the freshness window) never triggers an auto-action. We never demote on stale stats.
Platform Health Files
MailAccess maintains two files in ~/.mailaccess/; both live outside the
project repository and are never committed to Git:
platform_health.db— SQLite probe history containing per-platform hit, miss, inconclusive, and latency observations over a rolling window. The self-healing rules use this data for auto-demotion decisions.platform_demotion.log— JSONL audit log containing every auto-demotion and auto-upgrade event.
Audit trail
Every auto-action writes one JSONL line to ~/.mailaccess/platform_demotion.log:
{"timestamp": "2026-06-24T10:00:00Z", "platform": "NoisySite.com", "action": "skip",
"reason": "inconclusive_rate=0.82, probes=134",
"stats": {"inconclusive_rate": 0.82, "hit_rate": 0.08, "total_probes": 134},
"reversible_via": "MAIGRET_FORCE_NOISYSITECOM"}
The log is append-only and one JSON object per line. Use
mailaccess platform-audit --show-demotions to render it as a table with the
override env-var hint for each entry.
Per-platform override
To force a specific platform to run in its native wave regardless of health stats, set its override env var:
MAIGRET_FORCE_NOISYSITECOM=true mailaccess investigate user@example.com
Mapping rule: take the platform name, strip non-alphanumeric characters,
uppercase it, and prefix with MAIGRET_FORCE_. So NoisySite.com becomes
MAIGRET_FORCE_NOISYSITECOM. Any truthy value (true, 1, yes, on)
disables the auto-action for that platform.
Community health sharing (opt-in)
Contribute anonymized platform stats back to the community:
mailaccess platform-health --share
This posts platform-level metadata only (hit / miss / inconclusive rates, average latency, total probes, last probed) to a public GitHub Gist. No user data, no email addresses, no investigation targets, no finding content.
The --share flag is the only way this code path runs. There is no scheduled
share, no background-job share, no investigation-completion share. Setting
MAILACCESS_SHARE_HEALTH=true in .env does not enable sharing — it is
documentation only.
Zombie Investigation Recovery
On startup, MailAccess finds investigations left in RUNNING state for more
than 10 minutes and marks them FAILED with reason Recovered: server restart.
This prevents zombie investigations from accumulating across restarts.
Rate Limiting
| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_ENABLED | true | Global toggle for rate limiting |
REQUEST_DELAY_MS | 1000 | Default minimum delay between requests to the same domain (ms) |
RATE_LIMIT_OVERRIDES | {} | Per-domain overrides as a JSON object (values in ms). Example: {"api.github.com": 500, "haveibeenpwned.com": 1500} |
RATE_LIMIT_DELAYS | {} | Legacy per-domain delays in seconds (kept for compatibility). Use RATE_LIMIT_OVERRIDES for new configs. |
Proxy
| Variable | Default | Description |
|---|---|---|
PROXY_URL | (unset) | Proxy URL for all outbound requests. Examples: socks5://127.0.0.1:9050 (Tor), http://user:pass@proxy:8080 |
PROXY_ENABLED | false | Set true to activate the proxy. The URL is ignored when this is false. |
ScrapingAnt proxy routing is configured separately from the generic
PROXY_URL setting. The REST API key is used for rest_api transport, while
the residential and datacenter proxy transports use the ScrapingAnt proxy
username/password pairs. Select the active transport with
mailaccess configure proxy enable residential|datacenter, inspect it with
mailaccess configure proxy show, and revert with
mailaccess configure proxy disable. When you run
mailaccess harvest-emails --use-proxies, only the proxy-aware harvest
modules use that transport; the rest stay direct.
Webhooks
| Variable | Description |
|---|---|
SLACK_WEBHOOK_URL | Slack incoming webhook URL for investigation completion notifications |
DISCORD_WEBHOOK_URL | Discord webhook URL |
INTEGRATION_WEBHOOK_URL | Generic HTTP POST endpoint |
INTEGRATION_WEBHOOK_SECRET | Optional HMAC secret for signing webhook payloads |
API Keys
All API keys are optional. Modules that require a missing key skip themselves with status: skipped rather than failing.
| Variable | Used by | Where to get |
|---|---|---|
HIBP_API_KEY | hibp module | https://haveibeenpwned.com/API/Key |
SERPAPI_KEY | google_dork module | https://serpapi.com |
GITHUB_TOKEN | github_commits module (optional) | https://github.com/settings/tokens |
COMPANIES_HOUSE_API_KEY | companies_house module | https://developer.company-information.service.gov.uk |
SHODAN_API_KEY | domain_intel (optional), shodan module | https://account.shodan.io |
EMAILREP_API_KEY | emailrep module (raises rate limit) | https://emailrep.io |
HUNTER_IO_API_KEY | hunter_io module | https://hunter.io |
VIRUSTOTAL_API_KEY | Reserved for future module | https://virustotal.com |
FULLCONTACT_API_KEY | Reserved for future module | https://fullcontact.com |
CLEARBIT_API_KEY | Reserved for future module | https://clearbit.com |
Enabling Opt-in Modules
Six opt-in features require explicit enabling per run or via .env:
| Module | Description |
|---|---|
breach_deep | Probes 100 breach sites (slow, ~90 s) |
ghunt | Deep Gmail intel (requires one-time ghunt login setup) |
press_intel | Press release contact extraction for business domains |
email_discovery | Name → email dorks (requires SERPAPI_KEY) |
maigret_platforms | Native Maigret engine across 2500+ platforms (set ENABLE_MAIGRET_PLATFORMS=true) |
maigret_platforms Wave 2 | Slower and more fragile Maigret sweep (set ENABLE_MAIGRET_WAVE2=true) |
Enable for one run using the -m / --enable flag:
mailaccess investigate email -m breach_deep
mailaccess investigate email -m press_intel
mailaccess investigate email -m all
Enable permanently via .env:
ENABLE_BREACH_DEEP=true
ENABLE_MAIGRET_PLATFORMS=true
-m all enables all opt-in modules for the current run only.
Module Timeout Overrides
whatsmyname and account_discovery perform hundreds of HTTP requests per investigation and routinely exceed the default 30-second timeout. Set longer values in MODULE_TIMEOUT_OVERRIDES to prevent them from being cancelled early:
MODULE_TIMEOUT_OVERRIDES={"whatsmyname": 120, "account_discovery": 90, "username_pivot": 180}
Recommended values by connection quality:
| Module | Fast connection | Slow connection |
|---|---|---|
whatsmyname | 120 | 240 |
account_discovery | 90 | 180 |
username_pivot | 180 | 360 |
user_scanner | 180 | 300 |
Modules that hit their timeout return status: partial with whatever findings were collected up to that point.
Investigation Cache
When ENABLE_INVESTIGATION_CACHE=true (the default), a completed investigation result is cached for INVESTIGATION_CACHE_WINDOW_MINUTES minutes. Submitting the same email within that window returns the cached result immediately (cached: true in the response) without running the modules again.
To force a fresh run even when a cached result exists:
# CLI
mailaccess investigate you@example.com --force
# API
POST /api/investigate
{ "email": "you@example.com", "force": true }
To disable caching entirely:
ENABLE_INVESTIGATION_CACHE=false
Switching to PostgreSQL
Docker Compose:
Add the following to your .env:
DATABASE_URL=postgresql+asyncpg://mailaccess:mailaccess@postgres:5432/mailaccess
Then start with the postgres profile:
docker compose --profile postgres up
The postgres service uses postgres:16-alpine with a named volume (postgres_data) for persistence.
Manual / external Postgres:
Set DATABASE_URL to any valid postgresql+asyncpg:// connection string pointing at your database. MailAccess creates tables on startup via init_db() — no manual migration step required for a fresh database.
Proxy and Tor
To route all module HTTP requests through Tor:
-
Run a Tor SOCKS5 proxy (the default port is 9050):
docker run -d -p 9050:9050 dperson/torproxy -
Add to
.env:PROXY_URL=socks5://127.0.0.1:9050 PROXY_ENABLED=true -
Restart MailAccess.
All outbound requests made via build_client() (every module) will be routed through the proxy. The /health endpoint and database connections are not affected.
Some APIs (HIBP, SerpAPI) may block Tor exit nodes. Modules that encounter connection errors return
status: partialorstatus: failedand log the error.
Maltego Transform Import
MailAccess generates a Maltego configuration bundle (.mtz) automatically at startup, written to maltego/MailAccess.mtz.
Import steps
- Start MailAccess (
docker compose up). - The bundle is created at
./maltego/MailAccess.mtzon the host. - Open Maltego Desktop.
- Go to Import/Export → Import Config.
- Select
MailAccess.mtzand complete the wizard. - In the transform settings, confirm the Transform URL is set to your MailAccess instance, e.g.
http://localhost:8000/maltego/email_investigate. - Restart Maltego.
The transform accepts a Maltego EmailAddress entity and returns entities for each finding (breach records, social profiles, domain data).
The
/maltego/endpoint is exempt from API key authentication. If your instance is publicly accessible, restrict it at the network level rather than relying on MailAccess auth.
Manual Installation
Backend
cd backend
pip install -e ".[dev]"
cp ../.env.example ../.env
uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
Frontend
cd frontend
npm install
npm run dev
CLI
pip install -e .
mailaccess investigate user@example.com
README Install Options
CLI only (no Docker)
pip install mailaccess
# The backend auto-starts for a one-off investigation
mailaccess investigate you@example.com
# Or keep it running for multiple investigations
mailaccess serve
mailaccess investigate you@example.com
# Full stack with Web UI
git clone https://github.com/KatrielMoses/MailAccess
docker compose up -d
README Self-Hosting Quick Setup
cp .env.example .env # all API keys are optional
docker compose up # backend :8000 · frontend :3000
Open http://localhost:3000 in your browser. Full setup guide: docs/self-hosting.md.