Office 365 email forwarding manager with multi-tenant OAuth support, round-robin and time-based routing
  • JavaScript 57.8%
  • Python 37.9%
  • Shell 2.2%
  • Dockerfile 1.6%
  • HTML 0.3%
  • Other 0.2%
Find a file
crashmedia e6f294c65d
All checks were successful
Build and Push Docker Image / Build and Push (push) Successful in 2m13s
refactor: rewrite README for monolithic SQLite design
2026-05-27 17:45:08 +00:00
.forgejo/workflows ci: adopt proven opencode-docker build pipeline 2026-05-20 22:37:46 +00:00
actions ci: adopt proven opencode-docker build pipeline 2026-05-20 22:37:46 +00:00
backend Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
frontend Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
.dockerignore Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
.env.example refactor: simplify .env.example for monolithic SQLite design 2026-05-27 17:44:53 +00:00
.gitignore Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
docker-compose.yml refactor: convert to monolithic single-container design with embedded SQLite 2026-05-27 17:44:50 +00:00
Dockerfile fix: remove npm ci --omit=optional flag 2026-05-20 22:42:11 +00:00
generate-certs.sh Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
nginx.conf Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00
README.md refactor: rewrite README for monolithic SQLite design 2026-05-27 17:45:08 +00:00
start.sh Initial commit: Forwarding Manager v1.0.0 2026-05-20 04:43:20 +00:00

Forwarding Manager

Office 365 email forwarding manager with multi-tenant OAuth support, round-robin and time-based routing. Built with a Python FastAPI backend and a React frontend, fully containerized with Docker.

Table of Contents


Prerequisites


Quick Start

# 1. Clone the repository
git clone https://git.crashmedia.ca/crashmedia/forwarding-manager.git
cd forwarding-manager

# 2. Create the environment configuration
cp .env.example .env

3. Generate the required secrets and edit .env:

python3 -c "import secrets; print('SECRET_KEY=' + secrets.token_hex(32))"
python3 -c "import secrets; print('ENCRYPTION_KEY=' + secrets.token_hex(32))"
# Generate a strong DEFAULT_PASSWORD (min 12 chars, uppercase + lowercase + digit)

Edit .env with the output of the commands above. At minimum you must set:

Variable Example
SECRET_KEY SECRET_KEY=abc123... (64 hex chars)
ENCRYPTION_KEY ENCRYPTION_KEY=def456... (64 hex chars, different from SECRET_KEY)
DEFAULT_PASSWORD DEFAULT_PASSWORD=MySecurePass123

4. Start the application:

docker compose up -d

5. Access the application:

Open http://localhost:8080 in your browser.


Architecture

The deployment is a single monolithic container with an embedded SQLite database — no external services required.

┌──────────────────────────────────────────────┐
│              forwarding-manager              │
│  ┌──────────┐    ┌────────────────────────┐  │
│  │  React   │    │   FastAPI (uvicorn)    │  │
│  │  Frontend│    │   Port 8000            │  │
│  └──────────┘    └───────────┬────────────┘  │
│                              │                │
│                    ┌─────────▼──────────┐     │
│                    │   SQLite Database  │     │
│                    │  /data/forwarding  │     │
│                    │  _manager.db       │     │
│                    └────────────────────┘     │
└──────────────────────────────────────────────┘
        │
        ▼
  Volume: forwarding-data (/data)

Preconfigured for zero external dependencies — SQLite is built in, data persists in a Docker volume, and everything works out of the box with just docker compose up -d.

PostgreSQL is still supported — If you need PostgreSQL (e.g., for high-availability or multi-replica deployments), set DATABASE_URL in your .env file. See Optional Variables below.


Environment Variables

Required

Variable Description Generation Command
SECRET_KEY Session signing key — used by the web framework to sign cookies and session data. Must be kept secret. python3 -c "import secrets; print(secrets.token_hex(32))"
ENCRYPTION_KEY Data encryption key — used to encrypt sensitive data at rest. Must be different from SECRET_KEY. python3 -c "import secrets; print(secrets.token_hex(32))"
DEFAULT_PASSWORD Initial admin password set on first launch. Must be at least 12 characters and include uppercase, lowercase, and a digit. N/A (choose a strong password)

Security: Both SECRET_KEY and ENCRYPTION_KEY should be unique per deployment. Never use the same value or commit them to version control. Rotate them if compromised.

Optional

Variable Default Description
DEFAULT_USERNAME admin Initial administrator username created on first launch.
DATABASE_URL sqlite+aiosqlite:////data/forwarding_manager.db Full database connection URL. Defaults to embedded SQLite stored on the forwarding-data volume. Set to postgresql+asyncpg://user:pass@host:5432/dbname to use an external PostgreSQL database.
HOST 0.0.0.0 Uvicorn bind address.
PORT 8000 Uvicorn listen port.
LOG_LEVEL info Logging verbosity (debug, info, warning, error, critical).
ACCESS_TOKEN_EXPIRE_MINUTES 480 JWT access token lifetime in minutes (8 hours by default).
CORS_ORIGINS http://localhost:8080 Comma-separated list of allowed CORS origins. Include the scheme and port (e.g., http://localhost:8080,https://fwd.example.com).

Volumes

Volume Mount Point Description
forwarding-data /data Application persistent data. Stores the SQLite database file and any other runtime data.

Volumes are managed by Docker and persist until explicitly removed:

# Remove all data (irreversible!)
docker compose down -v

Ports

Container Container Port Host Port (default) Protocol Service
forwarding-manager 8000 8080 TCP FastAPI / uvicorn HTTP application

To change the host port, edit the ports section in docker-compose.yml:

ports:
  - "9090:8000"   # host:9090 → container:8000

Dockerfile Overview

The Dockerfile uses a multi-stage build to produce a small, secure production image.

Stage 1: Builder (ubuntu:22.04)

  1. System dependencies — Installs Python 3.11 (from deadsnakes PPA), Node.js 20.x, SQLite 3, and build tools.
  2. Python environment — Creates a virtual environment and installs all backend Python dependencies from backend/requirements.txt.
  3. Frontend build — Copies the React source, runs npm ci for deterministic installs, then npm run build to produce optimized static assets. node_modules and npm cache are cleaned to minimize size.
  4. Application code — Copies the backend source code and the start.sh entrypoint script.

Stage 2: Runtime Configuration

  • Sets EXPOSE 8000
  • Configures PATH to use the virtual environment Python
  • Creates a non-root user appuser for security
  • Sets the working directory to /app
  • Defines the entrypoint as /app/start.sh

Build Command

docker build -t forwarding-manager .

The build context is pruned by .dockerignore, which excludes Git metadata, Python virtual environments, node_modules, IDE files, and Docker-related files.

Entrypoint (start.sh)

The entrypoint script performs the following at container startup:

  1. Validates required variablesSECRET_KEY, ENCRYPTION_KEY, DEFAULT_PASSWORD must be set.
  2. Defaults DATABASE_URL — Falls back to SQLite at /data/forwarding_manager.db if not provided.
  3. Ensures data directory — Creates /data if missing and verifies write permissions.
  4. Waits for PostgreSQL (optional) — If DATABASE_URL contains postgresql://, polls the database host:port for up to 60 seconds. Skipped automatically when using SQLite.
  5. Starts uvicorn — Launches the FastAPI application using --factory app.main:create_app, enabling --proxy-headers for correct operation behind a reverse proxy.

Development vs Production

Development

  • Use docker compose up -d — the application will be available on http://localhost:8080.
  • The SQLite database is persisted in the forwarding-data volume.
  • For rapid frontend iteration, run the React dev server outside Docker:
    cd frontend && npm ci && npm run dev
    
    and point it at the backend at http://localhost:8080.

Production

  • Set strong secrets: Generate unique SECRET_KEY and ENCRYPTION_KEY per deployment.
  • SQLite is preconfigured — Suitable for single-server deployments. Data persists in the forwarding-data volume.
  • Use PostgreSQL (optional): For high-availability or multi-replica deployments, set DATABASE_URL explicitly.
  • Use a reverse proxy: Deploy nginx (or another reverse proxy) in front for SSL termination, rate limiting, and security headers. An example nginx.conf is included in the repository.
  • Set CORS_ORIGINS: Restrict to your actual domain(s) to prevent unauthorized cross-origin access.
  • Adjust ACCESS_TOKEN_EXPIRE_MINUTES: Lower values (e.g., 60) improve security; higher values reduce re-authentication frequency.
  • Database backups: Ensure the forwarding-data volume is backed up regularly.
  • Resource limits: Consider adding resource constraints to docker-compose.yml:
    services:
      forwarding-manager:
        deploy:
          resources:
            limits:
              memory: 512M
    

Database Options

Scenario DATABASE_URL Value Notes
SQLite (default) (not set) Embedded database, no external dependencies
PostgreSQL (external) postgresql+asyncpg://user:pass@host:5432/dbname For high-availability or multi-replica setups

Files

File Purpose
docker-compose.yml Service definition for the monolithic application container
Dockerfile Multi-stage build for the application image
start.sh Container entrypoint — validation, database setup, uvicorn launch
nginx.conf Nginx reverse proxy configuration (optional, for production use)
generate-certs.sh Self-signed TLS certificate generator (development)
.env.example Template for required and optional environment variables
.dockerignore Excludes unnecessary files from the Docker build context