A production-ready, secure, and privacy-focused Haraka email server for handling anonymous email forwarding.
- Anonymous Forwarding: Accepts emails for
@anon.lialiases and custom domains, forwarding them to real recipients - Reply System: Handles replies via
@reply.anon.litokenized addresses to hide user identities - PGP Encryption: Automatically encrypts forwarded emails if the recipient has uploaded a public key
- Privacy Protection: Removes tracking pixels, cleans HTML, and sanitizes dangerous content
- SRS Support: Implements Sender Rewriting Scheme to maintain SPF compliance when forwarding
- DKIM Signing: Signs all outbound mail to ensure deliverability
- Rate Limiting: Redis-backed rate limiting to prevent abuse
- Custom Domains: Supports user-verified custom domains with per-domain DKIM keys
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ External │ │ Haraka │ │ Next.js API │
│ Sender │────▶│ MTA │────▶│ (Prisma) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Redis │ │ PostgreSQL │
│ (Rate Limit) │ │ (Aliases) │
└─────────────────┘ └─────────────────┘
- Docker and Docker Compose
- Node.js 20+ (for local development)
- Redis
- Access to the Next.js frontend API
-
Clone and configure:
cp .env.example .env # Edit .env with your MAIL_API_SECRET -
Generate DKIM keys:
./scripts/generate-dkim.sh anon.li ./scripts/generate-dkim.sh reply.anon.li
The generated keys stay on the host under
config/dkim/and are mounted into the container at runtime. On startup, the entrypoint stages them into a private runtime directory owned by theharakauser, so host-side0600permissions remain safe and ARC/DKIM can still read them. They are intentionally not baked into the Docker image. -
Add DNS records (output from script):
default._domainkey.anon.li. IN TXT "v=DKIM1; k=rsa; p=..." default._domainkey.reply.anon.li. IN TXT "v=DKIM1; k=rsa; p=..." -
Place TLS certificates:
# Place your Let's Encrypt or other TLS certs cat privkey.pem cert.pem chain.pem > config/tls/anon.li.pem
-
Start the server:
docker compose up -d --build
The container now validates
MAIL_API_SECRETand the TLS bundle at startup, and warns when local DKIM keys are missing so deliverability issues are visible immediately.
| Variable | Description | Required |
|---|---|---|
MAIL_API_SECRET |
Shared secret for API authentication | Yes |
FRONTEND_URL |
URL of Next.js API (default: http://app:3000) |
No |
UPSTASH_REDIS_REST_URL |
Upstash Redis REST endpoint (rate limiting) | No |
UPSTASH_REDIS_REST_TOKEN |
Upstash Redis auth token | No |
DKIM_REQUIRED_DOMAINS |
Space-separated local DKIM domains to warn about at startup | No |
LOCAL_DKIM_DIR |
Internal runtime directory for staged DKIM keys | No |
| Plugin | Purpose |
|---|---|
tls |
STARTTLS enforcement |
limit-upstash |
IP-based rate limiting (Redis + in-memory fallback) |
mailauth |
Inbound SPF / DKIM / DMARC validation |
rcpt_to.bounce |
Validates SRS bounce return-paths |
rcpt_to.anonli |
Validates aliases + per-alias rate limiting |
rcpt_to.reply |
Handles reply token addresses |
data.loop_detect |
Rejects mail loops |
data.tracking_remove |
Strips tracking pixels / UTM params |
data.spam_check |
Spam scoring |
arc.sign |
ARC sealing for forwarded mail |
dkim.custom |
Per-domain DKIM signing (local keys or API-fetched) |
queue.forward |
Main forwarding (SRS, PGP, reply rewriting) |
; MX Record
anon.li. IN MX 10 mx.anon.li.
; SPF
anon.li. IN TXT "v=spf1 mx a:mx.anon.li -all"
; DKIM (generated by script)
default._domainkey.anon.li. IN TXT "v=DKIM1; k=rsa; p=..."
; DMARC
_dmarc.anon.li. IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@anon.li"
Users must add:
- MX record pointing to
mx.anon.li - SPF record including
include:_spf.anon.li - DKIM record (provided in dashboard)
- Verification TXT record
- TLS 1.2+ required for all connections
- Timing-safe API secret comparison
- Input validation on all email addresses
- Rate limiting per IP and globally
- DKIM/SPF/DMARC for email authentication
- PGP encryption for enhanced privacy
- Tracking removal for privacy protection
Logs are output in JSON format for easy parsing:
docker compose logs -f harakaProduction notes:
- DKIM private keys and TLS certificates are mounted at runtime; keep them out of the image build context.
- DKIM host mounts are staged into
LOCAL_DKIM_DIRon startup so the unprivilegedharakaprocess can read them without loosening host file permissions. - ARC signing reuses the same key lookup path as DKIM signing: staged local key first, API fallback second.
- Docker Compose requests an IPv6-enabled user-defined bridge network for Haraka.
- Outbound delivery uses Haraka's default dual-stack behavior, which prefers IPv6 when the container has working IPv6 egress.
- Docker daemon IPv6 settings in
/etc/docker/daemon.jsonare optional for this setup. They are only needed if you want to customize Docker's IPv6 address pools or enable IPv6 on the defaultbridgenetwork.
Health check endpoint:
nc -z localhost 25 && echo "OK"# Install dependencies
npm install
# Run locally (requires Haraka global install)
npm install -g Haraka
npm start
# Run tests
npm testGNU Affero General Public License v3.0 (AGPL-3.0-only).
Because anon.li-mx is network-facing server software, the AGPL requires that anyone running a modified version on a publicly accessible server make the corresponding source code available to its users.