Audit-proof e-mail journaling for CommuniGate Pro
drift-journal captures every business e-mail into a write-once, content-addressed archive with encryption, retention, legal hold, tamper-evident integrity, search and audit export — self-contained, with no third-party services.
01 Overview
drift-journal is two small, self-contained executables that mirror all mail passing through CommuniGate Pro into a legally-compliant archive. There are no runtime dependencies: the Linux build is statically linked (runs on glibc 2.28+ — Debian 10, AstraLinux SE 1.7 and newer), and the Windows build links the CRT statically (no VC++ redistributable needed).
What it does
- Captures every message incl. BCC, fail-closed
- WORM store, SHA-256 content-addressed
- Encryption at rest (XChaCha20-Poly1305)
- Retention classes, legal hold, audited purge
- Chained integrity manifests + RFC-3161 timestamps
- Field + full-text search, Z3 audit export
What you get
journald— archival service + admin tooljournal-filter— the CGP capture filter- Hardened installer (locked ACLs, WORM, boot service)
- Auto-generated Verfahrensdokumentation (GoBD)
- Admin web UI on
127.0.0.1:9087 - Legal conformance matrix
02 Architecture
The capture path is fail-closed and the two executables share nothing in-process — the only hand-off is the on-disk spool, so an archive or index hiccup can never cause a message to be lost or delivered uncaptured.
Everything downstream of the spool — the writer, metadata, index, search, retention and maintenance — is
linked into the single journald binary, which also serves the loopback control API
(127.0.0.1:9086) and the admin web UI (127.0.0.1:9087).
03 Requirements
| Platform | Supported | Runtime dependencies |
|---|---|---|
| Linux | Debian 10+, AstraLinux SE 1.7+, RHEL 8+, Ubuntu 20.04+ (glibc 2.28+) | None (static musl) |
| Windows | Windows 10 / 11 / Server 2016+ | None (static CRT) |
CommuniGate Pro is required for live capture. A dedicated, low-privilege service account is created by the
installer (drift-journal on Linux; LOCAL SERVICE on Windows). Allow a few
hundred MB plus your mail volume × retention for the archive.
04 Install — Debian 10 / Linux
Build the package (once)
# static musl binaries (no glibc dependency), then assemble the .deb dist/build-musl.sh dist/build-deb.sh 1.0.0 # -> dist/out/drift-journal_1.0.0_amd64.deb
Install on the server
sudo dpkg -i drift-journal_1.0.0_amd64.deb
The package installs the binaries to /usr/bin, a hardened systemd unit
(drift-journald.service), example configs to /etc/drift-journal/, creates the
drift-journal service account and /var/lib/drift-journal/, then runs
journald init automatically to generate the key system and the
Verfahrensdokumentation, and enables the service.
sudo systemctl start drift-journald
journald status # health, counts, last integrity check
/var/lib/drift-journal/keys/archive.key. Copy it offline,
separately from the archive. Losing it makes the entire archive permanently unreadable.05 Install — Windows
Build the package (once)
powershell -ExecutionPolicy Bypass -File dist\build-win.ps1
# -> dist\out\drift-journal_1.0.0_win64.zip
Install on the server
Unzip on the target, then from an elevated PowerShell:
powershell -ExecutionPolicy Bypass -File install.ps1 # options: # -InstallDir "C:\Program Files\drift-journal" # -DataDir "C:\ProgramData\drift-journal" # -TsaUrl "http://timestamp.digicert.com" # optional RFC-3161 # -NoHardenWorm # skip the delete-deny ACL (not recommended) # -NoService # files + key system only, no boot service
The installer copies the binaries, writes a live config (encryption on), locks down the data store
(ACL inheritance broken; only SYSTEM / Administrators full, LOCAL SERVICE modify), runs
journald init, applies the WORM deny-delete ACL, and registers a
least-privilege boot service — a Scheduled Task drift-journald running as
LOCAL SERVICE, auto-restarted on failure.
…\keys\archive.key. Copy the key offline.
Without it the archive cannot be read.06 The key system
journald init (run automatically by both installers, idempotent) sets up two things:
| Item | File | Purpose |
|---|---|---|
| Encryption key | keys/archive.key | 32-byte XChaCha20-Poly1305 key; seals every record at rest. Generated once, never rotated in place. |
| API tokens | keys/api-tokens.tsv | admin (full) and auditor (read + export only) tokens for the control API and web UI. |
The keys directory is locked to the service account (0700 on Linux; read-only to LOCAL SERVICE on Windows).
Run journald init by hand for a manual setup; an existing key or token file is always kept.
07 WORM hardening
The archive is made append-only so that even an administrator cannot silently delete a record outside an audited purge. The installers apply a baseline automatically; re-assert it after the archive grows.
| Platform | Apply | Temporarily release (for an audited purge) |
|---|---|---|
| Linux | sudo chattr -R +a /var/lib/drift-journal/archive | chattr -R -a … → purge → re-apply |
| Windows | harden-worm.ps1 -Apply | -Release → journald purge --confirm → -Apply |
On Windows the deny ACE blocks delete and rename for everyone while still allowing the service to create new record files. Content-addressing plus the chained manifest mean any out-of-band change is also detectable.
08 Connect capture to CommuniGate Pro
journald is not running CommuniGate Pro will hold mail rather
than deliver it un-journaled. Install and start journald first, then wire up capture.- Copy
journal-filter(.exeon Windows) and its config into the CGPHelpersdirectory. - Set
cgp_settings_dirandfilter_exeinjournald.cfg. - Preview, then apply the registration (adds the ExternalFilter + a Server-Wide Rule):
journald register # preview the changes (no writes) journald register --apply # writes .bak-register-<ts> per file
Restart CommuniGate Pro afterwards. Send a test message and confirm it lands:
journald search subject:test after:2026-01-01
09 Licensing
Both executables are protected by the standard drift-helper key system: an RSA-signed license.key,
string obfuscation, a self-integrity stamp, anti-rollback and a compiled-in beta cutoff.
- Licensed: place a valid
license.key(covering thejournalhelper) next to the executables — no time limit. - No license (eval/beta): full function until 2026-12-31, then the binaries refuse to run (fail-closed: the filter holds mail, the service refuses to start).
- Tamper / clock-rollback: a modified binary or a clock moved backward is refused the same way.
One license covers both journald and journal-filter. Issue one with the key generator:
cd keygenerator
python gen-license-cli.py --issued-to "Acme GmbH" --helpers journal \
--expiry 2028-12-31 --verify
10 Operating
| Task | Command |
|---|---|
| Health / counts | journald status |
| Search | journald search from:acme subject:rechnung after:2026-01-01 |
| Full-text | journald search text:invoice |
| Integrity check | journald verify |
| Rebuild index | journald reindex |
| Classify a record | journald classify <digest> beleg |
| Legal hold | journald hold add CASE-1 "from:party@ext" |
| Retention report | journald retention |
| Audited purge | journald purge → journald purge --confirm |
| Z3 export | journald export /media/usb case:CASE-1 |
| Timestamp manifest | journald timestamp |
| Verfahrensdokumentation | journald docs ./Verfahrensdokumentation.md |
Search fields: from: to: subject: id: case:
after: before: text:/body: plus bare terms. BCC is searchable
in the server-side copy.
Admin web UI
With the service running, open http://127.0.0.1:9087/ and log in with a
token from keys/api-tokens.tsv. Admin-only actions (classify / hold / purge) appear only for the
admin token; the auditor token is read + export. The UI and API are loopback-only.
11 Retention & purge
Retention is counted from the end of the calendar year in which the document arose; the earliest permitted deletion is 1 January of (year + N + 1). Nothing is ever deleted before that date, and deletion after it is operator-initiated and audited — never automatic.
| Class | Period | Applies to |
|---|---|---|
brief (default) | 6 years | Handels- und Geschäftsbriefe (most business e-mail) |
beleg | 8 years | Buchungsbelege / invoices (post-BEG IV, 2025) |
buch | 10 years | Handelsbücher, Inventare, Jahresabschlüsse |
classify only if you want to delete the 6-year bulk earlier.12 Verfahrensdokumentation
GoBD requires a documented procedure (Verfahrensdokumentation). The installers generate a German one
automatically from the live configuration — Linux: /var/lib/drift-journal/Verfahrensdokumentation.md,
Windows: <DataDir>\Verfahrensdokumentation.md. Regenerate any time:
journald docs ./Verfahrensdokumentation.md
13 Integrity & verification
Three independent layers prove immutability:
- Content address — each record is stored under the SHA-256 of its bytes; any change changes the address.
- Chained manifest —
journald verifybuilds and links an append-only manifest; a removed or altered record breaks the chain. The service also re-checks on a schedule. - OS immutability — the WORM ACL /
chattr +a.
Optionally, each manifest is timestamped by a trusted RFC-3161 TSA (only a hash is sent). The release binaries themselves are integrity-stamped; verify any binary:
python3 dist/stamp.py --verify /usr/bin/journald # Linux python dist\stamp.py --verify journald.exe # Windows (stamp.py ships in the zip)
14 Legal compliance
Every control traces to a named legal requirement. A per-message check is available via the admin UI / API; the
full mapping is in legal-conformance.md (shipped alongside this manual).
GoBD principles
- Vollständigkeit (incl. BCC)
- Richtigkeit
- Zeitgerechtheit
- Ordnung (index + search)
- Unveränderbarkeit (WORM)
- Nachvollziehbarkeit
Also covered
- HGB §257 / AO §147 retention
- DSGVO (purpose limitation, access roles, erasure)
- Fernmeldegeheimnis (private mail excluded by scope + policy)
- Datenzugriff Z1–Z3 (search + export with checksums)
- RFC-3161 proof of time
15 Uninstall
Linux: sudo apt-get remove drift-journal (or dpkg -r drift-journal) stops
and removes the service and binaries. The data under /var/lib/drift-journal/ is kept.
Windows:
powershell -ExecutionPolicy Bypass -File uninstall.ps1 # keeps archive + key powershell -ExecutionPolicy Bypass -File uninstall.ps1 -PurgeData # also deletes data (confirms)
16 Troubleshooting
| Symptom | Cause & fix |
|---|---|
| CGP is holding / queuing mail | The filter is fail-closed and journald is not running or the spool is unwritable. Start the service and check journald status. |
protection check failed (tampered) | The binary was modified after stamping. Reinstall from the original package. |
protection check failed (beta-expired) | Eval cutoff reached. Install a valid license.key next to the executables. |
encryption enabled but key unavailable | The key file is missing or unreadable by the service account. Restore the backed-up archive.key. |
| Search returns nothing new | Index lag or a rebuild is needed: journald reindex. Check status for index vs archived counts. |
| Windows service not running after reboot | Check the Scheduled Task drift-journald (Task Scheduler) and that LOCAL SERVICE can read the keys directory. |
README-deploy.md,
legal-conformance.md and, after install, your generated Verfahrensdokumentation.md.