Email Policies Disclaimers Disclaimers Pro Edition feature. Maps to Email Policies > Disclaimers ( view_disclaimers.cfm, edit_disclaimer.cfm, disclaimer_delete.cfm). Hermes appends a configurable disclaimer to outbound mail at the gateway, with two scopes: Scope Sender match Use case Domain All senders in @example.com Default org-wide compliance/legal language Relay Recipient Specific full address (e.g. vendor@example.com) Per-relay-user override for tenants with extra regulatory language Most-specific match wins: a relay-recipient match is used before the domain default. Pipeline placement Disclaimers are applied at SMTP receive time by the hermes_body_milter container, which Postfix consults as a milter alongside OpenDKIM and OpenDMARC. External MTA / MUA submission │ ▼ Postfix smtpd ├─ smtpd_milters chain (in order): │ 1. OpenDKIM (signs/verifies) │ 2. OpenDMARC (DMARC policy) │ 3. hermes_body_milter (THIS — disclaimers, signatures, banners) ▼ content_filter → Amavis (unmodified path; sees the body milter's output) ▼ Ciphermail (server-side S/MIME or PGP, if configured) ▼ Postfix :10026 (OpenDKIM signs the final composed body here) ▼ external Body modification happens at smtpd time, before content_filter routes to Amavis. By the time Amavis sees the message, the disclaimer is already baked in. Amavis processes a normal-looking message; no internal-state coupling, no temp-file races. OpenDKIM's outbound signing fires at the :10026 re-injection — after both the body milter and Ciphermail. Hermes' own DKIM therefore always covers whatever the recipient ultimately receives. Ciphermail's server-side crypto also covers the disclaimer because Ciphermail runs after the milter. Behavior with S/MIME, PGP, and DKIM-signed mail The behavior depends on who signed/encrypted the message and when in the pipeline. Server-side: signed/encrypted by Ciphermail — disclaimer is applied Ciphermail runs after the body milter. Mail arrives at the milter as plaintext, the disclaimer is appended, then Ciphermail signs or encrypts the modified body. The recipient sees a valid signature and the disclaimer. No conflict. Client-side: signed/encrypted by the user's MUA — disclaimer is skipped Mail signed in Outlook (S/MIME) or Thunderbird+Enigmail (PGP) arrives at the gateway with the cryptographic envelope already sealed. Modifying the body would either invalidate the signature or mangle the ciphertext. The body milter detects the following patterns in the headers (or first 32 KB of the body) and exits unchanged when any matches: Pattern matched Meaning Content-Type: multipart/signed; protocol="application/pkcs7-signature" S/MIME detached signature Content-Type: application/pkcs7-mime S/MIME opaque-signed or enveloped Content-Type: multipart/signed; protocol="application/pgp-signature" PGP/MIME detached signature Content-Type: multipart/encrypted; protocol="application/pgp-encrypted" PGP/MIME encrypted -----BEGIN PGP SIGNED MESSAGE----- in body PGP inline-signed -----BEGIN PGP MESSAGE----- in body PGP inline-encrypted When any of those match, the body is left untouched, the signature stays valid, the user's legal-text expectations are preserved (their MUA template is already in the body), and the gateway gets out of the way. Operational consequence. A site whose users sign client-side will not get gateway disclaimers on those specific signed messages — by design. If org-wide legal text on all outbound is mandatory, the only safe pattern is server-side signing in Ciphermail with the disclaimer applied first. DKIM: Hermes-signed mail is fine; upstream-signed mail is skipped OpenDKIM signs at the Postfix :10026 re-injection step — after the body milter. So Hermes' own DKIM signature always covers the recipient's view of the message (with disclaimer baked in). No conflict. The risk is mail that arrives at Hermes already DKIM-signed by an upstream MTA — typically a relay user whose own mail server signs before forwarding through us. Modifying that body would invalidate the upstream signature at the recipient. The body milter treats a pre-existing DKIM-Signature: header the same way as a sealed S/MIME or PGP envelope and skips the disclaimer. Since Hermes' own DKIM signs at :10026 (downstream of this milter), any DKIM-Signature header present at the milter's point in the pipeline came from somewhere upstream of Hermes. Reply-chain handling — no dedup, by design The milter does not detect or skip messages that already carry a previous disclaimer in their quoted history. Every outbound message gets a fresh disclaimer applied — including replies inside a long thread. This matches industry norm: commercial server-side disclaimer / signature platforms (Exclaimer, Crossware, CodeTwo, Microsoft 365 transport rules) all stamp every outbound without dedup. The reasoning: Compliance. Many regulatory regimes (HIPAA email confidentiality, GDPR data-controller notices, financial-services disclosure) treat each transmission as requiring its own disclaimer. Stamping only the first message in a thread arguably leaves later replies non-compliant. Self-contained messages. If a recipient forwards a reply (with quoted history) to a third party, the disclaimer is preserved per-message in the forwarded text. Predictable behavior. Operators don't have to explain "sometimes the disclaimer shows, sometimes it doesn't." Cosmetic concern is weak. Modern MUAs (Gmail, Outlook, Apple Mail) collapse quoted history by default, so stacked disclaimers in long threads are rarely visible to readers. Earlier iterations of #214 included a sentinel-marker dedup mechanism ( [HD] / ). That was removed during DEV testing in favor of the industry-norm pattern. Position: append vs prepend The schema and UI both expose position = append | prepend, but v1 honors append only. Prepend is tracked as a v2 enhancement. Failure semantics The body milter is graceful-degradation by design. Postfix's milter_default_action = accept means: Milter container down or unreachable → mail flows unmodified (missed disclaimer, but no delivery outage) Map file unreadable → no entries match → all mail flows unmodified Modifier raises an exception → caught and logged → mail flows unmodified altermime / parse errors → caught and logged → mail flows unmodified In every failure case, mail keeps flowing. Worst case is a missed disclaimer, never lost mail. Compare the legacy "modify in amavis hook" approach (#214 Phase 3 v1, retired) which silently dropped messages when the in-place body modification desynced amavis's internal state. Files generated on save/delete The CFML include inc/disclaimer_write_and_reload.cfm runs after every save or delete and rewrites the entire on-disk state from the disclaimers table: /etc/hermes/body_milter/disclaimers/disclaimer_by_sender sender → option map /etc/hermes/body_milter/disclaimers/files/