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:

  1. Ein DKIM-Schlüsselpaar auf PMG vorhanden sein (bei Erst-Setup: pmgconfig dkim genkey)
  2. Der DNS-TXT-Record beim DNS-Provider angelegt sein
  3. 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

VorherNachher
Zammad → Mailserver:587 (Auth) → PMG → InternetZammad → PMG:25 (kein Auth) → Internet
DKIM durch MailserverDKIM durch PMG
Passwort in Zammad konfiguriertKein Credentials nötig
Abhängigkeit Mailserver für OutboundNur 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?