245 lines
7.1 KiB
Markdown
245 lines
7.1 KiB
Markdown
# 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)
|