Das Problem
Zammad braucht für ausgehende Mails (Ticket-Replies, Benachrichtigungen) einen SMTP-Server. Die übliche Empfehlung: einen externen SMTP-Account konfigurieren oder den internen Mail-Server per Auth-Login nutzen.
Wer aber ohnehin Proxmox Mail Gateway (PMG) als internes Mail-Relay betreibt, macht sich das Leben unnötig schwer. PMG steht bereits da, hat Postfix darunter — und Postfix hat mynetworks.
Das Muster das du vielleicht kennst:
Zammad → interner Mailserver:587 (mit Auth) → PMG → Internet
Was eigentlich sinnvoll ist:
Zammad → PMG:25 (kein Auth) → Internet
Der interne Mailserver ist in diesem Fall nur Umweg. Die eingehenden Mails liest Zammad sowieso direkt per IMAP vom Mailserver — aber für den Ausgang braucht man ihn nicht.
Warum PMG ohne Auth funktioniert
PMG nutzt Postfix. Postfix hat mynetworks — eine Liste von IP-Ranges, die ohne Auth weiterleiten dürfen:
# /etc/postfix/main.cf auf PMG
mynetworks = 127.0.0.0/8, 192.168.66.0/24
Jeder Host im internen Netz kann damit direkt auf Port 25 senden. Kein Username, kein Passwort, kein TLS-Zertifikat-Ärger.
Der PMG selbst übernimmt dann Spam-Filterung, DKIM-Signierung und Weiterleitung ans Internet.
Schritt 1: DKIM für die Domain auf PMG aktivieren
PMG kann DKIM-Signierung pro Domain aktivieren. Dafür muss:
- Ein DKIM-Schlüsselpaar auf PMG vorhanden sein (bei Erst-Setup:
pmgconfig dkim genkey) - Der DNS-TXT-Record beim DNS-Provider angelegt sein
- Die Domain in PMGs Signing-Liste eingetragen sein
Den DNS-Record prüfen:
dig TXT default._domainkey.<DEINE-DOMAIN>
Erwartete Ausgabe:
default._domainkey.deathstar-cleaning.de. IN TXT "v=DKIM1; k=rsa; p=MIIBIj..."
Domain in PMG eintragen:
ssh root@192.168.66.37 "pmgsh create /config/dkim/domains -domain deathstar-cleaning.de"
# Verifikation
ssh root@192.168.66.37 "pmgsh get /config/dkim/domains"
[
{ "domain": "deathstar-cleaning.de" }
]
✅ Ab jetzt signiert PMG alle ausgehenden Mails von *@deathstar-cleaning.de mit DKIM.
Schritt 2: Zammad Channels auf PMG umstellen
Zammads Email-Channels speichern ihre SMTP-Konfiguration in der Datenbank. Die sauberste Methode ist die Rails Console.
Script schreiben (lokal in /tmp/smtp_pmg.rb):
# Channel-IDs anpassen — üblicherweise 1 (Notification) und 4 (Email-Account)
[1, 4].each do |cid|
ch = Channel.find_by(id: cid)
next unless ch
opts = ch.options['outbound']['options']
opts['host'] = '192.168.66.37' # PMG-IP
opts['port'] = 25
opts['ssl'] = 'none'
opts.delete('user')
opts.delete('password')
ch.save!
puts "Channel #{cid} updated: #{opts['host']}:#{opts['port']}"
end
Welche Channel-IDs hat meine Instanz? Mit einem kurzen Rails-Snippet prüfen:
docker exec \
-e DATABASE_URL='postgres://zammad:zammad@zammad-postgresql:5432/zammad_production?pool=50' \
zammad-zammad-railsserver-1 bundle exec rails runner \
"puts Channel.all.map{|c| \"#{c.id}: #{c.area} #{c.options.dig('outbound','options','host')}\"}.join(\"\\n\")"
Script deployen und ausführen:
scp /tmp/smtp_pmg.rb root@192.168.66.48:/tmp/
ssh root@192.168.66.48 "docker cp /tmp/smtp_pmg.rb zammad-zammad-railsserver-1:/tmp/"
ssh root@192.168.66.48 "docker exec \
-e DATABASE_URL='postgres://zammad:zammad@zammad-postgresql:5432/zammad_production?pool=50' \
zammad-zammad-railsserver-1 bundle exec rails runner /tmp/smtp_pmg.rb"
Erwartete Ausgabe:
Channel 1 updated: 192.168.66.37:25
Channel 4 updated: 192.168.66.37:25
Schritt 3: Verifikation
Konfiguration prüfen:
ssh root@192.168.66.48 "docker exec \
-e DATABASE_URL='postgres://zammad:zammad@zammad-postgresql:5432/zammad_production?pool=50' \
zammad-zammad-railsserver-1 bundle exec rails runner \
'puts [1,4].map{|id| ch=Channel.find_by(id:id); next unless ch;
\"Ch#{id}: #{ch.options[%q(outbound)][%q(options)][%q(host)]}:\
#{ch.options[%q(outbound)][%q(options)][%q(port)]} \
ssl=#{ch.options[%q(outbound)][%q(options)][%q(ssl)]} \
user=#{ch.options[%q(outbound)][%q(options)][%q(user)].inspect}\"}.compact.join(%q(\n))'"
Ch1: 192.168.66.37:25 ssl=none user=nil
Ch4: 192.168.66.37:25 ssl=none user=nil
Mail-Test: Test-Antwort aus Zammad versenden, dann auf PMG prüfen:
# PMG-Log: DKIM-Signierung bestätigen
ssh root@192.168.66.37 "grep 'deathstar-cleaning.de' /var/log/mail.log | tail -5"
postfix/cleanup[...]: message-id=<...@deathstar-cleaning.de>
opendkim[...]: ... DKIM-Signature header added
postfix/smtp[...]: status=sent (250 2.0.0 Ok: queued)
✅ DKIM-Signature header added — fertig.
Gotchas
!-Methoden in Bash-inline-Ruby: Das ! in save! wird von bash history expansion interpretiert. Immer .rb-Datei via docker cp übergeben, nie inline.
docker compose exec vs. docker exec: Bei docker compose exec erbt der Prozess die Umgebungsvariablen aus der Compose-Konfiguration inkl. DATABASE_URL. Bei docker exec nicht — also immer explizit mitgeben:
-e DATABASE_URL='postgres://zammad:zammad@zammad-postgresql:5432/zammad_production?pool=50'
Port 25 vs. 587: Port 587 ist für Auth-basiertes Submission (mit TLS, Username, Passwort). Port 25 ist MTA-to-MTA, also genau richtig für interne Relays ohne Auth.
Passwort-Änderungen: Wenn du das Passwort deines Mailkontos änderst, musst du den SMTP-Channel nicht anfassen — er hat kein Passwort mehr. Nur der IMAP-Inbound-Channel (für eingehende Mails) braucht Anpassung.
Zusammenfassung
| Vorher | Nachher |
|---|---|
| Zammad → Mailserver:587 (Auth) → PMG → Internet | Zammad → PMG:25 (kein Auth) → Internet |
| DKIM durch Mailserver | DKIM durch PMG |
| Passwort in Zammad konfiguriert | Kein Credentials nötig |
| Abhängigkeit Mailserver für Outbound | Nur PMG |
Der Trick ist simpel: mynetworks in Postfix macht interne Hosts zu vertrauenswürdigen Relays — und genau das ist PMG. Warum also den Umweg über einen Auth-pflichtigen Mailserver nehmen?