mail-autodiscover is a small deploy-and-forget HTTP service for self-hosted mail admins.
It exposes standard autodiscovery endpoints for:
- Outlook Autodiscover
- Thunderbird Autoconfig
- Apple Mail
.mobileconfig - POP3 in Thunderbird config when enabled for a domain
The product goal is deliberately narrow:
- stateless container
- no admin panel
- no database
- no mailbox existence checks
- no LDAP / Synology / directory lookups
If you want a small service you can deploy behind HTTPS and mostly forget about, this is what it is for.
For any syntactically valid mailbox in an allowed domain, the service returns the configured IMAP/SMTP settings and, optionally, POP3 for Thunderbird.
It does not:
- verify whether a mailbox exists
- expose your internal domain list on the landing page
- return different answers for existing vs non-existing mailboxes
- manage users or credentials
- Copy
.env.exampleto.env. - Choose one configuration mode:
- single/global config in ENV
- multi-domain config in
config/config.example.yaml
- Deploy with Docker Compose or Portainer.
- Put the service behind HTTPS reverse proxy.
- Point
autodiscover.andautoconfig.DNS records to that proxy. - Test the endpoints before sharing them with users.
APP_ENV=production
PUBLIC_BASE_URL=https://autodiscover.example.com
ALLOWED_DOMAINS=example.com,example.org
MAIL_DISPLAY_NAME=Example Mail
MAIL_DISPLAY_SHORT_NAME=Example
IMAP_HOST=mail.example.com
IMAP_PORT=993
IMAP_SOCKET_TYPE=SSL
SMTP_HOST=mail.example.com
SMTP_PORT=587
SMTP_SOCKET_TYPE=STARTTLS
POP3_ENABLED=false
TRUST_PROXY_HEADERS=true
TRUSTED_PROXY_IPS=127.0.0.1,10.0.0.0/8This mode serves the same IMAP/SMTP profile for every domain listed in ALLOWED_DOMAINS.
Set:
CONFIG_FILE=/config/config.yamlThen mount a file like config/config.example.yaml.
If CONFIG_FILE exists, domain settings are loaded from YAML and the application ignores the global ALLOWED_DOMAINS/IMAP_*/SMTP_* values for request routing.
USERNAME_FORMAT is not per-domain in YAML mode; it still comes from ENV and applies to every configured domain.
For local builds:
docker compose up -dFor GHCR images:
docker compose -f docker-compose.ghcr.yml up -dThe compose examples mount ./config:/config:ro, so placing config/config.yaml next to the compose file is enough for multi-domain mode.
Prebuilt images are published to GHCR:
ghcr.io/solarssk/mail-autodiscover:latest
ghcr.io/solarssk/mail-autodiscover:0.3.2
Prefer pinned tags or digests in production.
TRUST_PROXY_HEADERS=falseis the safe default.- Enable
TRUST_PROXY_HEADERS=trueonly when traffic really arrives through your own proxy. - In production,
TRUSTED_PROXY_IPSis required whenever proxy header trust is enabled. - Thunderbird and Apple Mail use
?emailaddress=user@example.comin the query string.
Important:
Your reverse proxy may log the full query string even though this application does not. Review access log settings for:
/mail/config-v1.1.xml/.well-known/autoconfig/mail/config-v1.1.xml/mail/ios.mobileconfig/.well-known/apple-mail.mobileconfig
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness probe |
GET |
/ready |
Readiness probe |
GET |
/ |
Minimal landing page |
GET |
/robots.txt |
No-index hint |
GET |
/mail/config-v1.1.xml?emailaddress=... |
Thunderbird Autoconfig |
GET |
/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=... |
Thunderbird alias |
GET |
/mail/ios.mobileconfig?emailaddress=... |
Apple Mail profile |
GET |
/.well-known/apple-mail.mobileconfig?emailaddress=... |
Apple Mail alias |
POST |
/autodiscover/autodiscover.xml |
Outlook Autodiscover |
GET |
/autodiscover/autodiscover.xml |
Neutral Outlook response |
- Profiles are unsigned by default.
- iOS and macOS warn about unsigned profiles in self-hosted setups; this is expected.
- Profiles never contain mailbox passwords.
- Re-downloading the same mailbox profile updates the existing profile because identifiers are stable.
- If you need signed profiles, sign them externally before distribution.
X-Request-IDis attached to responses and access logs.- Access logs never include the full mailbox address.
STRUCTURED_JSON_LOGS=trueswitches access logging to JSON./metricsis intentionally not built in.
- Reverse Proxy and DNS
- Nginx
- Caddy
- Nginx Proxy Manager
- Synology Reverse Proxy
- Cloudflare Tunnel / Generic Proxy
- Outlook client setup
- Thunderbird client setup
- Apple Mail client setup
- Troubleshooting
- no mailbox enumeration
- no full email address logging
- safe XML parsing
- bounded in-memory rate limiting
- security headers enabled by default
- no admin API
See SECURITY.md for the detailed trust model.
python3 -m venv .venv
source .venv/bin/activate
pip install ".[dev]"
pre-commit install
cp .env.example .env
uvicorn app.main:app --host 0.0.0.0 --port 8000 --no-access-log --reloadFor development with placeholder values, set APP_ENV=test or APP_ENV=development.
Run the same Python checks as GitHub CI:
./scripts/check.shpre-commit install also runs ruff, mypy, and pytest automatically before each commit.
Developer workflow and release process live in CONTRIBUTING.md.