Self-Hosting Guide

August 28, 2026 · View on GitHub

MailAccess

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

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: always on all services

.env Reference

Every setting is optional unless marked required.

Database

VariableDefaultDescription
DATABASE_URLsqlite+aiosqlite:///./data/mailaccess.dbFull SQLAlchemy async connection URL. Leave blank for SQLite.

Application

VariableDefaultDescription
DEBUGfalseEnable FastAPI debug mode and verbose tracebacks
LOG_LEVELINFOPython logging level: DEBUG, INFO, WARNING, ERROR
CORS_ORIGINShttp://localhost:5173Comma-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_BRIEFtrueShow Defender's Brief in CLI output. Set false to suppress for all investigations.

Worker

VariableDefaultDescription
MAX_CONCURRENT_MODULES10Maximum number of modules that run in parallel per investigation
MODULE_TIMEOUT_SECONDS30Per-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_CACHEtrueCache complete investigation results; repeated queries within the window return instantly
INVESTIGATION_CACHE_WINDOW_MINUTES30How long a cached result is considered fresh (minutes)

Modules

VariableDefaultDescription
ENABLE_BREACH_DEEPfalseOpt-in deep breach probe
BREACH_DEEP_LIMIT100Sites to probe; max 750
BREACH_DEEP_FULLfalseProbe all 750 HIBP sites
ENABLE_EMAIL_DISCOVERYtrueName-to-email dorks
ENABLE_MAIGRET_PLATFORMStrueDefault true. Set to false to disable 2500+ platform sweep and reduce investigation time by ~35-90s.
ENABLE_MAIGRET_WAVE2falseOptional. Enable Wave 2 slow/fragile platform sweep. Requires ENABLE_MAIGRET_PLATFORMS=true. Adds ~90-150s.
MAILACCESS_DISABLE_HEALTH0Set 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_HEALTHfalsePhase 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_CAP20Maximum platform domains checked for infrastructure clustering.
enable_domain_clustertrueEnable 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_EMAILtrueMaster kill switch for the Common Crawl email module (domain harvest mode only).
CC_MAX_RECORDS100Maximum Common Crawl URL Index records fetched per harvest run.
CC_FETCH_CONCURRENCY10Concurrent 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_DORKtrueMaster kill switch for the search-engine dork module (DuckDuckGo + Bing, domain harvest mode only).
DORK_MAX_QUERIES_PER_ENGINE5Maximum dork queries per engine per harvest. 5 covers all core patterns while staying below CAPTCHA thresholds.
DORK_LITE_MODEfalseLite mode runs only the first two dork patterns per engine. Faster, lower yield.
ENABLE_CODE_AND_CERT_EMAILtrueMaster kill switch for the GitHub + certificate-transparency module (domain harvest mode only).
GITHUB_EMAIL_MAX_RESULTS30Maximum GitHub code-search results per harvest. 30 is the default per research.
ENABLE_EMPLOYEE_NAME_DISCOVERYtrueMaster kill switch for the multi-source employee name discovery module. Feeds pattern generation.
EMPLOYEE_NAME_MAX_COMPANY_PAGES5Maximum company about / team / leadership pages probed per harvest.
ENABLE_SMTP_VERIFICATIONfalseOPT-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_DOMAIN100Hard cap on SMTP probes per domain per run. Cannot be raised higher than 100 in source.
SMTP_PROBE_DELAY_SECONDS2.5Per-request delay between SMTP probes. 2.5s = ~24 probes/min, well under the 30/min ceiling.
ENABLE_NPM_EMAILtrueMaster kill switch for the npm registry package-author module.
ENABLE_PYPI_EMAILtrueMaster kill switch for the PyPI registry package-author module.
ENABLE_PGP_DOMAIN_EMAILtrueMaster 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:

ModeTypical runtime
Default (no SMTP)30 seconds – 2 minutes
With --verify-smtp2 – 5 minutes
Large domains with --verify-smtp5 – 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-audit shows 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

VariableDefaultDescription
RATE_LIMIT_ENABLEDtrueGlobal toggle for rate limiting
REQUEST_DELAY_MS1000Default 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

VariableDefaultDescription
PROXY_URL(unset)Proxy URL for all outbound requests. Examples: socks5://127.0.0.1:9050 (Tor), http://user:pass@proxy:8080
PROXY_ENABLEDfalseSet 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

VariableDescription
SLACK_WEBHOOK_URLSlack incoming webhook URL for investigation completion notifications
DISCORD_WEBHOOK_URLDiscord webhook URL
INTEGRATION_WEBHOOK_URLGeneric HTTP POST endpoint
INTEGRATION_WEBHOOK_SECRETOptional 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.

VariableUsed byWhere to get
HIBP_API_KEYhibp modulehttps://haveibeenpwned.com/API/Key
SERPAPI_KEYgoogle_dork modulehttps://serpapi.com
GITHUB_TOKENgithub_commits module (optional)https://github.com/settings/tokens
COMPANIES_HOUSE_API_KEYcompanies_house modulehttps://developer.company-information.service.gov.uk
SHODAN_API_KEYdomain_intel (optional), shodan modulehttps://account.shodan.io
EMAILREP_API_KEYemailrep module (raises rate limit)https://emailrep.io
HUNTER_IO_API_KEYhunter_io modulehttps://hunter.io
VIRUSTOTAL_API_KEYReserved for future modulehttps://virustotal.com
FULLCONTACT_API_KEYReserved for future modulehttps://fullcontact.com
CLEARBIT_API_KEYReserved for future modulehttps://clearbit.com

Enabling Opt-in Modules

Six opt-in features require explicit enabling per run or via .env:

ModuleDescription
breach_deepProbes 100 breach sites (slow, ~90 s)
ghuntDeep Gmail intel (requires one-time ghunt login setup)
press_intelPress release contact extraction for business domains
email_discoveryName → email dorks (requires SERPAPI_KEY)
maigret_platformsNative Maigret engine across 2500+ platforms (set ENABLE_MAIGRET_PLATFORMS=true)
maigret_platforms Wave 2Slower 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:

ModuleFast connectionSlow connection
whatsmyname120240
account_discovery90180
username_pivot180360
user_scanner180300

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:

  1. Run a Tor SOCKS5 proxy (the default port is 9050):

    docker run -d -p 9050:9050 dperson/torproxy
    
  2. Add to .env:

    PROXY_URL=socks5://127.0.0.1:9050
    PROXY_ENABLED=true
    
  3. 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: partial or status: failed and log the error.


Maltego Transform Import

MailAccess generates a Maltego configuration bundle (.mtz) automatically at startup, written to maltego/MailAccess.mtz.

Import steps

  1. Start MailAccess (docker compose up).
  2. The bundle is created at ./maltego/MailAccess.mtz on the host.
  3. Open Maltego Desktop.
  4. Go to Import/ExportImport Config.
  5. Select MailAccess.mtz and complete the wizard.
  6. In the transform settings, confirm the Transform URL is set to your MailAccess instance, e.g. http://localhost:8000/maltego/email_investigate.
  7. 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.