Sign inSign up

onesystems/mailrelay

By onesystems

•Updated 19 days ago

Minimal Postfix-based SMTP relay with TLS/SASL and docker-friendly logging.

Image
Networking
Internet of things
Web servers
0

10K+

onesystems/mailrelay repository overview

⁠MailRelay

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.


⁠Image

docker pull onesystems/mailrelay:1.2.0
RegistryTags
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).


⁠Quick start

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.


⁠What it does

  • Relays only. No local mailboxes.
  • Accepts clients from MYNETWORKS and rejects everything else (permit_mynetworks, reject_unauth_destination).
  • Speaks TLS to the upstream (required, opportunistic, or none). TLS 1.0 and 1.1 are disabled.
  • Rewrites local infrastructure senders (root, daemon, postmaster, and addresses at HOSTNAME_FQDN) to DEFAULT_FROM, or to RELAY_SASL_USER when DEFAULT_FROM is empty.
  • Watches the Postfix log and, when alerting is on, opens an internal Zammad ticket. Repeated hits are throttled.

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.


⁠Sender handling

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.


⁠Alerting

Set ALERT_ENABLED=1. Optional Zammad delivery uses ALERT_ZAMMAD_ENABLED=1 plus ZAMMAD_BASE_URL and ZAMMAD_TOKEN or ZAMMAD_TOKEN_FILE.

MatchMeaning
invalid_userUpstream mailbox or identity does not exist
sendas_deniedSender is not allowed to send as this address
postfix_bouncedDelivery failed permanently (status=bounced)
postfix_deferredDelivery 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.


⁠Testing

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.


⁠Logs

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.


⁠Environment variables

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.

⁠Relay
VariableDescriptionDefault
HOSTNAME_FQDNPostfix myhostnamemailrelay.local
ORIGIN_DOMAINPostfix myoriginonesystems.ch
RELAY_HOSTUpstream SMTP host— (required)
RELAY_PORTUpstream port587
RELAY_TLSrequired, opportunistic, or nonerequired
TLS_CA_DIRCA directory for the upstream connection/etc/ssl/certs
MYNETWORKSComma-separated client networks that may relayRFC1918 + loopback
MESSAGE_SIZE_LIMITMaximum message size in bytes30720000
STRICT_RELAYExit when RELAY_HOST is empty1
⁠SASL and sender
VariableDescriptionDefault
RELAY_SASL_USERUpstream usernameempty
RELAY_SASL_PASSUpstream passwordempty
RELAY_SASL_PASS_FILEFile with the password onlyempty
DEFAULT_FROMRewrite target for local sendersRELAY_SASL_USER
FORCE_DEFAULT_FROM1 rewrites local / infrastructure senders1
⁠Logging
VariableDescriptionDefault
DEBUG1 raises smtp_tls_loglevel to 20
DEBUG_PEER_LISTPostfix debug_peer_listempty
LOG_FILEPostfix log path inside the container/var/log/postfix.log
TRUNCATE_LOG_ON_STARTTruncate the log file on start1
LOG_TEST_ON_STARTRun postfix check before start0
LOG_STATUS_ONLYStream only status and error lines to stdout0
⁠Alerts and Zammad
VariableDescriptionDefault
ALERT_ENABLED1 watches the log for failures0
ALERT_MATCHComma-separated matchersinvalid_user,sendas_denied,postfix_bounced,postfix_deferred
ALERT_THROTTLE_SECONDSMinimum seconds between alerts300
ALERT_TAIL_LINESLog lines included as context120
ALERT_DEBUG1 logs the matched line0
ALERT_ZAMMAD_ENABLED1 creates a ticket instead of a log-only alert0
ZAMMAD_BASE_URLZammad base URLempty
ZAMMAD_TOKEN / ZAMMAD_TOKEN_FILEAPI tokenempty
ZAMMAD_GROUPTicket group name or idSupport
ZAMMAD_STATETicket statenew
ZAMMAD_PRIORITY_IDPriority id (1 low, 2 normal, 3 high)2
ZAMMAD_CUSTOMER_EMAILTicket customermailrelay@ORIGIN_DOMAIN
ZAMMAD_TAGSComma-separated tagsmailrelay,alert

⁠Production checklist

  • Publish port 25 only on an internal network.
  • Set MYNETWORKS to the client ranges that may submit mail.
  • Keep RELAY_TLS=required unless the upstream is on an isolated network.
  • Pass the SASL password with RELAY_SASL_PASS_FILE, not as a plain environment variable.
  • Persist /var/spool/postfix and /var/lib/postfix so the queue and the chroot copy survive restarts.
  • Mount the host CA bundle read-only at /etc/ssl/certs when the upstream certificate is not in the image trust store.
  • Turn on ALERT_ENABLED so a permanent failure does not stay only in the log.

⁠Health check

The image health check verifies that the Postfix master process is running and that the queue stays under 5000 messages.


⁠Author

Michael Kleger
OneSystems GmbH⁠
[email protected]⁠


⁠License

MIT License. Free for commercial and private use.

Tag summary

Content type

Image

Digest

sha256:0863d5c6e…

Size

63.3 MB

Last updated

19 days ago

docker pull onesystems/mailrelay