Minimal Postfix-based SMTP relay with TLS/SASL and docker-friendly logging.
10K+
Minimal Postfix SMTP relay by OneSystems GmbH.
MailRelay accepts mail from trusted internal networks and forwards it to an upstream SMTP server with TLS and optional SASL. It stores no mailboxes. Delivery and identity failures can open an internal Zammad ticket instead of sending another email.
| Vendor | OneSystems GmbH |
| Support | Submit a ticket |
| Image | onesystems/mailrelay on Docker Hub |
| Source | git.onesystems.ch/docker/mailrelay |
docker pull onesystems/mailrelay:1.2.0
| Registry | Tags |
|---|---|
Docker Hub onesystems/mailrelay | <version>, <major.minor>, latest |
git.onesystems.ch/docker/mailrelay | <version>, <major.minor> |
Example: 1.2.0, 1.2, and on Docker Hub also latest.
The image includes Postfix, swaks, and a startup script that renders main.cf from the environment. Logs go to stdout (docker logs).
Expose port 25 only on a trusted network. Never publish it to the internet.
services:
mailrelay:
image: onesystems/mailrelay:1.2.0
restart: unless-stopped
ports:
- "25:25"
environment:
HOSTNAME_FQDN: mailrelay.example.local
ORIGIN_DOMAIN: example.com
DEFAULT_FROM: [email protected]
RELAY_HOST: smtp.example.com
RELAY_PORT: "587"
RELAY_TLS: required
MYNETWORKS: 127.0.0.0/8,10.0.0.0/8,192.168.0.0/16
RELAY_SASL_USER: [email protected]
RELAY_SASL_PASS_FILE: /run/secrets/relay_sasl_pass
secrets:
- relay_sasl_pass
volumes:
- postfix_spool:/var/spool/postfix
- postfix_state:/var/lib/postfix
- /etc/ssl/certs:/etc/ssl/certs:ro
secrets:
relay_sasl_pass:
file: ./secrets/relay_sasl_pass.txt
volumes:
postfix_spool:
postfix_state:
docker compose up -d
docker logs -f mailrelay
relay_sasl_pass.txt contains only the password, no username. A trailing newline is stripped.
MYNETWORKS and rejects everything else (permit_mynetworks, reject_unauth_destination).required, opportunistic, or none). TLS 1.0 and 1.1 are disabled.root, daemon, postmaster, and addresses at HOSTNAME_FQDN) to DEFAULT_FROM, or to RELAY_SASL_USER when DEFAULT_FROM is empty.SASL is required on relay ports 587 and 465. On other ports the relay starts without authentication when both user and password are empty. If one of them is set, the other is required too.
Many upstreams reject a message when the envelope sender does not match the authenticated account (553 5.7.1 Sender address rejected: not owned by user).
With FORCE_DEFAULT_FROM=1 (the default), only local and infrastructure senders are rewritten. Other addresses pass through unchanged.
Set ALERT_ENABLED=1. Optional Zammad delivery uses ALERT_ZAMMAD_ENABLED=1 plus ZAMMAD_BASE_URL and ZAMMAD_TOKEN or ZAMMAD_TOKEN_FILE.
| Match | Meaning |
|---|---|
invalid_user | Upstream mailbox or identity does not exist |
sendas_denied | Sender is not allowed to send as this address |
postfix_bounced | Delivery failed permanently (status=bounced) |
postfix_deferred | Delivery failed temporarily (status=deferred) |
The watcher reads the log, keeps queue id, sender, recipient, and the Postfix reason, then creates one internal ticket. Further matches inside ALERT_THROTTLE_SECONDS (default 300) are dropped. Without Zammad, the same text is written to the log.
swaks is in the image.
docker exec -it mailrelay swaks \
--to [email protected] \
--from [email protected] \
--server 127.0.0.1 \
--port 25 \
--header "Subject: Relay test" \
--data "Hello from mailrelay"
docker exec -i mailrelay sendmail [email protected] <<EOF
From: [email protected]
To: [email protected]
Subject: Relay test
Hello from mailrelay
EOF
Do not use sendmail -v unless a delivery status notification should come back to you.
Postfix writes /var/log/postfix.log and the entrypoint streams it to stdout.
docker logs -f mailrelay
Startup lines look like [2026-10-13 09:58:00] [entrypoint] …. On the first start Postfix copies CA certificates into its chroot (updating /etc/ssl/certs/…). Later starts only refresh files that changed, usually resolv.conf and hosts.
LOG_STATUS_ONLY=1 limits stdout to status, alert, warning, and error lines. LOG_TEST_ON_START=1 runs postfix check before the daemon starts.
Secrets can be passed as NAME or NAME_FILE. Setting both is an error. NAME_FILE is the Docker secret path; a trailing newline is removed.
| Variable | Description | Default |
|---|---|---|
HOSTNAME_FQDN | Postfix myhostname | mailrelay.local |
ORIGIN_DOMAIN | Postfix myorigin | onesystems.ch |
RELAY_HOST | Upstream SMTP host | — (required) |
RELAY_PORT | Upstream port | 587 |
RELAY_TLS | required, opportunistic, or none | required |
TLS_CA_DIR | CA directory for the upstream connection | /etc/ssl/certs |
MYNETWORKS | Comma-separated client networks that may relay | RFC1918 + loopback |
MESSAGE_SIZE_LIMIT | Maximum message size in bytes | 30720000 |
STRICT_RELAY | Exit when RELAY_HOST is empty | 1 |
| Variable | Description | Default |
|---|---|---|
RELAY_SASL_USER | Upstream username | empty |
RELAY_SASL_PASS | Upstream password | empty |
RELAY_SASL_PASS_FILE | File with the password only | empty |
DEFAULT_FROM | Rewrite target for local senders | RELAY_SASL_USER |
FORCE_DEFAULT_FROM | 1 rewrites local / infrastructure senders | 1 |
| Variable | Description | Default |
|---|---|---|
DEBUG | 1 raises smtp_tls_loglevel to 2 | 0 |
DEBUG_PEER_LIST | Postfix debug_peer_list | empty |
LOG_FILE | Postfix log path inside the container | /var/log/postfix.log |
TRUNCATE_LOG_ON_START | Truncate the log file on start | 1 |
LOG_TEST_ON_START | Run postfix check before start | 0 |
LOG_STATUS_ONLY | Stream only status and error lines to stdout | 0 |
| Variable | Description | Default |
|---|---|---|
ALERT_ENABLED | 1 watches the log for failures | 0 |
ALERT_MATCH | Comma-separated matchers | invalid_user,sendas_denied,postfix_bounced,postfix_deferred |
ALERT_THROTTLE_SECONDS | Minimum seconds between alerts | 300 |
ALERT_TAIL_LINES | Log lines included as context | 120 |
ALERT_DEBUG | 1 logs the matched line | 0 |
ALERT_ZAMMAD_ENABLED | 1 creates a ticket instead of a log-only alert | 0 |
ZAMMAD_BASE_URL | Zammad base URL | empty |
ZAMMAD_TOKEN / ZAMMAD_TOKEN_FILE | API token | empty |
ZAMMAD_GROUP | Ticket group name or id | Support |
ZAMMAD_STATE | Ticket state | new |
ZAMMAD_PRIORITY_ID | Priority id (1 low, 2 normal, 3 high) | 2 |
ZAMMAD_CUSTOMER_EMAIL | Ticket customer | mailrelay@ORIGIN_DOMAIN |
ZAMMAD_TAGS | Comma-separated tags | mailrelay,alert |
MYNETWORKS to the client ranges that may submit mail.RELAY_TLS=required unless the upstream is on an isolated network.RELAY_SASL_PASS_FILE, not as a plain environment variable./var/spool/postfix and /var/lib/postfix so the queue and the chroot copy survive restarts./etc/ssl/certs when the upstream certificate is not in the image trust store.ALERT_ENABLED so a permanent failure does not stay only in the log.The image health check verifies that the Postfix master process is running and that the queue stays under 5000 messages.
Michael Kleger
OneSystems GmbH
[email protected]
MIT License. Free for commercial and private use.
Content type
Image
Digest
sha256:0863d5c6e…
Size
63.3 MB
Last updated
19 days ago
docker pull onesystems/mailrelay