---
title: "Journaling and dual delivery: a compliance copy, and what a journal rule does not see"
url: https://www.hmailserver.co.uk/topic/28271/journaling-and-dual-delivery-a-compliance-copy-and-what-a-journal-rule-does-not-see
author: "Progressiverobot"
published: 2026-09-27T19:53:13.731Z
updated: 2026-09-27T19:53:13.731Z
category: "Rules, Sieve & routing"
tags: ["smtp","security","administration","rest-api","limitations"]
site: hMailServer Community Forum
---

# Journaling and dual delivery: a compliance copy, and what a journal rule does not see

*Written for 6.3.4. hMailServer 6.3.7 (17 October 2026) regroups the Control Panel's sidebar: the *Journaling and dual delivery* page is *Journaling & dual delivery*, in the new **Compliance** group rather than under *Monitoring & troubleshooting* as the table below has it, and Ctrl+K takes the old title to it. See [the 6.3.7 announcement](/topic/28277).*

A journal rule sends a copy of a domain's mail, or of one account's, to an address. Exchange calls it *journaling* and sends an envelope report with the message attached; Google calls it *dual delivery* and sends the message itself. In hMailServer they are one mechanism with two shapes of copy, new in 6.3.4 (27 September 2026, schema 6104; the rules themselves arrived with schema 6075). This topic covers what a rule says, the two shapes, when a rule is evaluated, how it avoids looping, where it is edited, and what it deliberately does not do. The one thing to know first: **a journal rule covers mail that comes in through the SMTP session, a fetch account or the send API, and nothing this server queues itself.** A compliance archive built on a journal rule therefore has known gaps, and they are listed exactly below.

Nothing is journalled until a rule exists. With an empty table the whole of this costs one comparison per accepted message.

### What a rule says

| Field | Meaning |
|---|---|
| `scope` | `domain` or `account`: whose mail the rule is about |
| `target` | The domain name, or the account address. It must be a domain this server hosts, or an account in one |
| `direction` | `inbound` (a message with a local recipient the rule covers), `outbound` (a message from an authenticated sender it covers), or `both` |
| `journal_address` | Where the copy goes. One address, local or anywhere else, checked as an address before the row is written. It may be a mailbox in the very domain the rule journals; it may not be the account an account rule names |
| `mode` | `report` or `copy` |
| `enabled` | A rule that is off copies nothing and keeps everything it said |
| `note` | Why the rule exists. At most 255 characters |

The rules are stored in `hm_journalrules`. The row holds the domain id and, for an account rule, the account id, so nothing breaks when a domain or an account is renamed under it. A domain's rules go with the domain when it is deleted, and a rule naming an account goes with the account.

**A rule has no conditions.** It covers all of a domain's or an account's mail in the direction it names. There is no "only messages over 10 MB" and no "only from these senders"; a mail flow rule is the place for that.

### The two shapes

**`report`** is the Exchange journal shape: a `multipart/mixed` message whose first part is the envelope report and whose second part is the original message attached as `message/rfc822`, written through byte for byte. The report is four fixed fields, one per line, in one order:

```text
Sender: alice@example.com
Subject: Quarterly figures
Message-Id: <abc@example.com>
Recipient: bob@example.test
Recipient: carol@example.test
```

The envelope is the point. A message's `To` and `Cc` headers are what the sender typed; the `Recipient:` lines are where the message actually went, blind copies and expanded lists included. A message with no envelope sender - a bounce - writes `Sender: <>`.

**`copy`** is the message itself, as it was accepted, with one header line (`X-hMailServer-Journal`) in front of it and nothing else changed: not the body, not its transfer encoding, not its signature. The file is written byte for byte rather than through the MIME writer.

What a migration or a second mail system wants is the mail, not a report about it; that is what `copy` is for.

Both shapes are queued through the ordinary delivery queue, so a journal address somewhere else is delivered, retried and bounced as any other mail is, and a local one lands in a mailbox.

**The envelope sender of a journal message is the journalled domain's postmaster**, not the person who wrote the message. A journal address that stops answering is the administrator's problem, and bouncing it to the sender would tell somebody outside this server that their mail is being copied.

### When a rule is evaluated

**Once per accepted message**, in the SMTP session, after the row is saved and the message file is settled - the same place, and the same recipient list, the on-disk archive decision is made from. Not at delivery: the deliverer runs again for every retry and for every route a message splits into, and a compliance archive that gained a second copy because a remote host was busy would be worse than useless.

**One report or copy per matching rule per message, not per recipient.** A domain rule that matches a message addressed to three accounts of the domain sends one report, with all three on it.

There are three points of entry where rules are evaluated:

1. **The SMTP session.** The outbound half judges an authenticated sender; this is what a desktop or phone client uses.
2. **A fetch account.** Mail a mailbox collects from another provider by POP3 or IMAP is journalled once per message, at the same point in that path. A download has no authenticated sender, so it is the **inbound** half of a rule that speaks for it, and it has no SMTP session, so its lines go to the application log.
3. **The send API**, `POST /api/v1/send`, which is new in 6.3.4 and is journalled in it (CHANGELOG lists the fix in its 6.3.4 section). The key's sender counts as the sender an outbound rule judges when that sender is an account here.

### What a journal rule does not see

A message the server queues itself is queued past all three points and is **not journalled in either direction**, even when it is addressed to a mailbox a rule covers:

| Not journalled | Note |
|---|---|
| A message the webmail queues directly | `POST /api/v1/me/messages` |
| A send-later draft | |
| A message written by an event script | |
| A mail flow rule's forward, copy or reply | |
| An automatic reply | |
| A Sieve redirect | |
| An account's forwarding | |
| A delivery report | |

Two consequences follow, and both matter to anyone relying on this for compliance:

- **The outbound half covers SMTP submission and the send API, and not the browser.** Mail a person sends from the webmail is not journalled by an outbound rule.
- **The inbound half covers mail from outside this server, and not what the server sends on to a mailbox of its own.**

Two further limits of the same kind:

- **A message that was refused is not journalled.** A message rejected at `RCPT TO` or by a rule never existed as far as this is concerned.
- **A message held for review is journalled**, because it was accepted.

### Not looping

There is one guard, and it takes nobody's word for anything. Every journal message this server writes carries `X-hMailServer-Journal`, and its value is the rule's id, a full stop, and a **mark**: 128 random bits the server process chose when it first needed one. The evaluation reads that header before it does anything else and stops only when the mark is this process's own. A journal message that comes back into this server - relayed by the far end, forwarded by the journal mailbox's owner - is never journalled again, and the guard stops the copy, not the delivery: the mailbox still gets its mail.

**A message carrying the header with any other value is journalled exactly as it would have been without it**, and the attempt is noted in the log. The header's name is in the published document, so a guard that honoured the header as written would let anybody under an outbound compliance rule keep a message out of the archive by typing one line into their mail client. An archive that the people it is about can opt out of is not an archive.

Three properties of the mark to understand:

- **It is per process, and nothing is stored for it.** A restart forgets the marks it wrote, so one of this server's own reports that returns after a restart is journalled once more, and the report that makes carries a mark the running process does know, so it stops there. A loop cannot run away; the price of keeping no secret on disk is one duplicate.
- **It is not a signature over the message.** It travels in the messages it marks, so whoever can read this server's journal mail - the journal address itself, and anything relaying to it - could quote a mark back in a message of their own and keep that one message out of the archive. If that relay is a worry, journal to a local mailbox, or over an authenticated route.
- **There is no second guard**, and no rule is skipped for where its journal address points.

**Journalling a domain into a mailbox of that domain works.** `example.com` to `archive@example.com` sends one report per message into that mailbox and nothing cascades, because a journal message is written straight into the delivery queue and never passes an evaluation point. It used to be refused at run time, silently: the rule matched, the copy was dropped with a line in the log, and every surface went on showing the rule as enabled. That guard went.

**The one arrangement refused is an account journalled into itself**, and it is refused when the rule is written rather than found out later: it would put every message in the mailbox twice and archive nothing anywhere.

### Where it is edited

Journal rules are the server administrator's. A rule says that somebody's mail is copied to an address outside their sight, so a credential confined to named domains is refused even though a rule names one.

| Surface | How | Platform |
|---|---|---|
| REST | `GET/POST /api/v1/journal-rules`, `PUT/DELETE /api/v1/journal-rules/{id}` | Windows and Linux |
| The Control Deck | The *Journaling* view: every rule, a switch on each row, and a form to add one | Windows and Linux |
| The Control Panel | The *Journaling and dual delivery* page, under **Monitoring & troubleshooting** (since 6.3.7 *Journaling & dual delivery*, in the **Compliance** group; Ctrl+K takes the old title to it), over three `Utilities` methods (`GetJournalRules`, `SaveJournalRule`, `DeleteJournalRule`) | Windows only |
| hmctl | `hmctl journalrule list\|create\|update\|delete` | A client over the REST API |
| PowerShell module | `Get-HmJournalRule`, `New-HmJournalRule`, `Set-HmJournalRule`, `Remove-HmJournalRule` | Over the REST API |

Over REST, reading needs `view_configuration` and writing needs `manage_server`, so the auditor role reads the rules and writes none. Every surface goes through one function on the server, `Journaling::SaveRule`, so the three small vocabularies (`domain`/`account`, `inbound`/`outbound`/`both`, `report`/`copy`) mean the same thing whichever you use, and a refusal reads the same sentence. Writing, changing or removing a rule is on [the audit trail](/topic/28273).

**Setting up a compliance copy for one domain:**

1. Decide the journal address. A mailbox in the same domain, such as `archive@example.com`, is allowed; an address elsewhere is delivered through the ordinary queue.
2. Create one rule with `scope` `domain`, `target` the domain name, `direction` `both`, `mode` `report` and the `journal_address`.
3. Over SMTP - from a mail client signed in to an account, or from outside, and not from the webmail - send one message to two recipients of the domain, one of them as a blind copy, and open what arrives at the journal address: one report for the message, with a `Recipient:` line for each envelope recipient.
4. Then send one message from the webmail and confirm that it is **not** journalled, so that whoever owns the archive knows the gap is there.

A rule is refused before it is written for an unhosted target, a word outside the three vocabularies, no journal address, one that is not an address, one carrying a line break, and an account journalled into itself.

### Journaling beside three other features

- **Sealed mail.** The journal keeps the bytes verbatim, so a message that was encrypted end to end still decrypts from the copy - but nobody reviewing the journal can read its content.
- **Restricted delivery.** A journal rule's copy of a restricted account's mail goes where the rule says, because the copy is the administrator's and not the account's forward.
- **Rules and Sieve.** A rule's forward, copy or reply and a Sieve redirect are among the messages the server queues itself, so they are not journalled.

### What it does not do

- **It is not the archive.** `ArchiveDir` and `ArchiveDomains` keep files on this server; a journal rule sends mail elsewhere. Neither replaces the other, and turning one on does not turn the other off.
- **It is not eDiscovery.** A case searches the mailboxes, their deleted items and the archive on this server, and a journal copy has left it. A journal address that is a mailbox here is searched like any other custodian.
- **It is not an account's forwarding.** A forward is the account holder's, and can deliver *instead of* the mailbox. A journal copy is the administrator's, and never changes what the mailbox gets.
- **It is not a mail flow rule's forward or copy action.** Those have conditions, run per delivery attempt, and can be edited by the account.
- **It is not retention.** Once a journal message leaves the queue it belongs to whatever receives it. This server keeps no second copy, expires nothing and withdraws nothing; deleting a rule stops the next copy and does not reach the ones already sent.
- **It is not encryption or redaction.** A journal address outside your control sees everything the recipient sees.

### Where to read more

- The document at the 6.3.4 tag: [Journaling.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Journaling.md), with [Discovery.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Discovery.md), [SendApi.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SendApi.md), [SealedMail.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SealedMail.md) and [RestrictedDelivery.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/RestrictedDelivery.md)
- Wiki: [Routes and Relays](https://gitlab.com/hmailserver/hmailserver/-/wikis/Routes-and-Relays) (section 14.2d) and [Compliance and eDiscovery](https://gitlab.com/hmailserver/hmailserver/-/wikis/Compliance-and-eDiscovery)
- On this forum: the [6.3.4 release announcement](/topic/28218); [Rules and Sieve](/topic/28268) for the forwards and redirects a journal rule does not see; [restricted delivery](/topic/28269); [the send API](/topic/28266); [compliance, eDiscovery and legal holds](/topic/28242); and [the archive](/topic/28236)
