# 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= 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: "" 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