agentic.email/DEPLOYMENT.md
2026-10-04 14:40:06 +05:30

7.1 KiB
Raw Blame History

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 main website at gentic.email can stay on another host (e.g. GCP). Only the email stack lives on the mail VPS:

Hostname Points to Purpose
gentic.email GCP / main site IP Marketing / main app (unchanged)
email.gentic.email Mail VPS public IP Agmail web UI (Caddy → frontend)
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──► email.gentic.email (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://email.gentic.email 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://email.gentic.email"]

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=email.gentic.email
CADDY_ACME_EMAIL=admin@gentic.email

# Postfix hostname (inbound + outbound)
MAIL_HOSTNAME=mail.gentic.email
MAIL_DOMAIN=gentic.email
INBOUND_DOMAINS=gentic.email
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://email.gentic.email/smtp-credentials:

Inbound

Type Host Value Notes
A @ main site IP Keep — marketing site
A email <VPS_IP> 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:

  1. APP_PUBLIC_HOST=email.gentic.email resolves to this server's public IP
  2. Ports 80 and 443 are open
  3. CADDY_ACME_EMAIL is set in .env

Config: deploy/caddy/Caddyfile → reverse-proxies to frontend:80.

Smoke test

# HTTPS UI
curl -fsSI https://email.gentic.email/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=email.gentic.email
# 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://email.gentic.email/

Requirements for Let's Encrypt:

  • email.gentic.email A record → this server's public IP
  • Firewall allows inbound TCP 80 and 443 from 0.0.0.0/0
  • CADDY_ACME_EMAIL set 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 /health on the web host)
  • POST /api/v1/dns-setup/preview — DNS generator (HTTP Basic Auth)