Antispam Settings
Antispam Settings
Admin path: Content Checks > Antispam Settings
(view_antispam_maintenance.cfm, inc/get_spam_settings.cfm,
inc/spam_settings_save.cfm, inc/update_amavis_config_files.cfm,
inc/update_spamassassin_config_files.cfm, inc/restart_amavis.cfm,
inc/restart_spamassassin.cfm, inc/antispam_init_pyzor.cfm,
inc/antispam_init_razor.cfm, inc/antispam_clear_bayes.cfm).
This page configures the SpamAssassin engine that Amavis calls inside
hermes_mail_filter for every message that clears the SMTP-time
perimeter, plus the Amavis-level handling policies that decide what
happens to a message once it has been scored or otherwise classified.
Per-rule weight adjustments live on Score Overrides;
this page is engine settings and quarantine destiny only.
Where SpamAssassin sits in the flow
+-----------------------------------+
inbound msg -->| Perimeter Checks pass |
+---------------+-------------------+
|
v
+-----------------------------------+
| Postfix smtpd_proxy_filter |
| -> hermes_mail_filter:10024 |
+---------------+-------------------+
|
v
+-----------------------------------+
| Amavis (hermes_mail_filter) |
| - ClamAV virus scan |
| - SpamAssassin scoring |
| DCC / Razor / Pyzor net DBs |
| Bayes statistical engine |
| custom rules + scores |
| - banned-file checks |
| - final_*_destiny -> quarantine/DSN/discard
+---------------+-------------------+
|
v
+-----------------------------------+
| Re-inject -> hermes_postfix_dkim:10026
+-----------------------------------+
A virus verdict from ClamAV always pre-empts the spam score; the
final_virus_destiny setting on this page decides what Amavis does
with that already-classified virus. The final_spam_destiny,
final_banned_destiny, and final_bad_header_destiny settings work
the same way for the other three Amavis verdict categories.
Container and tool placement
| Component | Detail |
|---|---|
| Container | hermes_mail_filter (IPv4 .105) |
| Engine | SpamAssassin (spamd / Mail::SpamAssassin Perl modules called from Amavis) |
| Amavis config | /etc/amavis/conf.d/50-user (rendered from /opt/hermes/conf_files/50-user.HERMES on every save) |
| SpamAssassin config | /etc/spamassassin/local.cf (rendered from /opt/hermes/conf_files/local.cf.HERMES on every save) |
| Bayes DB | Lives in the SpamAssassin user dir inside hermes_mail_filter (sa-learn --dump magic reports the actual path) |
| Network plugin state | /etc/razor/identity (Razor), Pyzor's per-user config dir, DCC's local socket, all inside hermes_mail_filter |
| Reload mechanism | spamassassin --lint + docker container restart hermes_mail_filter on every save |
The container exposes no host ports. Amavis is reached only by
Postfix internally at hermes_mail_filter:10024 and re-injects to
hermes_postfix_dkim:10026.
Spam Detection Plugins card
Three boolean toggles enable third-party network-aware spam DBs.
Storage: spam_settings.value for parameters use_dcc, use_razor2,
use_pyzor (each row keyed by parameter, value 0 or 1).
| Plugin | What it does | Maintenance action |
|---|---|---|
| DCC (Distributed Checksum Clearinghouse) | Fuzzy-checksum bulk-mail detection; matches a message against a network of receivers' checksum counters | None. cdcc runs as part of the SpamAssassin call chain |
| Razor2 (Vipul's Razor v2) | Collaborative spam catalog; checksum + signature lookup against the Razor network | Initialize Razor (see Maintenance) before first use |
| Pyzor | Collaborative digest-based spam detection | Initialize Pyzor before first use |
Each toggle substitutes into local.cf via the placeholders USE-DCC,
USE-PYZOR, USE-RAZOR2 -> use_dcc 0|1, use_pyzor 0|1,
use_razor2 0|1.
Operational consequence: network DB connectivity. All three plugins make outbound queries (DCC over UDP, Razor and Pyzor over TCP) at scan time. If outbound to the public Internet is blocked from
hermes_mail_filter, the plugins quietly time out per message and add measurable per-scan latency. Disable plugins the gateway cannot actually reach.
Defaults
All three ship disabled from v260807. They are effective, but each one transmits a digest or checksum of every scanned message to a third-party network. Whether that is acceptable is a decision for the operator, so Hermes does not make it silently. Installs predating v260807 had all three enabled in the database while Razor was never registered, so it returned no result regardless.
Enabling a plugin the gateway has not been provisioned for is harmless but useless; see the maintenance actions above.
DCC is not included in the published image
DCC is absent from the shipped hermes_mail_filter image, so leaving
use_dcc enabled has no effect unless you add it yourself.
This is a licensing constraint, not a technical one. The DCC licence is free only to organisations that "do not sell filtering devices or services except to their own users," and it does not permit redistributing binaries. Most self-hosted operators filtering their own mail do qualify; Deeztek sells Hermes Pro and does not, so we cannot ship the binaries on your behalf.
To add it, uncomment the build: block for hermes_mail_filter in
docker-compose.yml and rebuild:
docker compose build hermes_mail_filter
docker compose up -d hermes_mail_filter
Then set DCC = Enabled on this page. The build fetches DCC directly from Rhyolite, so your relationship with that licence is your own.
Re-run the rebuild after every Hermes upgrade. The update orchestrator pulls images, which replaces your locally built one and removes DCC until you rebuild.
Subject Tagging card
Single field, sa_spam_subject_tag in spam_settings. Substitutes
into 50-user via the sa-spam-subject-tag placeholder, which sets
Amavis's $sa_spam_subject_tag. Default [SUSPECTED SPAM]. Required
(empty value rejected with error 2). Only applied when
sa_spam_modifies_subj = 1 (a fixed value in spam_settings, not
exposed in the UI).
Message Handling Policies card
Four radio pairs, one per Amavis verdict category. Each row stores
D_DISCARD or D_BOUNCE in spam_settings.value and substitutes
into 50-user via final-<category>-destiny. Amavis acts on the
value as follows:
| Setting | DB row | "Quarantine Only" (D_DISCARD) |
"Quarantine & Send DSN" (D_BOUNCE) |
|---|---|---|---|
| Virus Messages | final_virus_destiny |
Message goes to quarantine; no DSN | Message goes to quarantine; DSN sent to envelope sender |
| Banned File Messages | final_banned_destiny |
Same as above for banned-file matches | DSN sent |
| Spam Messages | final_spam_destiny |
Quarantined silently | DSN sent |
| Bad-Header Messages | final_bad_header_destiny |
Quarantined silently | DSN sent |
The labels are deliberately conservative; D_DISCARD does not
delete the message, it routes it to Amavis's quarantine where Message
History can review and release it. Defaults: virus + banned send DSN;
spam + bad-header quarantine silently.
Operational consequence: Send DSN on spam. Setting
final_spam_destiny = D_BOUNCEmeans Hermes will deliver a non-delivery report to the envelope sender of every quarantined spam. Because the envelope sender is almost always forged on spam, the DSN will either bounce, contribute to backscatter against innocent third parties, or land in a victim's spam folder. The safe default for spam isD_DISCARD; reserve DSN for virus and banned-file (where the sender is more likely to be legitimate).
Bayes Database card
SpamAssassin's per-installation statistical learning engine. Three
controls, stored in spam_settings:
| Field | DB row | Substitution placeholder | Effect |
|---|---|---|---|
| Enable Bayes Database | use_bayes |
USE-BAYES -> use_bayes followed by 0 or 1 |
Master switch; when off, Bayes rules contribute no score |
| Enable Auto-Learning | bayes_auto_learn |
BAYES-AUTO-LEARN -> bayes_auto_learn followed by 0 or 1 |
When on, SpamAssassin trains the Bayes DB automatically based on the message's final score relative to the thresholds below |
| Spam Threshold | bayes_auto_learn_threshold_spam |
BAYESAUTOLEARN-SPAM -> bayes_auto_learn_threshold_spam <value> |
Final score above which auto-learn treats the message as spam. Must be numeric and in the range 0.01 .. 999 |
| Non-Spam Threshold | bayes_auto_learn_threshold_nonspam |
BAYESAUTOLEARN-HAM -> bayes_auto_learn_threshold_nonspam <value> |
Final score below which auto-learn treats the message as ham. Must be numeric and in the range -999 .. -0.01 |
The thresholds are SpamAssassin's bayes_auto_learn_threshold_spam
and bayes_auto_learn_threshold_nonspam directives. JavaScript on
the page collapses the thresholds when Bayes or auto-learning is
disabled.
Defaults and first use
From v260807, use_bayes ships enabled and bayes_auto_learn ships
disabled. Bayes is local-only (it transmits nothing anywhere), so
leaving it on costs nothing, but it is trained deliberately rather than
automatically for the reason described below.
The database ships empty. SpamAssassin applies no Bayes score at all until it has learned roughly 200 spam and 200 ham messages; below that threshold it stays silent rather than guessing from thin evidence. A new gateway therefore shows Bayes as enabled and contributing nothing, which is correct behaviour and not a fault.
Train it from Quarantine and Message History with Train as Spam and Train as Ham. Mailbox users can train from their own portal as well, so the corpus grows from real classification decisions rather than from the rule set's guesses.
Installs predating v260807 shipped with a pre-trained Bayes corpus built from unrelated mail between 2020 and 2025. Because SpamAssassin merges all learning into a single token store, anything trained locally since was inseparable from it. The v260807 upgrade clears the database once so the gateway learns from its own traffic.
Auto-learning remains available but is not recommended for general use.
Operational consequence: Bayes poisoning. Auto-learning trusts the final score (which already includes Bayes's own contribution) to decide whether to train. A bad spam wave that sneaks past the score threshold can train Bayes to think more spam is ham, which lowers detection on the next batch. If detection quality regresses noticeably after enabling auto-learning, use the Clear Bayes Database action and re-train manually or via a known-good corpus before re-enabling.
Save flow
1. View page submits action="save_settings" (all four cards in one POST)
2. spam_settings_save.cfm validates:
- sa_spam_subject_tag non-empty (error 2)
- if bayes_auto_learn=1:
spam threshold numeric (error 5), > 0 and <= 999 (error 4),
non-empty (error 3)
non-spam threshold numeric (error 10), < 0 and >= -999 (error 8),
non-empty (error 7)
3. On valid input, UPDATEs 13 rows in spam_settings (sa_spam_subject_tag,
four final_*_destiny, use_bayes, bayes_auto_learn, both thresholds,
use_dcc, use_razor2, use_pyzor)
4. cfinclude update_amavis_config_files.cfm:
- Reads /opt/hermes/conf_files/50-user.HERMES
- Substitutes SERVER-NAME, SERVER-DOMAIN, sa-spam-subject-tag,
final-{virus,banned,spam,bad-header}-destiny,
enable-dkim-{verification,signing},
HERMES-USERNAME, HERMES-PASSWORD,
FILE-RULES-GO-HERE (from file_rule_components table),
DKIM-KEYS-GO-HERE (from dkim_sign table)
- Backs up /etc/amavis/conf.d/50-user -> 50-user.HERMES.BACKUP
- Moves rendered file into place
5. cfinclude update_spamassassin_config_files.cfm:
- Reads /opt/hermes/conf_files/local.cf.HERMES
- Substitutes USE-DCC, USE-PYZOR, USE-RAZOR2, USE-BAYES,
BAYES-AUTO-LEARN, BAYESAUTOLEARN-SPAM, BAYESAUTOLEARN-HAM
- Appends per-rule score lines (from spam_settings where spamfilter=1)
- Appends custom message rules (from message_rules table)
- Backs up /etc/spamassassin/local.cf -> local.cf.HERMES.BACKUP
- Moves rendered file into place
6. cfinclude restart_amavis.cfm -> restart_mail_filter.cfm:
- docker container restart hermes_mail_filter
7. cfinclude restart_spamassassin.cfm:
- docker exec hermes_mail_filter /usr/bin/spamassassin --lint
- docker container restart hermes_mail_filter
8. session.m = 1 -> green "Anti-spam settings have been saved and applied" alert
9. cflocation back to view_antispam_maintenance.cfm
The same container is restarted twice (once for Amavis, once for
SpamAssassin) because the restart includes are intentionally
independent helpers used elsewhere; both calls resolve to the same
docker container restart hermes_mail_filter. Outbound mail queues
briefly during the restart cycle (typically a few seconds); Postfix
will retry.
Maintenance card group
Initialize Pyzor
Action handler: antispam_init_pyzor.cfm
docker exec hermes_mail_filter /usr/bin/pyzor ping
Pings the Pyzor servers; success is detected by the literal string
200 in the output. The command both verifies connectivity and writes
the per-user Pyzor config the first time it runs. Required before
use_pyzor = 1 returns meaningful results.
Initialize Razor
Action handler: antispam_init_razor.cfm
docker exec hermes_mail_filter /bin/bash -c \
'rm -f /etc/razor/identity && razor-admin -create && razor-admin -register'
Deletes the existing Razor identity, creates a fresh config, and
registers the gateway with the Razor network. Success is detected by
Register successful or created in the output. Re-run if Razor
queries start failing (typically after the identity is rotated or the
network rejects the existing identity).
Clear Bayes Database
Action handler: antispam_clear_bayes.cfm
docker exec hermes_mail_filter /usr/bin/sa-learn --clear
Wipes the learned spam/ham corpus. SpamAssassin will need to re-learn
from scratch before Bayes rules contribute meaningful scores again.
Use only when the database is known-poisoned or when migrating between
servers without preserving training. The button is gated behind a
JavaScript confirm() and renders inside a yellow warning card.
You do not need to run this after upgrading to v260807; that upgrade clears the previously shipped corpus once, automatically. Fresh v260807 installs start with an empty database and are unaffected.
Failure semantics
| Failure | Behavior |
|---|---|
Empty sa_spam_subject_tag |
session.m=2, red alert, no save |
| Bayes spam threshold empty | session.m=3 |
| Bayes spam threshold not numeric | session.m=5 |
| Bayes spam threshold <= 0 or > 999 | session.m=4 |
| Bayes non-spam threshold empty | session.m=7 |
| Bayes non-spam threshold not numeric | session.m=10 |
| Bayes non-spam threshold >= 0 or < -999 | session.m=8 |
| Any cfcatch during the save -> apply chain | session.m=9, red alert with session.saveError showing cfcatch.message |
spamassassin --lint failure during restart |
error.cfm cfabort with the lint failure message; the rendered local.cf is already in place but Amavis is not restarted further |
Pyzor ping output without 200 |
session.m=12, red alert; full output shown in a <pre> for diagnosis |
Razor init output without Register successful or created |
session.m=14, similar surfacing |
Bayes clear cfcatch |
session.m=16 with the catch message |
spamassassin --lint is the canonical pre-restart sanity check:
when a custom rule (added via Score Overrides or message rules) has
invalid syntax, the lint catches it before the container restart
finishes and prevents Amavis from starting against a broken config.
Files and containers touched
| Path | Owner | Role |
|---|---|---|
config/hermes/var/www/html/admin/2/view_antispam_maintenance.cfm |
hermes_commandbox |
The page |
config/hermes/var/www/html/admin/2/inc/spam_settings_save.cfm |
hermes_commandbox |
Validation + UPDATE + apply chain |
config/hermes/var/www/html/admin/2/inc/get_spam_settings.cfm |
hermes_commandbox |
Loads current spam_settings rows |
config/hermes/var/www/html/admin/2/inc/update_amavis_config_files.cfm |
hermes_commandbox |
Renders 50-user from template + DB |
config/hermes/var/www/html/admin/2/inc/update_spamassassin_config_files.cfm |
hermes_commandbox |
Renders local.cf from template + DB |
config/hermes/var/www/html/admin/2/inc/restart_amavis.cfm / restart_spamassassin.cfm / restart_mail_filter.cfm |
hermes_commandbox |
docker container restart hermes_mail_filter |
config/hermes/var/www/html/admin/2/inc/antispam_init_pyzor.cfm / antispam_init_razor.cfm / antispam_clear_bayes.cfm |
hermes_commandbox |
Maintenance docker-exec helpers |
config/hermes/opt/hermes/conf_files/50-user.HERMES |
template (read) -> hermes_mail_filter (live /etc/amavis/conf.d/50-user) |
Amavis directives template |
config/hermes/opt/hermes/conf_files/local.cf.HERMES |
template (read) -> hermes_mail_filter (live /etc/spamassassin/local.cf) |
SpamAssassin directives template |
/etc/amavis/conf.d/50-user.HERMES.BACKUP |
hermes_mail_filter |
Pre-write backup, refreshed each save |
/etc/spamassassin/local.cf.HERMES.BACKUP |
hermes_mail_filter |
Pre-write backup, refreshed each save |
spam_settings table |
hermes_db_server (hermes DB) |
Source of truth for every UI value on this page; also holds per-rule scores (spamfilter=1 rows) for Score Overrides |
message_rules table |
hermes_db_server |
Custom header/body/full message rules; rendered into local.cf |
file_rule_components / files tables |
hermes_db_server |
Banned-file rules; rendered into 50-user |
dkim_sign table |
hermes_db_server |
Per-domain DKIM keys; rendered into 50-user for outbound signing |
Related
- Antivirus Settings -- the ClamAV engine that runs in the same Amavis pass and whose virus verdict always pre-empts the spam score
- Malware Feeds -- third-party ClamAV signature feeds; orthogonal to spam scoring but consumed in the same scan
- Score Overrides -- per-rule SpamAssassin
weight adjustments (the
spamfilter=1rows inspam_settings); this page sets the engine knobs, that page sets the rule weights - Message Rules -- custom header / body / full
message regex rules that ride into
local.cfon every save here - SVF Policies -- per-sender and per-recipient
spam-handling overrides that apply before the engine-wide
final_*_destinysettings on this page - Perimeter Checks -- the SMTP-time gate; every check on this page runs only after a connection clears the perimeter
- ARC Settings -- seals over the body Amavis passed, so a high spam score (and any quarantine action) naturally pre-empts the seal
- DMARC Settings -- a DMARC-fail verdict can promote a message to a higher spam score via SpamAssassin's DMARC rule weights (tunable on Score Overrides)
- Scheduled Tasks --
sa-updatefor the SpamAssassin rule set runs on its own Ofelia schedule; the Bayes DB is per-installation and not updated bysa-update - Email flow -- full pipeline diagram