Skip to content

Getting Started

Prerequisites

  • Python 3.12+
  • PostgreSQL 16+ (or MariaDB 10.6+)
  • uv (recommended) or pip

Installation

From PyPI

pip install fastsmtp        # the server
pip install fastsmtp-cli    # the remote CLI, `fsmtp`; independent of the server

The CLI is a separate distribution that talks to a server over HTTP, so it installs on a workstation without any of the server's dependencies.

musl-based distributions

google-re2 publishes manylinux wheels but no musllinux wheels. On Alpine and other musl systems pip has to build it from source, which needs a compiler and the RE2 library. Use the container image there unless you have a reason not to.

The migrations ship inside the package, so fastsmtp db upgrade head works from a pip install exactly as it does from a checkout. Nothing applies them for you: run it before starting a newer version, or the server refuses to start against a database behind it.

export FASTSMTP_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/fastsmtp"
export FASTSMTP_ROOT_API_KEY="your-secure-root-key"

fastsmtp db upgrade head
fastsmtp serve

From source

# Clone the repository
git clone https://github.com/jsenecal/fastsmtp.git
cd fastsmtp

# Install dependencies
uv sync

# Set up environment
export FASTSMTP_DATABASE_URL="postgresql+asyncpg://user:pass@localhost/fastsmtp"
export FASTSMTP_ROOT_API_KEY="your-secure-root-key"

# Run database migrations (needs only FASTSMTP_DATABASE_URL)
uv run fastsmtp db upgrade head

# Start the server (needs FASTSMTP_ROOT_API_KEY as well)
uv run fastsmtp serve

Docker

# Pull the image
docker pull ghcr.io/jsenecal/fastsmtp:latest

# Run with required environment variables
docker run -d \
  -p 8000:8000 -p 2525:2525 -p 4650:4650 \
  -e FASTSMTP_DATABASE_URL="postgresql+asyncpg://user:pass@host/fastsmtp" \
  -e FASTSMTP_ROOT_API_KEY="your-secure-key" \
  ghcr.io/jsenecal/fastsmtp:latest

Docker Compose

Migrations are never applied for you, so the stack runs them in a one-shot migrate service that the server and worker wait on. It needs only the database URL.

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: fastsmtp
      POSTGRES_USER: fastsmtp
      POSTGRES_PASSWORD: fastsmtp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U fastsmtp"]
      interval: 5s
      timeout: 5s
      retries: 5

  migrate:
    image: ghcr.io/jsenecal/fastsmtp:latest
    command: ["fastsmtp", "db", "upgrade", "head"]
    restart: "no"
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
    depends_on:
      postgres:
        condition: service_healthy

  fastsmtp:
    image: ghcr.io/jsenecal/fastsmtp:latest
    ports:
      - "8000:8000"   # API
      - "2525:2525"   # SMTP
      - "4650:4650"   # SMTP TLS
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
      FASTSMTP_ROOT_API_KEY: ${FASTSMTP_ROOT_API_KEY:?Required}
      FASTSMTP_API_HOST: 0.0.0.0
      FASTSMTP_SMTP_HOST: 0.0.0.0
    depends_on:
      migrate:
        condition: service_completed_successfully

  worker:
    image: ghcr.io/jsenecal/fastsmtp:latest
    command: ["fastsmtp", "serve", "--worker-only"]
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
      FASTSMTP_ROOT_API_KEY: ${FASTSMTP_ROOT_API_KEY:?Required}
    depends_on:
      migrate:
        condition: service_completed_successfully
    deploy:
      replicas: 2

volumes:
  postgres_data:

Re-run docker compose up after pulling a newer image and migrate applies whatever is pending before the server comes back; on an up-to-date database it is a no-op.

Docker Compose with S3 (MinIO)

For attachment storage with MinIO:

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: fastsmtp
      POSTGRES_USER: fastsmtp
      POSTGRES_PASSWORD: fastsmtp
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U fastsmtp"]
      interval: 5s
      timeout: 5s
      retries: 5

  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    volumes:
      - minio_data:/data
    healthcheck:
      test: ["CMD", "mc", "ready", "local"]
      interval: 5s
      timeout: 5s
      retries: 5

  createbucket:
    image: minio/mc:latest
    depends_on:
      minio:
        condition: service_healthy
    entrypoint: >
      /bin/sh -c "
      mc alias set myminio http://minio:9000 minioadmin minioadmin;
      mc mb --ignore-existing myminio/attachments;
      exit 0;
      "

  migrate:
    image: ghcr.io/jsenecal/fastsmtp:latest
    command: ["fastsmtp", "db", "upgrade", "head"]
    restart: "no"
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
    depends_on:
      postgres:
        condition: service_healthy

  fastsmtp:
    image: ghcr.io/jsenecal/fastsmtp:latest
    ports:
      - "8000:8000"
      - "2525:2525"
      - "4650:4650"
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
      FASTSMTP_ROOT_API_KEY: ${FASTSMTP_ROOT_API_KEY:?Required}
      FASTSMTP_API_HOST: 0.0.0.0
      FASTSMTP_SMTP_HOST: 0.0.0.0
      FASTSMTP_ATTACHMENT_STORAGE: s3
      FASTSMTP_S3_ENDPOINT_URL: http://minio:9000
      FASTSMTP_S3_BUCKET: attachments
      FASTSMTP_S3_ACCESS_KEY: minioadmin
      FASTSMTP_S3_SECRET_KEY: minioadmin
      FASTSMTP_S3_PRESIGNED_URLS: "true"
    depends_on:
      migrate:
        condition: service_completed_successfully
      createbucket:
        condition: service_completed_successfully

  worker:
    image: ghcr.io/jsenecal/fastsmtp:latest
    command: ["fastsmtp", "serve", "--worker-only"]
    environment:
      FASTSMTP_DATABASE_URL: postgresql+asyncpg://fastsmtp:fastsmtp@postgres/fastsmtp
      FASTSMTP_ROOT_API_KEY: ${FASTSMTP_ROOT_API_KEY:?Required}
      FASTSMTP_ATTACHMENT_STORAGE: s3
      FASTSMTP_S3_ENDPOINT_URL: http://minio:9000
      FASTSMTP_S3_BUCKET: attachments
      FASTSMTP_S3_ACCESS_KEY: minioadmin
      FASTSMTP_S3_SECRET_KEY: minioadmin
    depends_on:
      migrate:
        condition: service_completed_successfully
    deploy:
      replicas: 2

volumes:
  postgres_data:
  minio_data: