Dieser Beitrag ist Teil 2 der Mailrelay-Serie und baut direkt auf Teil 1 auf.
Installation, Grundkonfiguration und Sicherheitsaspekte von Mailrelay werden dort erklärt:
Teil 1: Ein schlanker SMTP-Relay-Container für moderne Infrastrukturen
In diesem Artikel erweitern wir ein bestehendes Mailrelay-Setup um ein SMTP OAuth Relay, damit E-Mails
zu Microsoft 365 ohne klassische SMTP-Passwörter versendet werden können.
Warum OAuth statt SMTP-Passwort?
Microsoft 365 setzt zunehmend auf moderne Authentifizierung. Klassische SMTP-Logins (Benutzer/Passwort) sind in vielen Tenants
deaktiviert oder sollen aus Sicherheitsgründen vermieden werden. OAuth 2.0 löst dieses Problem durch tokenbasierten Zugriff:
kein dauerhaftes Passwort, bessere Kontrollmöglichkeiten (z.B. Conditional Access), weniger Credential-Sprawl.
Erweiterte Architektur
Mailrelay bleibt unverändert als interner SMTP-Empfangspunkt (Port 25). Neu kommt ein zusätzlicher Baustein hinzu:
smtp-oauth-relay (intern), welches SMTP annimmt und den Versand Richtung Microsoft 365 via OAuth 2.0 übernimmt.
[ App / Monitoring / Host ]
|
| SMTP (25, intern, ohne Auth)
v
[ Mailrelay ]
|
| SMTP + AUTH (intern)
| Port 8025
v
[ SMTP OAuth Relay ]
|
| OAuth 2.0 (extern)
v
[ Microsoft 365 ]
Was ist neu gegenüber Teil 1?
- Ein zusätzlicher Container: smtp-oauth-relay
- Mailrelay relayed nicht mehr direkt zum Smarthost, sondern zu smtp-oauth-relay:8025
- Mailrelay authentifiziert sich am OAuth Relay per SMTP AUTH mit tenant_id@client_id + Client Secret
- Der Versand zu Microsoft 365 erfolgt über OAuth (Token-Flow) statt SMTP-Passwort
Installation (Teil 2): Docker Compose (Mailrelay + SMTP OAuth Relay)
Unten ist eine angepasste Compose-Datei, die dein bestehendes Setup aus Teil 1 erweitert.
Sie enthält beide Services und nutzt ein Secret für das Microsoft Client Secret.
Verzeichnisstruktur
mailrelay-oauth/
├── docker-compose.yml
└── secrets/
└── m365_client_secret.txt
Secret: Microsoft Client Secret
Die Datei m365_client_secret.txt enthält ausschliesslich den Secret-Value (kein Username, keine Zusatzzeilen):
echo "YOUR_CLIENT_SECRET_VALUE" > secrets/m365_client_secret.txt chmod 600 secrets/m365_client_secret.txt
docker-compose.yml (komplett)
version: "3.9"
services:
# ------------------------------------------------------------
# 1) Mailrelay (Postfix) - nimmt SMTP :25 intern an
# ------------------------------------------------------------
mailrelay:
image: onesystems/mailrelay:latest
container_name: mailrelay
restart: unless-stopped
ports:
- "25:25" # nur intern / per Firewall schützen
environment:
HOSTNAME_FQDN: "mailrelay.example.local"
ORIGIN_DOMAIN: "example.local"
# Wer darf ohne AUTH einliefern (unbedingt einschränken!)
MYNETWORKS: "127.0.0.0/8,10.0.0.0/8,192.168.0.0/16"
# Optional: Absender kontrollieren (empfohlen bei M365 / Policies)
DEFAULT_FROM: "monitoring@example.local"
FORCE_DEFAULT_FROM: "1"
# Mailrelay ➜ SMTP OAuth Relay (intern)
RELAY_HOST: "smtp-oauth-relay"
RELAY_PORT: "8025"
RELAY_TLS: "none" # für den ersten Test ohne TLS zwischen den Containern
# SMTP AUTH gegenüber dem OAuth Relay:
# Username MUSS tenant_id@client_id sein, Passwort = Client Secret
RELAY_SASL_USER: "TENANT_ID@CLIENT_ID"
RELAY_SASL_PASS_FILE: "/run/secrets/m365_client_secret"
DEBUG: "0"
secrets:
- m365_client_secret
volumes:
- postfix_spool:/var/spool/postfix
- postfix_state:/var/lib/postfix
- /etc/ssl/certs:/etc/ssl/certs:ro
depends_on:
- smtp-oauth-relay
# ------------------------------------------------------------
# 2) SMTP OAuth Relay - nimmt SMTP intern an und sendet via OAuth zu M365
# ------------------------------------------------------------
smtp-oauth-relay:
image: ghcr.io/justiniven/smtp-oauth-relay:latest
container_name: smtp-oauth-relay
restart: unless-stopped
# Kein Port nach aussen nötig; mailrelay erreicht es im Docker-Netz
# ports:
# - "8025:8025"
environment:
LOG_LEVEL: "INFO"
# Für den ersten Test TLS aus:
TLS_SOURCE: "off"
REQUIRE_TLS: "false"
secrets:
- m365_client_secret
secrets:
m365_client_secret:
file: ./secrets/m365_client_secret.txt
volumes:
postfix_spool:
postfix_state:
Wichtig: Ersetze TENANT_ID und CLIENT_ID in beiden Services.
Der Wert in RELAY_SASL_USER muss exakt TENANT_ID@CLIENT_ID sein.
Starten
docker compose up -d
Logs prüfen:
docker logs -f mailrelay docker logs -f smtp-oauth-relay
Verwendung
SMTP-Ziel für interne Systeme
Aus Sicht deiner Applikationen ändert sich nichts: sie senden weiterhin an Mailrelay auf Port 25.
Server: mailrelay.example.local Port: 25 TLS: nein AUTH: nein
Mailrelay übernimmt:
- Mailannahme aus internen Netzen
- Absenderkontrolle (optional)
- Weiterleitung an smtp-oauth-relay
Test-Mail senden
echo "Test Mail" | mail -s "Mailrelay + OAuth Test" testuser@example.loca
Tipp: Beim Testen sendmail -v vermeiden, da dadurch Zustellreports angefordert werden können.
Die relevanten Informationen stehen ohnehin im Container-Log.
Produktivbetrieb: TLS zwischen Mailrelay und OAuth Relay
Für Produktion empfiehlt es sich, auch die interne Verbindung zwischen Mailrelay und OAuth Relay per TLS abzusichern.
Damit verschwindet ausserdem die Warnung „AUTH without TLS“.
- Im OAuth Relay TLS aktivieren (z.B.
TLS_SOURCE=file+ Zertifikate mounten) - In Mailrelay
RELAY_TLS=requiredsetzen
Die genaue TLS-Variante hängt davon ab, wie du Zertifikate intern handhaben möchtest (self-signed, interne CA, etc.).
Troubleshooting (kurz)
- OAuth token request failed (400) → meist falsches Client Secret oder falsche IDs (Tenant/Client)
- Sender rejected / not owned → Absenderadresse muss in M365 existieren bzw. erlaubt sein (DEFAULT_FROM prüfen)
- AUTH without TLS warning → für Test okay, produktiv TLS zwischen den Containern aktivieren


[…] Vorbereitung für Teil 2: Mailrelay + SMTP OAuth Relay: SMTP ohne Passwort, bereit für Microsoft 36… […]