External Banner
External Banner
Maps to Email Policies > External Banner (
view_external_banners.cfm,
edit_external_banner.cfm,
external_banner_delete.cfm). Available on both Community and Pro editions — phishing protection is a baseline security feature, not a Pro upsell.
Hermes prepends (or optionally appends) a warning banner to inbound mail from external senders destined for a local recipient. The banner is injected into the message body itself, so every MUA — webmail, Outlook, Apple Mail, mobile clients — renders it without relying on transport rules or recipient-side configuration. Tracked as #228.
Scope
Scope
Recipient match
Use case
System default
All recipient domains (no override)
Single banner used everywhere; recommended starting point
Per-recipient-domain
Specific local mailbox domain (e.g.
legal.example.com)
Different copy or compliance language for one domain
Resolution at message time, in the body milter's
ExternalBannerModifier:
Look up the first local recipient's domain in
/etc/hermes/body_milter/banners/banner_by_recipient_domain.
If a matching row exists, use it.
Otherwise fall back to the
_default system-wide entry.
Otherwise no banner is applied.
Only the first local recipient is consulted — mixed-domain envelopes get the banner of the first local recipient encountered. This keeps the modification deterministic regardless of envelope ordering.
The
recipient_domain field is locked after creation. Delete and re-create the row to change scope.
What counts as "external"
The body milter uses Postfix's
/etc/postfix/relay_domains file as the source of truth for "local". A message is considered inbound from an external sender when:
The
MAIL FROM sender domain is not in
relay_domains, AND
At least one
RCPT TO recipient domain is in
relay_domains.
Internal-to-internal mail (sender + all recipients local) is classified as
direction = internal and the banner is not applied. There is no separate allowlist of "trusted partner" external senders today — every external sender to a local recipient triggers the banner if one is configured for that recipient's domain.
Pipeline placement
The banner is injected at SMTP receive time by the
hermes_body_milter container, the same container that emits outbound disclaimers (disclaimers.md) and organizational signatures (organizational-signatures.md). The milter listens on
inet:hermes_body_milter:8893 and Postfix consults it as part of
smtpd_milters.
Inbound external MTA
|
v
Postfix smtpd
+- smtpd_milters chain (in order):
| 1. OpenDKIM (verifies upstream DKIM signature)
| 2. OpenDMARC (DMARC policy + ARC verification)
| 3. hermes_body_milter (THIS -- banner prepended here)
| --> Authentication-Results header has already been written
| by OpenDKIM/OpenDMARC BEFORE the banner touches the body
v
content_filter --> Amavis (sees the banner-prepended body)
v
Ciphermail (server-side S/MIME or PGP, if configured)
v
Postfix :10026 (multi-instance OpenDKIM re-signs the final body)
v
Local delivery (Dovecot LMTP)
Key ordering points:
OpenDKIM verifies first. The upstream sender's DKIM verdict is captured in
Authentication-Results: headers before the banner is injected. The header is preserved on the message; the banner does not retroactively change what OpenDKIM saw at smtpd time.
Amavis sees the modified body. Spam scoring runs against the banner-prepended message. This is intentional — the banner content is short and stable and does not skew SpamAssassin scores in practice.
Hermes' downstream re-sign covers the modified body. The multi-instance OpenDKIM at
:10026 (#232) signs after Ciphermail rebuild, so the final outgoing-to-Dovecot body is covered by Hermes' own signature.
Behavior with signed and encrypted mail
The modifier inherits the same skip rules as Disclaimers for sealed envelopes:
Pattern matched
Meaning
Banner action
Content-Type: multipart/signed; protocol="application/pkcs7-signature"
S/MIME detached
Skip
Content-Type: application/pkcs7-mime
S/MIME opaque/enveloped
Skip
Content-Type: multipart/signed; protocol="application/pgp-signature"
PGP/MIME detached
Skip
Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"
PGP/MIME encrypted
Skip
-----BEGIN PGP SIGNED MESSAGE----- in body
PGP inline-signed
Skip
-----BEGIN PGP MESSAGE----- in body
PGP inline-encrypted
Skip
Pre-existing
DKIM-Signature: header on inbound mail
Upstream DKIM signed
Modify anyway (see below)
The corresponding flags on
ExternalBannerModifier are
skip_on_signed = True,
skip_on_pgp_inline = True,
skip_on_dkim = False.
Why the banner does NOT skip on upstream DKIM
About 95% of inbound mail today carries a
DKIM-Signature: header. If the banner skipped on DKIM, the feature would be effectively inert — the warning would only land on the unsigned minority that needs it least.
Hermes already records the upstream DKIM verdict in
Authentication-Results: before modifying the body. Recipients overwhelmingly read mail through Dovecot/IMAP and the recipient MUA does not re-verify upstream DKIM. The banner is therefore safe in the common case.
The narrower edge case — a recipient who forwards Hermes-banner'd mail to a downstream MX that does re-verify upstream DKIM — is addressed by ARC sealing (#229). Hermes' ARC seal at
:10026 records
cv=fail for the upstream chain (because we modified the body), but the seal itself is mathematically valid and the downstream MX can trust Hermes' ARC verdict if Hermes is on its allowlist. See ARC Settings for the full discussion of the
cv=fail-by-design pattern.
Operational consequence. Banner injection breaks the original sender's DKIM body hash and any upstream ARC body hash. This is by design. Hermes is the authoritative auth boundary for the domains it relays; customer downstream MX servers must allowlist Hermes and accept its delivered mail without re-running DKIM/SPF/DMARC/ARC. A downstream MX that re-verifies upstream auth on mail Hermes forwards is misconfigured — cross-ref ARC Settings, DKIM Settings, and DMARC Settings.
Position: prepend vs append
Position
Behavior
Recommendation
Top (
prepend)
Banner becomes the first child of the message body (above any quoted history)
Industry standard — users see the warning before reading any content
Bottom (
append)
Banner is appended after the user-visible body
Available for sites that prefer it; rarely used
Both positions are implemented end to end (unlike Disclaimers, where only
append is honored in v1). HTML prepend is done with BeautifulSoup: the banner fragment is inserted as the first child of
when present, otherwise prepended to the root.
Templates
Banners use a server-side template gallery, not a free-form WYSIWYG editor. Quill 2.x's HTML normalization strips inline styles that Gmail and Outlook need (the same problem hit on Organizational Signatures #226 Phase 2 and on this feature), so admins pick a template and fill in form fields; the server renders pixel-perfect HTML at save time.
Bundled templates (each
inc/external_banner_templates/.cfm):
Template key
Display name
When to pick it
warning_yellow
Warning Yellow
Default. Yellow background with orange accent. Matches Microsoft 365 / Mimecast banner style most users recognize
critical_red
Critical Red
Red background, white text. Phishing-prone industries or post-incident periods where alert level needs to be raised
subtle_info
Subtle Info
Light gray with blue accent. Less alarming for high-volume inbound (support/sales) where alert fatigue is a concern
plain_text
Plain Text
Bold prefix + text, no background or border. Maximum cross-MUA compatibility, including text-only clients
All four templates expose the same field set:
Field
Type
Default
Notes
prefix
text
[EXTERNAL]
Short tag rendered bold at the start. Plain ASCII recommended for Outlook
headline
text
"This message originated from outside your organization."
First line, regular weight
body
text
"Do not click links or open attachments unless you recognize the sender..."
Second line, smaller text
show_learn_more
checkbox
false
Reveals the next two fields
learn_more_url
url
empty
Optional link to internal phishing-awareness training or wiki
learn_more_label
text
"Learn more about phishing"
Visible label for the learn-more link
All templates emit table-based HTML with
bgcolor= attributes so Outlook (which strips inline CSS but honors deprecated HTML attributes) renders the banner correctly. Inline styles are belt-and-suspenders for Gmail, Apple Mail, and mobile clients.
The edit page renders a live preview in an iframe via
inc/render_external_banner_preview.cfm so the admin sees exactly what
save_external_banner_action.cfm will store.
Files generated on save/delete
inc/external_banner_write_and_reload.cfm runs after every save or delete and rewrites the entire on-disk state from the
external_banners table:
/etc/hermes/body_milter/banners/banner_by_recipient_domain
\t