Skip to content

Configuration

This page walks you through the environment variables in your .env file, grouped by what they control. You do not need to set every single one right now -- scripts/generate-secrets.sh plus a handful of edits is enough for development -- but you should understand what is available.

How configuration works

Mailyte reads all its settings from environment variables. These are defined in the .env file at the root of the project, and Docker Compose passes them into each container.

Text Only
┌──────────┐     ┌──────────────────┐     ┌────────────┐
│  .env    │────>│  docker compose  │────>│ containers │
└──────────┘     └──────────────────┘     └────────────┘

After changing a variable, recreate the affected service (or the whole stack):

Bash
# Recreate whatever changed
docker compose up -d

# Recreate just the API
docker compose up -d api

restart is not enough

docker compose restart restarts the existing container without re-reading .env. To pick up an environment change you must docker compose up -d, which recreates the container with the new values. No image rebuild is needed either way.

Compose only injects variables it is told about

A variable set in .env reaches a container only if that service's environment: block in docker-compose.yml names it. Setting a new variable in .env alone does nothing for a service that does not pass it through.


Project name

Variable Description Default
COMPOSE_PROJECT_NAME Pins the Compose project name so every invocation -- start.sh, deployment/deploy.sh, plain docker compose -- resolves to the same project and the same named volumes. Without it, Compose derives the project from the current directory name, which orphans volumes on hosts that deploy into timestamped release directories. mailyte-prod

Database

These variables tell every service how to connect to MySQL.

Variable Description Default
DB_HOST Hostname of the MySQL server. Inside Docker Compose this is the service name mysql. mysql
DB_PORT MySQL port. 3306
DB_NAME Database name. mailserver
DB_USER MySQL user the application connects as. mailuser
DB_PASSWORD Password for the application user. Required -- the stack refuses to start with a weak or missing value. (none)
DB_ROOT_PASSWORD MySQL root password. Mirrored into secrets/db_root_password by scripts/generate-secrets.sh; MySQL itself boots from that file, not the env var. (none)

Set real passwords -- the stack enforces it

The secrets-check container aborts startup if DB_PASSWORD (or any of the other required secrets) is missing, under 16 characters, or a known-weak placeholder. The rag, oauth, url_protection, and jmap containers additionally refuse to start on their own if their secret is missing -- they no longer fall back to built-in defaults, so a container run outside the compose stack fails closed too. Run ./scripts/generate-secrets.sh instead of inventing values by hand. Changing DB_PASSWORD after the database volume exists also means updating the user inside MySQL (or recreating the volume).

Example:

Text Only
DB_HOST=mysql
DB_NAME=mailserver
DB_USER=mailuser
DB_PASSWORD=Kj8#mPq!2xVnL9aQ

Mail server identity

These variables control Postfix, Dovecot, and the DNS records the API generates for customers.

Variable Description Default
HOSTNAME The fully-qualified name of the mail server itself, e.g. mail.example.com. Appears in SMTP banners, HELO/EHLO, and generated DNS records. mail.example.com
DOMAIN The server's base domain, e.g. example.com. Also the default domain for admin subdomains (api., grafana., ...) in production. example.com
ADMIN_EMAIL Operator contact address; also the default sender for alert and report mail. (unset)
MAIL_HOSTNAME The customer-facing mail hostname handed to email clients and used as the MX in generated records -- distinct from HOSTNAME when the server's PTR identity differs from the brand name. (unset, falls back to HOSTNAME)
MAIL_SPF_HOST The host customers include: in their SPF records. Kept separate from MAIL_HOSTNAME on purpose, so renaming the mail host does not silently repoint every customer's SPF. (unset)
POSTFIX_MESSAGE_SIZE_LIMIT Maximum size of a single message in bytes. 52428800 is 50 MB. 52428800

HOSTNAME vs DOMAIN vs MAIL_HOSTNAME

DOMAIN is the part after the @. HOSTNAME is this server's own identity (matches its PTR record). MAIL_HOSTNAME is what customers type into their mail clients. On a single-domain setup, MAIL_HOSTNAME can stay unset.


Security secrets

Eight secrets are required before the stack will boot at all (see Installation). The ones you will interact with most:

Variable Description
WEBHOOK_SECRET Signs outgoing webhook payloads (HMAC). Consumers verify the signature.
ADMIN_TOKEN_SECRET Admin token secret for platform-level API access. The jmap container requires it at startup (wired through compose since 2026-08-30 -- before that it silently ran on a built-in default).
ADMIN_PASSWORD Legacy alias -- keep in sync with ADMIN_TOKEN_SECRET.
OAUTH_TOKEN_SECRET Signing secret for the OAuth 2.0 / XOAUTH2 service.
URL_HMAC_SECRET Signs Safe-Links URLs in the url_protection service.
GRAFANA_ADMIN_PASSWORD Grafana admin login (user: GRAFANA_ADMIN_USER, default admin).
DOVEADM_API_KEY Optional but recommended: authenticates the internal doveadm HTTP listener so revoking an SMTP credential flushes Dovecot's auth cache immediately instead of after up to an hour. Generate with openssl rand -hex 24.

Generate a random secret when you need one by hand:

Bash
openssl rand -base64 32

TLS certificates

In development, start.sh generates a self-signed certificate into storage/ssl_certs/server.crt and storage/ssl_private/server.key on first run. Postfix and Dovecot mount those directories.

In production, the cert_manager service obtains and renews Let's Encrypt certificates automatically (webroot HTTP-01 via Traefik) and reloads Postfix/Dovecot when they change. It is driven by:

Variable Description Default
ACME_EMAIL Email for Let's Encrypt expiry notices. admin@example.com
ACME_STAGING Use the Let's Encrypt staging CA. true in the base file; docker-compose.prod.yml forces false. true
CERT_SERVER_IPS This server's public IP(s), so cert_manager can skip SAN candidates whose DNS points elsewhere. Must be set explicitly in production. (unset)
CERT_RENEWAL_DAYS Renew when fewer than this many days remain. 30

There is no host-side certbot to configure -- do not set up your own certbot cron on the host, it will collide with cert_manager's locks.


Redis

Redis provides caching, rate limiting, queues, and the Rspamd backend.

Variable Description Default
REDIS_HOST Hostname of the Redis server -- the service name inside Compose. redis
REDIS_PORT Port Redis listens on. 6379
REDIS_PASSWORD Optional. When set, the Redis container starts with --requirepass. (empty)

Unless you are running Redis externally (see docker-compose.cloud.yml), you probably never need to change these.


Feature flags

Variable Description Default
TRACKING_ENABLED Open and click tracking for outgoing mail. The tracking injector rewrites links and adds a pixel on the submission path. true
ARCHIVE_OUTBOUND_ENABLED Archive every outbound message to the archiver service. true
TRANSPORT_RULES_ENABLED Enforce transport rules in Rspamd. Off by default -- read your rules before turning every draft rule into policy at once. false
AUTO_SUSPEND_ENABLED Automatic suspension of abusive SMTP API keys by the log ingestor. false
COMPOSE_PROFILES Optional webmail clients: roundcube, sogo, or both (comma-separated). The bundled webmail and console services are behind the webmail / console profiles, which start.sh and deployment/deploy.sh pass automatically. (unset)

Tracking and privacy

Email tracking inserts a tiny invisible image and rewrites links to pass through the tracking service. This is standard practice for transactional and marketing email, but be aware of privacy regulations (GDPR, CAN-SPAM) in your jurisdiction. Set TRACKING_ENABLED=false to turn it off.


All variables at a glance

For quick reference, the variables you are most likely to touch during initial setup:

Text Only
# Project
COMPOSE_PROJECT_NAME=mailyte-prod

# Database
DB_HOST=mysql
DB_NAME=mailserver
DB_USER=mailuser
DB_PASSWORD=<generated>
DB_ROOT_PASSWORD=<generated>

# Mail server identity
HOSTNAME=mail.example.com
DOMAIN=example.com
ADMIN_EMAIL=admin@example.com

# Secrets (generated by scripts/generate-secrets.sh)
WEBHOOK_SECRET=<generated>
OAUTH_TOKEN_SECRET=<generated>
URL_HMAC_SECRET=<generated>
ADMIN_PASSWORD=<generated>
ADMIN_TOKEN_SECRET=<generated>
GRAFANA_ADMIN_PASSWORD=<generated>

# TLS (production)
ACME_EMAIL=admin@example.com
ACME_STAGING=false

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# Features
TRACKING_ENABLED=true

This is not every variable

.env.example documents the exhaustive list, section by section, and the Configuration Reference covers the rest. This page focuses on the ones you need to understand during initial setup.

Next step

Your server is running and configured. Head to First Steps to create your first organization, domain, and mailbox.