agentic.email/DEPLOYMENT.md
2026-10-04 15:35:17 +05:30

245 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
```text
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
```bash
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 |
```bash
curl -fsS http://127.0.0.1:8000/health
docker compose ps
docker compose logs -f caddy
```
## Environment (`.env` at repo root)
### Required
```env
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)
```env
# 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):
```bash
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`.
```bash
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:
```bash
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` and `CADDY_ALT_HOST` resolve 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
```bash
# 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
```bash
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:
```bash
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:
```bash
curl -fsSI https://gentic.email/
```
Requirements for Let's Encrypt:
- `gentic.email` and `email.getall.help` A records → 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)