Files
mta-sts/README.md

9.9 KiB

MTA-STS Policy Server

A lightweight nginx-based server that hosts MTA-STS (Mail Transfer Agent Strict Transport Security) policies for multiple domains, ensuring secure email delivery through enforced TLS encryption.

What is MTA-STS?

MTA-STS is a security standard (RFC 8461) that helps protect email in transit by:

  • Enforcing TLS encryption for email delivery between mail servers
  • Preventing downgrade attacks that could force unencrypted connections
  • Validating certificates to ensure emails are delivered to legitimate servers
  • Providing a policy that sending servers can cache and enforce

Purpose

This repository serves MTA-STS policies for all domains under the mifi-holdings organization. Each domain has a subdomain mta-sts.<domain> (e.g., mta-sts.mifi.holdings) that serves:

  1. Policy file: /.well-known/mta-sts.txt - defines the MTA-STS policy
  2. Landing page: / - provides information about the service

Domains Covered

This server currently provides MTA-STS protection for:

  • mifi.holdings
  • mifi.com.br
  • mifi.dev
  • mifi.ventures
  • mifi.vix.br
  • mifi.me
  • blackice.vix.br
  • fitz.guru
  • umlautpress.com
  • camilla-rena.com
  • officelift.net
  • mylocalpro.biz
  • mylocalpro.online
  • happybeardedcarpenter.com
  • thenewenglandpalletguy.com
  • dining-it.com

Architecture

Components

┌─────────────────────────────────────────┐
│           Docker Container              │
│  ┌───────────────────────────────────┐  │
│  │         nginx:alpine              │  │
│  │                                   │  │
│  │  /etc/nginx/conf.d/default.conf  │  │
│  │  /usr/share/nginx/html/          │  │
│  │    ├── index.html                │  │
│  │    └── .well-known/              │  │
│  │          └── mta-sts.txt         │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
              │
              ▼
      ┌─────────────┐
      │   Traefik   │  (Reverse Proxy)
      └─────────────┘
              │
              ▼
    Multiple mta-sts.* domains

How It Works

  1. Build: Docker image is built with nginx, custom configuration, and static HTML content baked in
  2. Registry: Image is pushed to git.mifi.dev/mifi-holdings/mta-sts
  3. Deploy: Portainer pulls the latest image and restarts the container
  4. Routing: Traefik routes all mta-sts.* domains to the container
  5. Serving: nginx serves the policy file and landing page

Project Structure

.
├── .devcontainer/
│   └── devcontainer.json       # Dev Container config (pnpm + Docker)
├── .dockerignore               # Docker build context exclusions
├── .gitignore                  # Git exclusions
├── Dockerfile                  # Docker image definition
├── docker-compose.yml         # Portainer stack configuration
├── package.json               # Build and push scripts (pnpm)
├── nginx/
│   └── default.conf           # nginx server configuration
├── html/
│   ├── index.html             # Landing page
│   └── .well-known/
│       └── mta-sts.txt        # MTA-STS policy file
└── .woodpecker/
    ├── build.yaml             # CI: Build and push image
    └── deploy.yaml            # CI: Trigger Portainer webhook

Setup & Deployment

Prerequisites

  • Docker installed and logged into git.mifi.dev
  • pnpm (for manual builds) — see Package manager
  • Access to the Gitea package registry
  • Portainer webhook configured (for deployments)

Package Manager

This project uses pnpm as the package manager. The version is pinned in package.json via the packageManager field. If you use Corepack (bundled with Node.js 22+), run corepack enable once and the correct pnpm version will be used automatically.

Dev Container (Optional)

A Dev Container configuration is included for consistent local development. It provides:

  • Node.js 24 with pnpm 10.28
  • Docker CLI with access to the host Docker daemon (so you can run pnpm build and test images)
  • VS Code extensions: Docker, Prettier

To use: Open the repo in VS Code or Cursor, install the "Dev Containers" extension if needed, and run Dev Containers: Reopen in Container from the command palette. The first build may take a few minutes.

Local Development

  1. Clone the repository:

    git clone <repo-url>
    cd mta-sts
    
  2. Preview the site locally (optional):

    pnpm install
    pnpm dev
    

    Open http://localhost:3000 for the landing page and http://localhost:3000/.well-known/mta-sts.txt for the policy file.

  3. Build the Docker image:

    pnpm build
    
  4. Test locally (with the built image):

    docker run -p 8080:80 git.mifi.dev/mifi-holdings/mta-sts:latest
    

    Visit http://localhost:8080 to see the landing page and http://localhost:8080/.well-known/mta-sts.txt for the policy file.

Manual Deployment

  1. Build and push:

    pnpm build:push
    
  2. Trigger Portainer webhook (or let Portainer auto-pull):

    curl -X POST <PORTAINER_WEBHOOK_URL>
    

Automated CI/CD

The repository uses Woodpecker CI for automated builds and deployments:

Build Pipeline (.woodpecker/build.yaml)

Triggers on push to main branch:

  1. Build Docker image with commit SHA and latest tags
  2. Send Discord notification about build status
  3. Push to registry at git.mifi.dev/mifi-holdings/mta-sts
  4. Send Discord notification about push status

Deploy Pipeline (.woodpecker/deploy.yaml)

Depends on successful build:

  1. Trigger Portainer webhook to redeploy the stack
  2. Send Discord notification about deployment status

Updating the Policy

To modify the MTA-STS policy:

  1. Edit html/.well-known/mta-sts.txt
  2. Commit and push to main
  3. Woodpecker CI will automatically build and deploy

Important: The max_age directive in the policy determines how long mail servers will cache the policy. Plan policy changes accordingly.

Adding New Domains

To add MTA-STS support for a new domain:

  1. Add the domain's MX records to html/.well-known/mta-sts.txt
  2. Add Traefik labels in docker-compose.yml:
    - "traefik.http.routers.mta-sts-<domain-slug>.rule=Host(`mta-sts.<domain>`)"
    - "traefik.http.routers.mta-sts-<domain-slug>.entrypoints=websecure"
    - "traefik.http.routers.mta-sts-<domain-slug>.tls=true"
    - "traefik.http.routers.mta-sts-<domain-slug>.tls.certresolver=letsencrypt"
    - "traefik.http.routers.mta-sts-<domain-slug>.service=mta-sts-<domain-slug>"
    - "traefik.http.services.mta-sts-<domain-slug>.loadbalancer.server.port=80"
    
  3. Ensure DNS is configured with mta-sts.<domain> pointing to your server
  4. Add a TXT record _mta-sts.<domain> with value v=STSv1; id=<unique-id>

Package Scripts

Script Description
pnpm dev Start local dev server for html/ at http://localhost:3000
pnpm build Build Docker image locally
pnpm push Push Docker image to registry
pnpm build:push Build and push in one command

Technology Stack

  • nginx:alpine - Lightweight web server (base image)
  • Docker - Containerization
  • Traefik - Reverse proxy and SSL termination
  • Portainer - Container orchestration
  • Woodpecker CI - Automated build and deployment
  • Gitea - Package registry

Health Checks

The container includes health checks that:

  • Run every 30 seconds
  • Check if nginx is responding on port 80
  • Allow 10 seconds for startup
  • Retry 3 times before marking as unhealthy

Security Considerations

  • All content is served over HTTPS (enforced by Traefik)
  • MTA-STS policy enforces TLS for mail delivery
  • No sensitive data is stored in the container
  • nginx runs as non-root user (alpine default)
  • Container filesystem is read-only safe (all content is static)

Troubleshooting

pnpm install shows DEP0169 / url.parse() deprecation warning

On Node.js 24+, you may see:

DeprecationWarning: `url.parse()` behavior is not standardized...

This comes from pnpm (and its dependencies), not from this repo. The project has no runtime dependencies, so the warning is harmless and can be ignored. To hide it for a single run:

NODE_OPTIONS='--no-deprecation' pnpm install

To suppress it for your shell session: export NODE_OPTIONS='--no-deprecation'. See pnpm#9492 for upstream progress.

Container won't start

  • Check Portainer logs for the mta-sts container
  • Verify the image was pushed successfully to the registry
  • Ensure Traefik network exists: docker network ls | grep traefik

Policy not being respected

  • Verify DNS TXT record _mta-sts.<domain> exists
  • Check that the policy file is accessible at https://mta-sts.<domain>/.well-known/mta-sts.txt
  • Ensure the certificate is valid (no HTTPS errors)
  • Wait for cache expiry if you recently changed the policy

Build fails in Woodpecker

  • Check Docker daemon is accessible
  • Verify registry credentials are configured in Woodpecker secrets
  • Review build logs for specific error messages

License

ISC

Contributing

  1. Create a feature branch
  2. Make your changes
  3. Test locally with pnpm build
  4. Submit a pull request to main

Changes merged to main will automatically deploy via Woodpecker CI.