Es ist schnell passiert, dass der eigene Postfix-Mailserver im Microsoft365 Universum als „Böse“ angesehen wird und man dann keine Mails mehr an Adressen schicken kann, die bei Microsoft365 gehostet werden.

Leider weiß man vorher nicht ob eine Zieldomäne bei Microsoft liegt oder nicht. Erst wenn man einen Bounce bekommt in dem ein Server mit „mail.protection.outlook.com“ im Namen erscheint, weiß man, dass es wieder so ein Problem war.
Anfangs habe ich dann die Domains einzeln in der Transport-Map eingetragen, damit sie über einen Dienstleister wie z.B. Mailjet versendet werden. Ich kann nicht alle Mails über den Dienstleister versenden, weil ich dann schnell an das Limit des kostenlosen Kontos käme.
Also habe ich überlegt, dass Postfix doch beim Verarbeiten der Mail feststellen könnte ob der MX für die Empfängerdomain zu Microsoft gehört. Mit Bordmitteln ist es leider nicht machbar.
Aber mit einem kleinen Programm, das als Service von Postfix gestartet wird kann man schon vor der Routingentscheidung von Postfix diese per FILTER beeinflussen.
Das Programm benötigt das Python3 Modul python3-dnspython.
apt install python3-dnspython
# Oder entsprechendes Paket deiner Distribution
In der Variable CUSTOM_ROUTING wird der Filterbefehl hinterlegt: „CUSTOM_ROUTING = „FILTER custom_relay:[Relay.server.de]:587“, damit nicht zu viele DNS Abfragen erfolgen habe ich ein simples Caching eingebaut.
#!/usr/bin/env python3
import sys
import dns.resolver
import time
import syslog
# Syslog initialisieren
syslog.openlog(ident='postfix-mx-policy', facility=syslog.LOG_MAIL)
CACHE = {}
CACHE_TTL = 3600
TARGET_MX_KEYWORD = "mail.protection.outlook.com"
#Hier den gewünschten Relay eintragen
CUSTOM_ROUTING = "FILTER custom_relay:[Relay.server.de]:587"
def get_routing_action(domain):
now = time.time()
if domain in CACHE:
action, expiry = CACHE[domain]
if now < expiry:
return action
try:
#Frage den MX der Zieldomain ab und sage entweder DUNNO oder ergänze den FILTER
answers = dns.resolver.resolve(domain, 'MX')
is_microsoft = False
for rdata in answers:
mx_host = str(rdata.exchange).lower()
if TARGET_MX_KEYWORD in mx_host:
is_microsoft = True
break
action = CUSTOM_ROUTING if is_microsoft else "DUNNO"
except Exception as e:
#Bei Fehlern sage DUNNO, das ist der Sichere-Fallback
syslog.syslog(syslog.LOG_ERR, f"DNS Fehler für {domain}: {str(e)}")
action = "DUNNO"
CACHE[domain] = (action, now + CACHE_TTL)
return action
def main():
attributes = {}
while True:
line = sys.stdin.readline()
if not line:
break
line = line.strip()
if line:
if '=' in line:
key, val = line.split('=', 1)
attributes[key] = val
else:
recipient = attributes.get('recipient', '')
recipient = recipient.strip('<>')
domain = recipient.split('@')[-1].lower() if '@' in recipient else ''
action = "DUNNO"
if domain:
action = get_routing_action(domain)
sys.stdout.write(f"action={action}nn")
sys.stdout.flush()
attributes.clear()
if __name__ == '__main__':
main()
Nicht vergessen das Programm ausführbar zu machen:
chmod +x /usr/local/bin/postfix_mx_policy.py
Nun können wir das Script in Postfix integrieren:
- In der master.cf wird das Script als Hintergrunddienst eingerichtets und der alternative Transportweg definiert:
# Der Hintergrunddienst für das Python-Skript
mx_policy_service unix - n n - 0 spawn
user=nobody argv=/usr/local/bin/postfix_mx_policy.py
# Der alternative Transportweg
custom_relay unix - - n - - smtp
-o smtp_fallback_relay=
Nach der Einbindung in der master.cf kann man mit postfix check die Konfiguration überprüfen und mit systemctl restart postfix Postfix neu starten.
Im Log sieht man dann den geänderten Relay:
2026-06-18T13:26:29.882089+02:00 postfix/smtp[198199]: 3692841150: to=<empfaenger@domain.beimicrosoft.com>, relay=relay.server.com[4.2.18.249]:587, delay=392, delays=391/0.03/0.32/0.1, dsn=2.0.0, status=sent (250 OK queued as 6be4275a-cc49-4e38-880d-aaf988d9f776)
