{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreicgvksa3tds4b46kgrasbzcx4j6e6v6cbmojrbdvr3bc5eeiwjola",
"uri": "at://did:plc:nkxz2ojdvmieg2nhphvputvp/app.bsky.feed.post/3mmjdlmqe2hk2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreicbpgbltbaq4gycyfpuowhowro6pnxqhib7tgumdztr6gr6qoeyqu"
},
"mimeType": "image/png",
"size": 142591
},
"description": "Seit einiger Zeit nutze ich das GPT-Modul von Rspamd, um bei der Spam-Erkennung ein zusätzliches Signal zu bekommen. Es ersetzt nichts — kein Bayes, kein DKIM, kein RBL — sondern ist ein weiterer Sensor im Gesamtbild. Wer sich fragt, wie das in der Praxis aussieht und worauf man achten muss: hier mein aktuelles Setup.\n\nUpdate 2026-02-13: Dieser Beitrag wurde komplett überarbeitet. Die ursprüngliche Version nutzte json=false, was zu Parse-Problemen führte. Außerdem fehlte ein Custom […]",
"path": "/2025/09/30/gpt-in-rspamd-aktivieren/",
"publishedAt": "2026-05-23T11:24:11.000Z",
"site": "https://www.kernel-error.de",
"tags": [
"AI",
"Email",
"InfoSec",
"MailServer",
"RSPAMD",
"SelfHosted",
"SPAM",
"platform.openai.com",
"Ollama",
"rspamd mit Dovecot und IMAPSieve",
"Einfach melden."
],
"textContent": "Seit einiger Zeit nutze ich das GPT-Modul von Rspamd, um bei der Spam-Erkennung ein zusätzliches Signal zu bekommen. Es ersetzt nichts — kein Bayes, kein DKIM, kein RBL — sondern ist ein weiterer Sensor im Gesamtbild. Wer sich fragt, wie das in der Praxis aussieht und worauf man achten muss: hier mein aktuelles Setup.\n\n**Update 2026-02-13:** Dieser Beitrag wurde komplett überarbeitet. Die ursprüngliche Version nutzte `json=false`, was zu Parse-Problemen führte. Außerdem fehlte ein Custom Prompt — und genau das ist der entscheidende Punkt, wie sich herausgestellt hat.\n\n### Voraussetzungen\n\n * Rspamd >= 3.12 mit GPT-Plugin (bei mir aktuell 3.14.0 auf FreeBSD 15.0)\n * Ein OpenAI API-Key (oder kompatibler Endpoint)\n * Grundverständnis von Rspamd Metrics und Actions\n\n\n\n### OpenAI API-Key anlegen\n\nWer noch keinen Key hat: Auf platform.openai.com einloggen, unter API Keys einen neuen Service-Account-Key erzeugen. Der Key wird nur einmal angezeigt — sicher ablegen. Den Verbrauch sieht man im Dashboard. Bei `gpt-4o-mini` und Mailfiltering sind die Kosten minimal.\n\n### Die Konfiguration: gpt.conf\n\nHier meine aktuelle `/usr/local/etc/rspamd/local.d/gpt.conf`:\n\n\n enabled = true;\n type = \"openai\";\n model = \"gpt-4o-mini\";\n api_key = \"GEHEIMER-KEY\";\n\n model_parameters {\n gpt-4o-mini {\n max_tokens = 160;\n temperature = 0.0;\n }\n }\n\n timeout = 10s;\n allow_ham = true;\n allow_passthrough = false;\n json = true;\n\n prompt = \"You are an email spam detector. Analyze the email and respond with ONLY a JSON object, no other text. The JSON must have these fields: \"probability\" (number 0.00-1.00 where 1.0=spam, 0.0=ham), \"reason\" (one sentence citing the strongest indicator). Example: {\"probability\": 0.85, \"reason\": \"Unsolicited offer with urgent language and suspicious links.\"} LEGITIMATE patterns: verification emails with codes, transactional emails (receipts, confirmations), newsletter unsubscribe links. Flag as spam only with MULTIPLE red flags: urgent threats, domain impersonation, requests for credentials, mismatched URLs.\";\n\n symbols_to_except {\n RCVD_IN_DNSWL_MED = -0.1;\n RCVD_IN_DNSWL_HI = -0.1;\n DWL_DNSWL_MED = -0.1;\n WHITELIST_RECP_ADDR = -0.1;\n BAYES_HAM = -0.1;\n SPAMTRAP = 0;\n RCPT_IN_SPAMTRAP = 0;\n SPAMTRAP_ADDR = 0;\n RCVD_VIA_SMTP_AUTH = 0;\n LOCAL_CLIENT = 0;\n FROM_LOCAL = 0;\n }\n\n### Was hat sich gegenüber der alten Version geändert?\n\n### json = true und der Custom Prompt\n\nDas ist die wichtigste Änderung. In meiner ursprünglichen Konfiguration stand `json = false`. Das funktionierte, hatte aber einen Haken: die Antwort des Modells wurde als Freitext geparst, was unzuverlässig war.\n\nMit `json = true` aktiviert Rspamd den JSON-Modus. Das Modell wird angewiesen, strukturiertes JSON zurückzuliefern, und der Parser erwartet ein Feld `probability` in der Antwort.\n\n**Und hier kommt der Fallstrick:** Der Default-Prompt von Rspamd passt nicht zum JSON-Modus. Er fordert das Modell auf, nummerierte Textzeilen zurückzugeben:\n\n\n Output ONLY 2 lines:\n 1. Numeric score: 0.00-1.00\n 2. One-sentence reason...\n\nDer JSON-Parser erwartet aber:\n\n\n {\"probability\": 0.85, \"reason\": \"...\"}\n\nDas Ergebnis: `cannot convert spam score` im Log und `GPT_UNCERTAIN(0.00)` bei jeder Mail. Das GPT-Modul lief, lieferte aber nie ein verwertbares Ergebnis.\n\nLösung: ein **Custom Prompt** , der explizit JSON mit dem `probability`-Feld verlangt. Damit funktioniert die Kette:\n\n 1. Rspamd sendet Mail + Prompt an OpenAI\n 2. OpenAI antwortet mit `{\"probability\": 0.9, \"reason\": \"...\"}`\n 3. Rspamd parst das JSON, findet `probability`, mappt auf GPT_SPAM/GPT_HAM/GPT_SUSPICIOUS\n\n\n\n### reason_header entfernt\n\nIn der alten Version hatte ich `reason_header = \"X-GPT-Reason\"` gesetzt. Das schrieb die GPT-Begründung als eigenen Header in die Mail. Mit `json = true` ist das nicht mehr nötig — die Reason steckt im JSON und taucht im Rspamd-Log auf. Außerdem entferne ich ohnehin GPT-Header per Milter-Config, damit keine internen Analyse-Details an den Empfänger durchsickern.\n\n### symbols_to_except angepasst\n\nÄnderungen gegenüber der alten Version:\n\n * **GREYLIST entfernt:** Greylisting ist kein Vertrauens-Signal. Eine Mail die Greylisting besteht, kann trotzdem Spam sein. GPT soll diese Mails weiterhin bewerten.\n * **BAYES_HAM hinzugefügt:** Wenn Bayes die Mail bereits sicher als Ham einstuft, spart man sich den GPT-Call. Sinnvoll für Newsletter und regelmäßige Korrespondenz.\n * **SPAMTRAP-Symbole hinzugefügt:** Mails an Spamtrap-Adressen brauchen keine GPT-Analyse, die sind per Definition Spam.\n\n\n\n### Scoring: Gewichte und Thresholds\n\nDie GPT-Symbole und ihre Gewichte in der `metrics.conf` (bzw. `local.d/groups.conf`):\n\n\n symbols {\n GPT_SPAM { weight = 9.0; description = \"GPT: classified as SPAM\"; }\n GPT_SUSPICIOUS { weight = 4.5; description = \"GPT: classified as SUSPICIOUS\"; }\n GPT_HAM { weight = -0.5; one_shot = true; description = \"GPT: classified as HAM\"; }\n }\n\n**Warum diese Gewichte?**\n\n * **GPT_SPAM (9.0):** Kräftig, aber alleine nicht genug zum Rejecten. Erst in Kombination mit anderen Signalen (Bayes, RBL, fehlende Auth) wird der Reject-Threshold erreicht.\n * **GPT_SUSPICIOUS (4.5):** Schiebt Grenzfälle in Richtung Greylist oder Add-Header. Genau dafür ist GPT am nützlichsten.\n * **GPT_HAM (-0.5):** Bewusst niedrig und `one_shot`. GPT soll Spam erkennen, nicht Ham retten.\n\n\n\nDazu die Action-Thresholds:\n\n\n actions {\n greylist = 4;\n add_header = 6;\n reject = 12;\n }\n\nReject-Threshold bei mir: 12 statt Default 15. Das geht, weil die traditionellen Checks (SPF, DKIM, DMARC, RBL, Bayes, DNSBL) bereits solide arbeiten. GPT kommt als zusätzliches Signal obendrauf.\n\n### Praxis-Beispiel\n\nHier eine echte Spam-Mail aus dem Log, bei der GPT korrekt angeschlagen hat:\n\n\n rspamd_task_write_log: (default: T (reject): [13.83/12.00]\n [BAYES_SPAM(5.10){100.00%;},\n ABUSE_SURBL(5.00){next.schnapper-empfehlung.de:url;...},\n GPT_SPAM(2.40){0.9;},\n FROM_NEQ_ENVFROM(0.50){...},\n FORGED_SENDER(0.30){...},\n ...]\n\nWas man hier sieht:\n\n * `GPT_SPAM(2.40){0.9;}` — GPT hat Probability 0.9 (90% Spam) zurückgeliefert. Rspamd mappt den Probability-Wert nicht 1:1 auf das konfigurierte Gewicht, sondern skaliert intern — hier ergeben sich 2.40 von maximal 9.0 Punkten.\n * Zusammen mit BAYES_SPAM (5.10) und ABUSE_SURBL (5.00) kommt die Mail auf 13.83 — deutlich über dem Reject-Threshold von 12.\n * GPT war hier nicht das ausschlaggebende Signal, hat aber zur Gesamtbewertung beigetragen.\n\n\n\nDas ist genau das Verhalten, das ich will: GPT als ein Baustein unter vielen, der bei Grenzfällen den Ausschlag geben kann.\n\n### Datenschutz\n\nDas muss gesagt werden: Mit diesem Setup fließen Mailinhalte an OpenAI. Wer personenbezogene Daten verarbeitet oder in einem regulierten Umfeld arbeitet, muss prüfen ob das zulässig ist. Alternative: selbst gehostete Modelle über Ollama oder kompatible lokale Endpoints. Rspamd unterstützt das über den `type`-Parameter.\n\nFür meinen privaten Mailserver ist das Risiko vertretbar — und die Ergebnisse sprechen für sich.\n\n### Zusammenfassung\n\nParameter| Wert| Warum\n---|---|---\n`json`| `true`| Strukturiertes Parsing, zuverlässiger als Freitext\n`prompt`| Custom| **Pflicht bei json=true!** Default-Prompt liefert Textformat, Parser erwartet JSON\n`temperature`| 0.0| Deterministische Antworten, kein Kreativitäts-Bonus beim Spamfiltern\n`allow_ham`| true| Kleines positives Signal für legitime Mails\n`symbols_to_except`| BAYES_HAM, DNSWL, Whitelists, SMTP_AUTH, Spamtraps| Unnötige API-Calls vermeiden\n`reason_header`| nicht gesetzt| Nicht nötig mit json=true, interne Details gehören nicht in den Header\n\n**Die wichtigste Erkenntnis:** `json = true` ohne Custom Prompt ist kaputt. Der Default-Prompt und der JSON-Parser sprechen unterschiedliche Sprachen. Wer `json = true` setzt, **muss** einen Prompt mitliefern, der JSON mit einem `probability`-Feld verlangt. Sonst steht im Log `cannot convert spam score` und GPT liefert nur `GPT_UNCERTAIN(0.00)`.\n\nSiehe auch: rspamd mit Dovecot und IMAPSieve\n\nFragen? Einfach melden.",
"title": "GPT in Rspamd aktivieren: so nutze ich das LLM-Signal im Score"
}