7.1 KiB
Deployment guide
Agmail runs as one Docker Compose stack on a mail VPS (port 25 open): Caddy (HTTPS), frontend, backend, PostgreSQL, MongoDB, and Postfix (inbound + outbound).
The Agmail stack lives on the mail VPS:
| Hostname | Points to | Purpose |
|---|---|---|
gentic.email |
Mail VPS public IP | Agmail web UI (Caddy → frontend, primary) |
email.getall.help |
Mail VPS public IP | Legacy alternate URL to the same web UI |
mail.gentic.email |
Mail VPS public IP | Postfix :25 (inbound + outbound) |
@gentic.email MX |
mail.gentic.email |
Delivers mail to the VPS |
User addresses remain user@gentic.email.
Architecture
Browser ──HTTPS──► gentic.email / email.getall.help (Caddy :443)
└──► frontend ──► backend ──SMTP──► smtp:25 (Postfix outbound)
Internet ──SMTP:25──► mail.gentic.email (Postfix)
├── inbound → backend:2525 → GCS + MongoDB
└── outbound ← backend (Docker mynetworks)
Quick start
cp .env.example .env
# Edit .env — see Environment section below
docker compose up -d --build
Open https://gentic.email (or https://email.getall.help) after DNS and TLS are ready.
| Service | Host port | Purpose |
|---|---|---|
| caddy | 80, 443 | Public HTTPS entrypoint |
| frontend | 127.0.0.1:8080 | Local debug only |
| backend | 127.0.0.1:8000 | API / health (local) |
| smtp | 25 | Inbound + outbound Postfix |
curl -fsS http://127.0.0.1:8000/health
docker compose ps
docker compose logs -f caddy
Environment (.env at repo root)
Required
JWT_SECRET=<openssl rand -hex 32>
POSTGRES_PASSWORD="<strong password>"
MONGO_ROOT_PASSWORD="<strong password>"
GCS_RAW_MIME_BUCKET=<private bucket>
GCS_PROJECT=<gcp project id>
GCP_CREDENTIALS_FILE=./midyear-karma-470610-i1-da5334d20acc.json
CADDY_ACME_EMAIL=admin@gentic.email
CORS_ORIGINS=["https://gentic.email","https://email.getall.help"]
Quote values that contain #.
GCP_CREDENTIALS_FILE is relative to deploy/app/, not the repo root.
Outbound uses the Docker Postfix service by default (SMTP_HOST=smtp,
SMTP_PORT=25, SMTP_USE_TLS=false). No Amazon SES credentials are required.
Split-domain settings (mail VPS)
# User mailboxes are still @gentic.email
PLATFORM_DOMAIN=gentic.email
# Web UI on this host (Caddy terminates TLS here)
APP_PUBLIC_HOST=gentic.email
CADDY_ALT_HOST=email.getall.help
CADDY_ACME_EMAIL=admin@gentic.email
# Postfix hostname (inbound + outbound)
MAIL_HOSTNAME=mail.gentic.email
MAIL_DOMAIN=gentic.email
INBOUND_DOMAINS=gentic.email,getall.help
SMTP_PUBLIC_HOST=mail.gentic.email
SMTP_HOST=smtp
SMTP_PORT=25
SMTP_USE_TLS=false
# Same-host Docker defaults (usually leave as-is)
BACKEND_INGEST_HOST=backend
BACKEND_INGEST_PORT=2525
INBOUND_ALLOWED_CIDRS=["172.16.0.0/12","127.0.0.0/8"]
INBOUND_SMTP_BIND_IP=127.0.0.1
Namecheap DNS changes
Update DNS as follows (<VPS_IP> = public IP of the mail server). Generate the
exact rows at https://gentic.email/smtp-credentials:
Inbound
| Type | Host | Value | Notes |
|---|---|---|---|
| A | @ |
<VPS_IP> |
Agmail web UI |
| A | mail |
<VPS_IP> |
Postfix |
| MX | @ |
10 mail.gentic.email |
Priority 10 |
Outbound authentication
| Type | Host | Value | Notes |
|---|---|---|---|
| TXT | @ |
v=spf1 mx a ip4:<VPS_IP> ~all |
SPF for @gentic.email mail |
| TXT | _spf |
v=spf1 ip4:<VPS_IP> ~all |
Required — company domains SPF-include this |
| TXT | agmail._domainkey |
app DKIM public key | From /smtp-credentials |
| TXT | _dmarc |
v=DMARC1; p=quarantine; rua=mailto:dmarc@gentic.email |
One record only |
Remove any old Amazon SES Easy DKIM CNAMEs and include:amazonses.com from SPF.
Firewall / security group
| Port | Source | Purpose |
|---|---|---|
| 443 | 0.0.0.0/0 |
HTTPS web UI |
| 80 | 0.0.0.0/0 |
HTTP → HTTPS redirect (Caddy) |
| 25 | 0.0.0.0/0 |
Inbound mail (+ outbound egress) |
Do not expose 5432, 27017, 8000, or 2525 publicly.
Confirm egress on port 25 is allowed (required for delivery to Gmail/Outlook):
nc -vz -w 5 gmail-smtp-in.l.google.com 25
Reverse DNS (PTR) — required for Gmail/Outlook
Major receivers expect your VPS IP reverse DNS to match mail.gentic.email.
dig +short -x YOUR_VPS_IP
# Good: mail.gentic.email.
Set PTR in your VPS provider panel (is*hosting: client area → network /
rDNS, or open a support ticket) to mail.gentic.email.
STARTTLS on Postfix (port 25)
Gmail/Zoho use STARTTLS. Postfix generates a cert on first boot. After rebuild:
printf 'EHLO test\r\nSTARTTLS\r\n' | nc mail.gentic.email 25
# expect: 220 Ready to start TLS (not 454 TLS not available)
Caddy / TLS
Caddy obtains Let's Encrypt certificates automatically when:
APP_PUBLIC_HOSTandCADDY_ALT_HOSTresolve to this server's public IP- Ports 80 and 443 are open
CADDY_ACME_EMAILis set in.env
Config: deploy/caddy/Caddyfile → reverse-proxies to frontend:80.
Smoke test
# HTTPS UI
curl -fsSI https://gentic.email/health
curl -fsSI https://email.getall.help/health
# Inbound mail (account must exist)
swaks \
--server mail.gentic.email:25 \
--from sender@example.com \
--to existing-user@gentic.email \
--header "Subject: inbound smoke test" \
--body "Verify delivery"
# Outbound queue after sending from the UI
docker exec agmail-smtp postqueue -p
curl -fsS http://127.0.0.1:8000/health
Operations
docker compose logs -f caddy backend smtp
docker compose restart caddy
docker exec agmail-smtp postqueue -p
Persistent volumes: postgres_data, mongo_data, smtp_queue, caddy_data.
TLS troubleshooting (SSL_ERROR_INTERNAL_ERROR_ALERT)
This means Caddy is listening on 443 but does not have a valid certificate
(Let's Encrypt issuance failed or left bad state in caddy_data).
On the mail server:
cd /opt/apps/agentic_email
# 1. Confirm .env has a real contact email (use your Gmail if unsure)
grep CADDY_ACME_EMAIL .env
grep APP_PUBLIC_HOST .env
# APP_PUBLIC_HOST=gentic.email
# CADDY_ALT_HOST=email.getall.help
# CADDY_ACME_EMAIL=you@gmail.com
# 2. Check Caddy logs for ACME errors
docker compose logs caddy --tail 80
# 3. Reset Caddy cert storage and recreate
docker compose stop caddy
docker volume rm agentic_email_caddy_data 2>/dev/null || docker volume rm email_caddy_data
docker compose up -d --build caddy
docker compose logs -f caddy
Wait 30–60 seconds, then test:
curl -fsSI https://gentic.email/
Requirements for Let's Encrypt:
gentic.emailandemail.getall.helpA records → this server's public IP- Firewall allows inbound TCP 80 and 443 from
0.0.0.0/0 CADDY_ACME_EMAILset in.env
If logs show rate limits, wait an hour or use a staging CA temporarily.
API reference
GET /health— service checks (also proxied at/healthon the web host)POST /api/v1/dns-setup/preview— DNS generator (HTTP Basic Auth)