Files
gh-christianlouis-inboxconv…/docs/DEPLOYMENT_GUIDE.md
T

1039 lines
29 KiB
Markdown

# Deployment Guide
This guide walks you through deploying **InboxConverge** from scratch — whether you just want a single container pulling emails, a full multi-service SaaS stack with Docker Compose, or a production-grade Kubernetes setup.
---
## Table of Contents
- [Prerequisites](#prerequisites)
- [Deployment Options at a Glance](#deployment-options-at-a-glance)
- [Option 1 — Legacy Single-Container Deployment](#option-1--legacy-single-container-deployment)
- [Option 2 — Full SaaS Stack with Docker Compose](#option-2--full-saas-stack-with-docker-compose)
- [Option 3 — Kubernetes Deployment](#option-3--kubernetes-deployment)
- [Google OAuth and Gmail API Setup](#google-oauth-and-gmail-api-setup)
- [Reverse Proxy and TLS](#reverse-proxy-and-tls)
- [Environment Variable Reference](#environment-variable-reference)
- [Upgrading](#upgrading)
- [Troubleshooting](#troubleshooting)
---
## Prerequisites
| Requirement | Minimum | Recommended |
|---|---|---|
| **Docker** | 20.10+ | Latest stable |
| **Docker Compose** | v2.0+ | Latest stable |
| **RAM** | 1 GB (legacy) / 4 GB (SaaS) | 8 GB (SaaS) |
| **Disk** | 10 GB (legacy) / 40 GB (SaaS) | 80 GB (SaaS) |
| **CPU** | 1 vCPU (legacy) / 2 vCPU (SaaS) | 4 vCPU (SaaS) |
You will also need:
- A **Gmail account** with [2-Step Verification](https://myaccount.google.com/signinoptions/two-step-verification) enabled and an [App Password](https://myaccount.google.com/apppasswords) generated (for SMTP delivery).
- Credentials for one or more **POP3 mailboxes** you want to pull email from.
- *(SaaS stack only)* A **Google Cloud project** with OAuth 2.0 credentials if you want Google sign-in or Gmail API injection (see [Google OAuth and Gmail API Setup](#google-oauth-and-gmail-api-setup)).
---
## Deployment Options at a Glance
| | Legacy | SaaS (Docker Compose) | SaaS (Kubernetes) |
|---|---|---|---|
| **Services** | 1 container | 6 containers | 6+ pods |
| **Database** | None | PostgreSQL | PostgreSQL |
| **Queue** | None | Redis + Celery | Redis + Celery |
| **Web UI** | None | Next.js frontend | Next.js frontend |
| **Multi-user** | No | Yes | Yes |
| **Best for** | Personal / single mailbox | Small teams / self-hosted | Production / scale |
---
## Option 1 — Legacy Single-Container Deployment
The legacy mode runs a single Python script (`inbox_converge.py`) that polls POP3 mailboxes and forwards email via SMTP. No database, no web UI — just a container and an `.env` file.
### 1. Create the environment file
```bash
git clone https://github.com/christianlouis/inboxconverge.git
cd inboxconverge
cp .env.example .env
```
Edit `.env` with your credentials:
```ini
# POP3 source mailbox
POP3_ACCOUNT_1_HOST=pop.example.com
POP3_ACCOUNT_1_PORT=995
POP3_ACCOUNT_1_USER=user@example.com
POP3_ACCOUNT_1_PASSWORD=your_password
POP3_ACCOUNT_1_USE_SSL=true
# Gmail SMTP destination
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASSWORD=xxxx-xxxx-xxxx-xxxx # Gmail App Password
SMTP_USE_TLS=true
GMAIL_DESTINATION=you@gmail.com
# Tuning (optional)
CHECK_INTERVAL_MINUTES=5
MAX_EMAILS_PER_RUN=50
THROTTLE_EMAILS_PER_MINUTE=10
LOG_LEVEL=INFO
```
> **Tip:** Add more accounts by duplicating the `POP3_ACCOUNT_*` block with an incremented number (`POP3_ACCOUNT_2_*`, `POP3_ACCOUNT_3_*`, etc.).
### 2. Docker Compose file
The repository ships `docker-compose.yml` for this mode. Here is the content for reference:
```yaml
version: "3.8"
services:
inbox-converge:
# Build from source
build: .
# Or use the pre-built image:
# image: ghcr.io/christianlouis/inboxconverge:latest
container_name: inboxconverge
restart: unless-stopped
env_file:
- .env
environment:
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- CHECK_INTERVAL_MINUTES=${CHECK_INTERVAL_MINUTES:-5}
- MAX_EMAILS_PER_RUN=${MAX_EMAILS_PER_RUN:-50}
- THROTTLE_EMAILS_PER_MINUTE=${THROTTLE_EMAILS_PER_MINUTE:-10}
volumes:
- ./logs:/app/logs
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
```
### 3. Start
```bash
docker compose up -d
docker compose logs -f # watch the output
```
The forwarder will check for new mail every 5 minutes (configurable) and forward messages to your Gmail inbox.
---
## Option 2 — Full SaaS Stack with Docker Compose
The SaaS stack gives you a multi-user web application with a React frontend, FastAPI backend, PostgreSQL database, Redis cache, and Celery workers for background email processing.
### 1. Generate secrets
```bash
# Generate a 64-character hex secret for JWT signing
openssl rand -hex 32
# Generate a separate key for encrypting stored credentials
openssl rand -hex 32
```
Save both values — you will need them below.
### 2. Create the backend environment file
```bash
cd inboxconverge
cp backend/.env.example backend/.env
```
Edit `backend/.env`:
```ini
# ── Database ──────────────────────────────────────────────
DATABASE_URL=postgresql+asyncpg://postgres:change-me@postgres:5432/inbox_converge
# ── Security (paste the values you generated above) ──────
SECRET_KEY=<your-64-char-hex-secret>
ENCRYPTION_KEY=<your-64-char-hex-encryption-key>
# ── Redis / Celery ───────────────────────────────────────
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/0
# ── Google OAuth (optional — see setup section below) ────
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_REDIRECT_URI=https://your-domain.com/auth/callback/google
# ── CORS (include your frontend URL) ────────────────────
CORS_ORIGINS=http://localhost:3000
# ── Admin account (created on first startup) ─────────────
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-this-to-a-strong-password
# ── Application ─────────────────────────────────────────
DEBUG=false
LOG_LEVEL=INFO
HOST=0.0.0.0
PORT=8000
```
### 3. Production Docker Compose file
Below is a production-ready `docker-compose.prod.yml`. It is based on the `docker-compose.new.yml` that ships with the repository, hardened for production use:
```yaml
version: "3.8"
services:
# ── PostgreSQL ───────────────────────────────────────────
postgres:
image: postgres:15-alpine
container_name: inboxconverge-postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: change-me # must match DATABASE_URL
POSTGRES_DB: inbox_converge
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Do NOT expose the port in production unless you need
# external access — keep it on the internal network only.
# ports:
# - "5432:5432"
# ── Redis ────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: inboxconverge-redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# ── FastAPI Backend ──────────────────────────────────────
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: inboxconverge-backend
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- ./backend/.env
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
command: >
sh -c "alembic upgrade head &&
uvicorn app.main:app --host 0.0.0.0 --port 8000"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
# ── Celery Worker ────────────────────────────────────────
celery-worker:
build:
context: ./backend
dockerfile: Dockerfile
container_name: inboxconverge-celery-worker
restart: unless-stopped
env_file:
- ./backend/.env
depends_on:
- backend
command: celery -A app.workers.celery_app worker --loglevel=info --concurrency=2
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
# ── Celery Beat (scheduler) ─────────────────────────────
celery-beat:
build:
context: ./backend
dockerfile: Dockerfile
container_name: inboxconverge-celery-beat
restart: unless-stopped
env_file:
- ./backend/.env
depends_on:
- backend
command: celery -A app.workers.celery_app beat --loglevel=info
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "5"
# ── Next.js Frontend ────────────────────────────────────
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: inboxconverge-frontend
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_API_URL=http://backend:8000
depends_on:
- backend
volumes:
postgres_data:
redis_data:
```
### 4. Build and start
```bash
# Build all images
docker compose -f docker-compose.prod.yml build
# Start in detached mode
docker compose -f docker-compose.prod.yml up -d
# Verify all services are healthy
docker compose -f docker-compose.prod.yml ps
```
### 5. Verify
```bash
# Backend health check
curl http://localhost:8000/health
# Open the frontend
open http://localhost:3000
# Watch logs
docker compose -f docker-compose.prod.yml logs -f
```
### 6. Create the first admin account
If you set `ADMIN_EMAIL` and `ADMIN_PASSWORD` in `backend/.env`, an admin account is created automatically on first startup. Otherwise you can register via the API:
```bash
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","password":"your-password","full_name":"Your Name"}'
```
---
## Option 3 — Kubernetes Deployment
Below is a set of example Kubernetes manifests to get you started. Adapt namespaces, resource limits, and Ingress rules to your cluster.
### Namespace
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: inbox-converge
```
### Secrets
Store sensitive values in a Kubernetes Secret. In production, consider using an external secret manager (e.g., HashiCorp Vault, AWS Secrets Manager, or Sealed Secrets).
```yaml
apiVersion: v1
kind: Secret
metadata:
name: inbox-converge-secrets
namespace: inbox-converge
type: Opaque
stringData:
SECRET_KEY: "<your-64-char-hex-secret>"
ENCRYPTION_KEY: "<your-64-char-hex-encryption-key>"
DATABASE_URL: "postgresql+asyncpg://postgres:change-me@postgres:5432/inbox_converge"
REDIS_URL: "redis://redis:6379/0"
CELERY_BROKER_URL: "redis://redis:6379/0"
CELERY_RESULT_BACKEND: "redis://redis:6379/0"
ADMIN_EMAIL: "admin@example.com"
ADMIN_PASSWORD: "change-this-to-a-strong-password"
POSTGRES_PASSWORD: "change-me"
# Optional
# GOOGLE_CLIENT_ID: ""
# GOOGLE_CLIENT_SECRET: ""
```
### PostgreSQL
For production, consider a managed database (RDS, Cloud SQL, etc.) or an operator such as [CloudNativePG](https://cloudnative-pg.io/). The manifest below is a simple single-instance deployment for getting started:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: inbox-converge
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_DB
value: inbox_converge
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: inbox-converge-secrets
key: POSTGRES_PASSWORD
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: pgdata
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: inbox-converge
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: inbox-converge
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 10Gi
```
### Redis
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: inbox-converge
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
ports:
- containerPort: 6379
readinessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: inbox-converge
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
```
### Backend API
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: inbox-converge
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
initContainers:
- name: run-migrations
image: ghcr.io/christianlouis/inboxconverge-backend:latest
command: ["alembic", "upgrade", "head"]
envFrom:
- secretRef:
name: inbox-converge-secrets
env:
- name: DEBUG
value: "false"
containers:
- name: backend
image: ghcr.io/christianlouis/inboxconverge-backend:latest
ports:
- containerPort: 8000
envFrom:
- secretRef:
name: inbox-converge-secrets
env:
- name: HOST
value: "0.0.0.0"
- name: PORT
value: "8000"
- name: DEBUG
value: "false"
- name: LOG_LEVEL
value: "INFO"
- name: CORS_ORIGINS
value: "https://your-domain.com"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: inbox-converge
spec:
selector:
app: backend
ports:
- port: 8000
targetPort: 8000
```
### Celery Worker
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-worker
namespace: inbox-converge
spec:
replicas: 2
selector:
matchLabels:
app: celery-worker
template:
metadata:
labels:
app: celery-worker
spec:
containers:
- name: worker
image: ghcr.io/christianlouis/inboxconverge-backend:latest
command:
- celery
- -A
- app.workers.celery_app
- worker
- --loglevel=info
- --concurrency=2
envFrom:
- secretRef:
name: inbox-converge-secrets
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
```
### Celery Beat (scheduler)
Only one replica should run Celery Beat at a time:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-beat
namespace: inbox-converge
spec:
replicas: 1 # Must be exactly 1
strategy:
type: Recreate # Avoid two schedulers running simultaneously
selector:
matchLabels:
app: celery-beat
template:
metadata:
labels:
app: celery-beat
spec:
containers:
- name: beat
image: ghcr.io/christianlouis/inboxconverge-backend:latest
command:
- celery
- -A
- app.workers.celery_app
- beat
- --loglevel=info
envFrom:
- secretRef:
name: inbox-converge-secrets
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
```
### Frontend
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: inbox-converge
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: ghcr.io/christianlouis/inboxconverge-frontend:latest
ports:
- containerPort: 3000
env:
- name: NEXT_PUBLIC_API_URL
value: "https://api.your-domain.com"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: inbox-converge
spec:
selector:
app: frontend
ports:
- port: 3000
targetPort: 3000
```
### Ingress
The Ingress below assumes you have an Ingress controller installed (e.g., [ingress-nginx](https://kubernetes.github.io/ingress-nginx/)) and [cert-manager](https://cert-manager.io/) for automatic TLS certificates:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: inbox-converge-ingress
namespace: inbox-converge
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
spec:
ingressClassName: nginx
tls:
- hosts:
- your-domain.com
- api.your-domain.com
secretName: inbox-converge-tls
rules:
- host: your-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 3000
- host: api.your-domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend
port:
number: 8000
```
### Helm chart idea
If you manage many environments (staging, production, etc.) consider wrapping the manifests above into a Helm chart:
```text
helm/inbox-converge/
├── Chart.yaml
├── values.yaml # defaults for all environments
├── values-staging.yaml
├── values-production.yaml
└── templates/
├── namespace.yaml
├── secret.yaml
├── postgres.yaml
├── redis.yaml
├── backend-deployment.yaml
├── backend-service.yaml
├── celery-worker.yaml
├── celery-beat.yaml
├── frontend-deployment.yaml
├── frontend-service.yaml
└── ingress.yaml
```
Key values to parameterize in `values.yaml`:
```yaml
replicaCount:
backend: 2
celeryWorker: 2
frontend: 2
image:
backend: ghcr.io/christianlouis/inboxconverge-backend
frontend: ghcr.io/christianlouis/inboxconverge-frontend
tag: latest
ingress:
enabled: true
host: your-domain.com
apiHost: api.your-domain.com
tls: true
clusterIssuer: letsencrypt-prod
resources:
backend:
requests: { cpu: 250m, memory: 256Mi }
limits: { cpu: "1", memory: 512Mi }
postgres:
# Set to false when using an external/managed database
enabled: true
storage: 10Gi
redis:
enabled: true
```
---
## Google OAuth and Gmail API Setup
If you want Google sign-in or direct Gmail API email injection (instead of SMTP), follow these steps:
### 1. Create a Google Cloud project
1. Go to the [Google Cloud Console](https://console.cloud.google.com/).
2. Create a new project (or select an existing one).
3. Navigate to **APIs & Services → Library**.
4. Enable the **Gmail API**.
### 2. Configure the OAuth consent screen
1. Go to **APIs & Services → OAuth consent screen**.
2. Choose **External** (or **Internal** if you have a Google Workspace org).
3. Fill in the required fields (app name, user-support email, developer contact).
4. Under **Scopes**, add:
- `openid`
- `email`
- `profile`
- `https://www.googleapis.com/auth/gmail.insert` *(for Gmail API injection)*
- `https://www.googleapis.com/auth/gmail.labels`
### 3. Create OAuth 2.0 credentials
1. Go to **APIs & Services → Credentials**.
2. Click **Create Credentials → OAuth client ID**.
3. Application type: **Web application**.
4. Add **Authorized redirect URIs**:
- Development: `http://localhost:3000/auth/callback/google`
- Production: `https://your-domain.com/auth/callback/google`
5. Copy the **Client ID** and **Client Secret**.
### 4. Configure the application
Add these to `backend/.env`:
```ini
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret
GOOGLE_REDIRECT_URI=https://your-domain.com/auth/callback/google
GMAIL_API_ENABLED=true
```
---
## Reverse Proxy and TLS
In production you should place a reverse proxy in front of the backend and frontend to handle TLS termination.
### Example: nginx
```nginx
# /etc/nginx/sites-available/inbox-converge
# Frontend
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Backend API
server {
listen 443 ssl http2;
server_name api.your-domain.com;
ssl_certificate /etc/letsencrypt/live/api.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
Generate certificates with Let's Encrypt:
```bash
sudo apt-get install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d api.your-domain.com
```
### Example: Traefik (Docker Compose add-on)
If you prefer Traefik, add it as a service in your Compose file and use labels on the `backend` and `frontend` services. Traefik handles TLS via Let's Encrypt automatically.
---
## Environment Variable Reference
### Legacy mode (`.env`)
| Variable | Required | Default | Description |
|---|---|---|---|
| `POP3_ACCOUNT_N_HOST` | Yes | — | POP3 server hostname (N = 1, 2, 3…) |
| `POP3_ACCOUNT_N_PORT` | No | `995` | POP3 server port |
| `POP3_ACCOUNT_N_USER` | Yes | — | POP3 username |
| `POP3_ACCOUNT_N_PASSWORD` | Yes | — | POP3 password |
| `POP3_ACCOUNT_N_USE_SSL` | No | `true` | Use SSL for POP3 |
| `SMTP_HOST` | Yes | — | SMTP server (e.g., `smtp.gmail.com`) |
| `SMTP_PORT` | Yes | — | SMTP port (e.g., `587`) |
| `SMTP_USER` | Yes | — | SMTP username |
| `SMTP_PASSWORD` | Yes | — | SMTP password / App Password |
| `SMTP_USE_TLS` | No | `true` | Use TLS for SMTP |
| `GMAIL_DESTINATION` | Yes | — | Destination Gmail address |
| `CHECK_INTERVAL_MINUTES` | No | `5` | Minutes between polling cycles |
| `MAX_EMAILS_PER_RUN` | No | `50` | Max emails forwarded per cycle |
| `THROTTLE_EMAILS_PER_MINUTE` | No | `10` | Rate limit |
| `LOG_LEVEL` | No | `INFO` | Logging level |
| `POSTMARK_API_TOKEN` | No | — | Postmark token for error alerts |
| `POSTMARK_FROM_EMAIL` | No | — | Sender for error alerts |
| `POSTMARK_TO_EMAIL` | No | — | Recipient for error alerts |
### SaaS mode (`backend/.env`)
| Variable | Required | Default | Description |
|---|---|---|---|
| `DATABASE_URL` | Yes | — | PostgreSQL connection string |
| `SECRET_KEY` | Yes | — | JWT signing key (≥ 32 chars) |
| `ENCRYPTION_KEY` | Yes | — | Credential encryption key (≥ 32 chars) |
| `REDIS_URL` | Yes | `redis://localhost:6379/0` | Redis connection string |
| `CELERY_BROKER_URL` | Yes | `redis://localhost:6379/0` | Celery broker URL |
| `CELERY_RESULT_BACKEND` | Yes | `redis://localhost:6379/0` | Celery result backend URL |
| `CORS_ORIGINS` | Yes | `http://localhost:3000` | Comma-separated allowed origins |
| `ADMIN_EMAIL` | No | — | Auto-created admin email |
| `ADMIN_PASSWORD` | No | — | Auto-created admin password |
| `GOOGLE_CLIENT_ID` | No | — | Google OAuth client ID |
| `GOOGLE_CLIENT_SECRET` | No | — | Google OAuth client secret |
| `GOOGLE_REDIRECT_URI` | No | `http://localhost:3000/auth/callback/google` | OAuth redirect URI |
| `GMAIL_API_ENABLED` | No | `true` | Enable Gmail API injection |
| `DEBUG` | No | `false` | Enable debug mode |
| `LOG_LEVEL` | No | `INFO` | Logging level |
| `HOST` | No | `0.0.0.0` | Bind address |
| `PORT` | No | `8000` | Bind port |
| `STRIPE_API_KEY` | No | — | Stripe API key |
| `STRIPE_WEBHOOK_SECRET` | No | — | Stripe webhook secret |
| `MAX_EMAILS_PER_RUN` | No | `50` | Max emails per account per cycle |
| `CHECK_INTERVAL_MINUTES` | No | `5` | Minutes between polling cycles |
| `THROTTLE_EMAILS_PER_MINUTE` | No | `10` | Rate limit |
---
## Upgrading
```bash
cd inboxconverge
# Pull latest code
git pull origin main
# Rebuild and restart
docker compose -f docker-compose.prod.yml build
docker compose -f docker-compose.prod.yml up -d
# The backend init container / startup command runs migrations automatically.
# To run them manually:
docker compose -f docker-compose.prod.yml exec backend alembic upgrade head
```
---
## Troubleshooting
### Container won't start
```bash
# Check logs for the failing service
docker compose -f docker-compose.prod.yml logs backend
# Common causes:
# - DATABASE_URL is wrong or PostgreSQL isn't ready yet
# - SECRET_KEY or ENCRYPTION_KEY is shorter than 32 characters
# - Port conflict on the host
```
### Frontend can't reach the backend (CORS errors)
1. Ensure `CORS_ORIGINS` in `backend/.env` includes the frontend URL exactly (protocol + host + port).
2. Verify the backend is reachable from the frontend container: `docker compose exec frontend wget -qO- http://backend:8000/health`.
### Emails aren't being forwarded
1. Check Celery worker logs: `docker compose -f docker-compose.prod.yml logs celery-worker`.
2. Verify Redis is running: `docker compose -f docker-compose.prod.yml exec redis redis-cli ping`.
3. Confirm POP3 credentials are correct by testing manually.
### Database migration errors
```bash
# Check current migration state
docker compose -f docker-compose.prod.yml exec backend alembic current
# Show migration history
docker compose -f docker-compose.prod.yml exec backend alembic history
# If stuck, you may need to stamp the current head
docker compose -f docker-compose.prod.yml exec backend alembic stamp head
```
### OAuth redirect mismatch
The `GOOGLE_REDIRECT_URI` in `backend/.env` must **exactly** match one of the authorized redirect URIs configured in the Google Cloud Console (including protocol, host, port, and path).
---
## Further Reading
- [QUICKSTART.md](QUICKSTART.md) — Get running in under 10 minutes (legacy mode)
- [DEPLOYMENT_CHECKLIST.md](DEPLOYMENT_CHECKLIST.md) — Pre-deployment and post-deployment checklists
- [ARCHITECTURE.md](ARCHITECTURE.md) — System architecture and component overview
- [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) — Upgrading from v1 (legacy) to v2 (SaaS)
- [SECURITY_SUMMARY.md](SECURITY_SUMMARY.md) — Security best practices