External Recipients
External Recipients
Admin path: Encryption > External Recipients
(view_ext_rec_encryption.cfm, view_create_ext_recipient.cfm,
view_ext_smime_certificates.cfm, view_ext_pgp_keyrings.cfm,
view_ext_add_smime_cert.cfm, view_ext_add_pgp_keyring.cfm,
inc/create_ext_recipient.cfm, inc/delete_ext_recipient.cfm,
inc/reset_pdf_password.cfm, inc/reset_portal_password.cfm).
This is the per-counterparty encryption policy and key store for external (non-managed) email addresses. Each row binds a single external email to one of three protocols (PDF / S/MIME / PGP) and to one of two trigger modes (Mandatory / By Subject). It is the page where the policy referenced by Encryption Settings actually takes effect — the global page chooses the mechanism (subject trigger keyword, shared secrets, PDF reply sender); this page chooses the policy for every external recipient the gateway encrypts to.
The DataTable is the master view across both Hermes-side metadata
(external_recipients in the hermes DB) and CipherMail's own user
table (cm_users in the djigzo DB), joined on email address. Rows
are tagged Admin-Configured (explicitly created on this page,
with a matching external_recipients row) or Auto-Discovered
(materialized by CipherMail during message processing, no
external_recipients row).
Schema: two tables, one view
+--------------------------+ +--------------------------+
| hermes.external_recipients | djigzo.cm_users |
| (admin metadata) | | (CipherMail user store) |
+--------------------------+ +--------------------------+
| email | ---- | cm_email |
| encryption_mode | | cm_id --> cm_properties|
| pdf, smime, pgp (flags) | | (per-user |
| pdf_mode | | policy) |
| pdf_password (AES-enc.) | +--------------------------+
+--------------------------+
|
v
Page renders Admin badge
|
+--------------------------+
| If NO matching row, |
| recipient is "Auto" with |
| inferred policy from |
| cm_certificates_email / |
| cm_keyring_email |
+--------------------------+
The page never N+1's against CipherMail — three batch queries build
struct lookups (adminLookup, smimeLookup, pgpLookup) and the row
loop reads from those instead of per-row queries. That matters at any
scale beyond a few hundred recipients.
external_recipients columns:
| Column | Purpose |
|---|---|
id |
PK |
email |
External email address (joined to cm_users.cm_email) |
encryption_mode |
pdf_mandatory / pdf_by_subject / smime_mandatory / smime_by_subject / pgp_mandatory / pgp_by_subject |
pdf / smime / pgp |
Flag (1 / NULL) indicating which protocol is the active one for this recipient |
pdf_mode |
For PDF only: static / random / backtosender |
pdf_password |
AES-encrypted (with /opt/hermes/keys/hermes.key) copy of the static PDF password — for admin re-display only; CipherMail holds its own copy |
smime_mode / pgp_mode |
Reserved for parity; populated identically to encryption_mode for the matching protocol |
Encryption modes
The 6 encryption modes map cleanly onto two axes (protocol × trigger):
| Mode | CipherMail user.encryptMode |
CipherMail user.pdf.encryptionAllowed |
CipherMail user.sMIMEEnabled |
CipherMail user.pgp.enabled |
|---|---|---|---|---|
pdf_mandatory |
mandatory |
true |
false |
false |
pdf_by_subject |
allow |
true |
false |
false |
smime_mandatory |
mandatory |
false |
true |
false |
smime_by_subject |
allow |
false |
true |
false |
pgp_mandatory |
mandatory |
false |
false |
true |
pgp_by_subject |
allow |
false |
false |
true |
"By Subject" requires Encryption Settings > Trigger Encryption by
Subject = Enabled plus the configured keyword (default [encrypt])
in the message subject. See
Encryption Settings — Subject Trigger
for the decision tree.
PDF mode: three sub-policies
PDF encryption is the lowest-friction protocol (recipient needs only a PDF reader and a password — no certs, no keys, no portal account required up front), so it ships with three independent password-distribution sub-modes:
pdf_mode |
How the password reaches the recipient | When to use |
|---|---|---|
random |
CipherMail auto-generates a one-time password per message and pushes it through the Secure Email Portal (https://<console>/web/portal); recipient self-registers on first use |
Default. Best for ad-hoc / first-time external recipients |
static |
Admin sets a fixed password once (minimum 12 chars); recipient must already know it via out-of-band channel | Long-term partners who have agreed on a shared secret |
backtosender |
CipherMail generates a per-message password and emails it back to the original internal sender for them to relay to the recipient | Compliance scenarios where the sender must explicitly hand the password to the recipient (auditable trail) |
For backtosender, two extra fields are configurable per recipient:
| Field | Range | Purpose |
|---|---|---|
| Password Age (minutes) | 15-240 | How long the random password is valid |
| Password Length | 16-bit / 20-bit | Bit-strength of the generated random password |
Bulk vs single create
The Create External Recipient page (view_create_ext_recipient.cfm)
exposes a Single / Bulk toggle:
| Mode | Protocol options | Use case |
|---|---|---|
| Single | PDF, S/MIME, PGP (all three modes available) | One-off precise configuration including S/MIME / PGP recipients that need a cert/key uploaded afterward |
| Bulk | PDF only (Mandatory or By Subject) | Mass-onboard a list of external addresses, one per line; the UI auto-hides S/MIME and PGP because those protocols need per-recipient cert/key material that has no bulk equivalent |
The bulk path validates and skips per-row (invalid format / internal
domain / already-exists rows are reported but do not abort the batch);
session variables bulk_created, bulk_skipped, bulk_failed feed a
partial-success alert on return.
Both paths refuse internal domains. The check is a COUNT(*) FROM domains WHERE domain = <recipient-domain> — if Hermes is the
authoritative MX for that domain, the recipient is a local mailbox or
relay recipient, not an external recipient, and per-mailbox encryption
policy belongs on Email Server > Mailboxes instead.
Auto-Discovered recipients
When CipherMail processes mail to an address it has never seen, it
materializes a cm_users row with the global defaults. These
recipients show up here with Source = Auto and no
external_recipients row backing them. They:
- Use the global Subject Trigger policy (from Encryption Settings)
- Have no per-recipient password mode (PDF random is the CipherMail default)
- Display only the cert / keyring counts CipherMail actually holds
- Cannot be edited from this page (no Admin badge, no action buttons
for cert / PGP / password reset) — managing them means either
promoting them to Admin-Configured by creating an explicit row,
or dropping into CipherMail's own admin UI at
/ciphermail/
The Source dropdown defaults to Admin-Configured on page load — operators most often want to see what they explicitly configured, not the long tail of mail CipherMail has touched.
Per-row actions
The action column varies by what the recipient is configured for:
| Action | Icon | Visible when | What it does |
|---|---|---|---|
| S/MIME Certificates | fa-certificate (green) |
Admin row, smime = 1 |
Links to view_ext_smime_certificates.cfm?email=... for cert add / delete / send |
| PGP Keyrings | fa-key (blue) |
Admin row, pgp = 1 |
Links to view_ext_pgp_keyrings.cfm?email=... for keyring add / delete / publish |
| Reset PDF Password | fa-file-pdf (yellow) |
Admin row, pdf = 1 AND pdf_mode = static |
Opens modal; auto-generates a 16-char mixed-case-alphanumeric password client-side via generatePassword(16); submits to inc/reset_pdf_password.cfm |
| Reset Portal Password | fa-lock (grey) |
Admin row, pdf = 1 AND pdf_mode = random |
Opens modal; same 16-char generator; submits to inc/reset_portal_password.cfm (two-step: encode via --encode-password, then set user.portal.password) |
| Delete Recipient | fa-trash-alt (red) |
Every row | Confirms, then submits to delete_recipient handler |
The Cert Expiry column derives from a batch join of
cm_certificates_email + cm_certificates, picking the earliest
cm_not_after across all certs for that recipient. Color coding:
red bold (already expired), yellow bold (within 30 days), grey muted
(more than 30 days).
Delete cascade
Deleting an external recipient is a multi-table operation handled by
inc/delete_ext_recipient.cfm:
+---------------------------+
| For each row in |
| recipient_certificates |
| where user_id = recipient |
+---------------------------+
|
v
+---------------------------+ +---------------------------+
| inc/delete_smime_ |----->| Removes from |
| certificate.cfm | | cm_certificates_email, |
| | | CipherMail user store, |
| | | on-disk PFX |
+---------------------------+ +---------------------------+
|
v
+---------------------------+
| For each master keyring |
| in recipient_keystores |
+---------------------------+
|
v
+---------------------------+
| inc/delete_pgp_keyring. |
| cfm |
+---------------------------+
|
v
+---------------------------+
| DELETE FROM |
| external_recipients |
| WHERE id = ... |
+---------------------------+
|
v
+----------------------------------------+
| docker exec hermes_ciphermail CLITool |
| --delete-user <email> |
| (cascades all cm_properties, cm_users) |
+----------------------------------------+
On success the page surfaces a callout reminding the operator that any Sender Checks Bypass mapping tied to this recipient must be re-created — that relationship is not auto-cascaded.
Password reset specifics
PDF static password reset (inc/reset_pdf_password.cfm):
- Writes a one-liner
CLITool --set-property user.password --value <newpass> --encrypt --email <recipient>to/opt/hermes/tmp/<token>_reset_pdf_password.sh. chmod +x, executes (240s timeout), deletes.- AES-encrypts the new password with
/opt/hermes/keys/hermes.keyand UPDATEsexternal_recipients.pdf_passwordso the admin re-display path still works.
Portal password reset (inc/reset_portal_password.cfm) is
two-step because CipherMail's portal password is stored as an
encoded value, not the raw string:
- Step 1 — encode: runs
CLITool --encode-password <newpass>, captures stdout to/opt/hermes/tmp/<token>_portal_password, reads that file back into CFML, deletes the temp file. - Step 2 — set: runs
CLITool --set-property user.portal.password --encrypt --email <recipient> --value <encoded>to push the encoded value into CipherMail.
Both modals auto-generate a 16-character mixed-case-alphanumeric password client-side and pre-populate the hidden confirm field; the operator can regenerate or type-in their own. Min length 12 is enforced server-side; the regenerator produces 16.
The modal text explicitly notes that unencrypted voice calls and texts are NOT considered secure for relaying the password to the recipient — operators are expected to use Signal, an in-person exchange, or a separately-encrypted channel.
CipherMail integration: every action is docker exec
Every CipherMail-side mutation on this page uses the same pattern documented across the Hermes admin:
+----------------------+ +----------------------+ +-------------------+
| CFML builds shell |----->| Write to |----->| chmod +x |
| string with N | | /opt/hermes/tmp/ | | |
| docker exec CLITool | | <token>_<purpose>.sh | | |
| lines | | | | |
+----------------------+ +----------------------+ +-------------------+
|
v
+--------------------+
| cfexecute (240s), |
| then delete the |
| temp file |
+--------------------+
|
v
+-------------------------------+
| docker exec hermes_ciphermail |
| /usr/bin/java -cp '/.../lib/*'|
| mitm.application.djigzo.tools |
| .CLITool <args> |
+-------------------------------+
The Hermes app container (hermes_commandbox) holds no JVM and no
CipherMail libraries; everything reaches into hermes_ciphermail over
the docker socket via CLITool. The temp-script pattern (write +
chmod + execute + delete) survives the Lucee cfexecute quirks around
stderr and quoting that would otherwise make a direct inline
invocation unreliable.
What's NOT on this page
| Expectation | Where it actually lives |
|---|---|
| Per-recipient cipher / algorithm selection (AES-128 vs AES-256, RSA / EC) | CipherMail Advanced Settings (/ciphermail/); per-recipient overrides live in cm_properties directly |
| Auto-lookup of recipient PGP keys from a keyserver at send time | Not implemented; see PGP Key Servers — that page is publish-only. Keys must be uploaded manually on the PGP Keyrings sub-page |
| Auto-lookup of recipient S/MIME certs via LDAP / public directory | Not implemented; certs must be uploaded manually on the S/MIME Certificates sub-page, OR minted from an Internal CA row and sent to the recipient |
| Per-recipient subject-trigger keyword override | Not implemented; the keyword is global (one row in encryption_settings) |
| Recipient-side enrollment / self-service for their own keys | The Secure Email Portal handles recipient password registration for PDF-random mode; there is no self-service cert / PGP upload UI |
| Bulk import from CSV with mixed protocols | Bulk path is PDF-only by design (S/MIME / PGP need per-recipient material that doesn't bulk-import cleanly) |
| Sender-side "force encrypt for this thread" UI | Senders use the subject trigger; there is no per-mailbox sender UI |
Container and database touch-points
| Component | Container / path | Role |
|---|---|---|
| Page | config/hermes/var/www/html/admin/2/view_ext_rec_encryption.cfm (hermes_commandbox) |
List, filter, password resets, delete |
| Create page | view_create_ext_recipient.cfm + sub-pages for cert / keyring management |
Single + bulk insertion |
| Action includes | inc/create_ext_recipient.cfm, inc/delete_ext_recipient.cfm, inc/reset_pdf_password.cfm, inc/reset_portal_password.cfm |
One-liner CLITool dispatchers via temp script |
| Admin metadata | external_recipients in hermes DB (hermes_db_server) |
Per-recipient policy choices + AES-encrypted static PDF password copy |
| CipherMail user store | cm_users, cm_properties in djigzo DB |
Authoritative per-recipient state |
| CipherMail cert / key index | cm_certificates_email, cm_certificates, cm_keyring_email in djigzo DB |
Joined batch into smimeLookup / pgpLookup for column rendering |
| Encryption engine | hermes_ciphermail (Java; CipherMail Community 5.x branded djigzo) |
Actual S/MIME / PGP / PDF encryption + portal back-channel |
| AES key | /opt/hermes/keys/hermes.key (hermes_commandbox bind mount) |
Encrypts pdf_password for re-display |
| Secure Email Portal | https://<console.host>/web/portal/ (served by hermes_ciphermail) |
Recipient-facing landing page for PDF random + portal account flows |
Related
- Encryption Settings — global Subject Trigger, PDF reply sender, three shared secrets; the policy mechanism this page applies per recipient
- Internal CA — where the private CAs that can mint S/MIME certs for these recipients live (operator-issued S/MIME chain delivered out-of-band)
- PGP Key Servers — the publish list for the Publish action on the PGP Keyrings sub-page (note: publish-only, not lookup)
- System Certificates — distinct TLS cert store; not related to message-content S/MIME
- Disclaimers — body-mod ordering vs the CipherMail encryption pass (disclaimer is appended before encryption wraps the message)
- Organizational Signatures — same milter ordering applies to signature injection
- ARC Settings — same milter-ordering pattern applied to inbound chain sealing
- Advanced Settings (sidebar link to
/ciphermail/) — CipherMail's own admin UI for everything not surfaced here (per-recipient cipher tuning, custom DLP, directcm_propertiesediting)