Skip to content

Compliance and eDiscovery

Christopher Holloway edited this page Oct 18, 2026 · 6 revisions

Compliance and eDiscovery

Written on 26 September 2026 from hmailserver/docs/Discovery.md and PersonalData.md in 6.3.4 (27 September 2026, schema 6104). Everything on this page is new in 6.3.4: eDiscovery is schema 6097, and the erasure's reach into the stores beside the mailbox has no schema step. Nothing here does anything until somebody opens a case or asks for an erasure. 6.3.5 (29 September 2026, schema 6105), 6.3.6 (6 October 2026, schema 6108) and 6.3.7, the latest release (17 October 2026, schema 6108), are not yet reflected here.

A lawyer, a regulator or an internal investigation asks for the mail of some people over some months that mentions some words: found, kept from deletion while the matter lasts, and handed over in a form another tool can read, with a record of who looked for what. That is what Google Vault and Microsoft Purview do, and what this server does with cases, searches, holds and exports. The other half of the same subject is a person's own request - give me my data, forget me - and a legal hold outranks the second.

flowchart LR
    C["A case<br/>name, description"] --> S["Searches<br/>custodians, dates,<br/>words, attachment"]
    S --> R["Hits, listed -<br/>nothing copied"]
    R --> E["An export<br/>EML + mbox + SHA-256s"]
    C --> H["Holds<br/>an account, a domain,<br/>or archived copies"]
    H -->|"stops"| D["Deletes, purges,<br/>retention, moves,<br/>restores that replace"]
    C -->|"closed"| X["Every hold released"]
    A["The audit trail"] -.->|"records every search,<br/>hold, export, download"| C
Loading

Contents

Where it is

Where What
The Control Deck, Discovery view The whole of it: cases, the search form, holds with Lift the hold, exports with Download, Open and Close (The Control Deck)
REST, /api/v1/discovery/cases The same ten routes the Deck calls, in the OpenAPI document (The REST API)
The Control Panel, Monitoring & troubleshooting → Discovery Read-only: the cases and their lines of the audit trail, read over the REST listener, since discovery has no COM interface. It changes nothing (The Control Panel §5.3a)
COM, hmctl Neither: there is no archive group in either to put it beside

The archive has had a legal hold of its own since before 6.3.4 (/api/v1/archive/{id}/hold, and since 6.3.4 the Deck's Archive view), which keeps one archived copy from the archive's retention sweep. A case is the rest: it finds the mail across every mailbox and the archive, holds whole mailboxes against every way this server deletes a message in them, and exports what it found. Journaling - a copy of a domain's or an account's mail sent elsewhere as it passes - is a different tool again (Routes and Relays §14.2d).

Cases

A case has a name, a description, who opened it and when, and a state: open or closed. Its searches, holds and exports belong to it. Closing a case releases every hold it placed, in the same request; a closed case runs no search, places no hold and makes no export, and its searches, holds and exports stay readable and its exports downloadable. A case is never deleted: the record of what was looked for is the point, and an address's erasure does not reach it either.

The files a case makes are under the data directory, in Discovery\case-<id>\: a results file per search and a zip per export. They are not in the backup, and neither are the four tables that name them: a server rebuilt from a backup has no cases, no exports and no holds, so place the holds again before its mail can be deleted. Nothing removes an export; delete its zip from the case's folder by hand when it is no longer needed.

Searching

A search takes:

Part What it means
Custodians (required, at most 100) An address - one account and its archived copies - a domain - every account in it and the archive's copies filed under it - or *, every account and the whole archive
From and to (optional) The day a message is dated, by its Date field read as UTC, else the day the server stored it
Words (optional) Every word must be found, "a quoted phrase" as one - a case-insensitive match in what IMAP SEARCH TEXT reads: the decoded header, the text and HTML bodies, and text/* attachments. A search and an IMAP search of the same mailbox for the same word find the same messages
Attachment (optional) A file name containing a text, a media type or a family of them (image/), and a least size
Scope The mailboxes, the archive, or both (the default)

For each custodian account it looks in three places: its folders, its deleted items (what a hold kept, and what RecoverDeletedDays keeps), and the archive. A search copies nothing: it lists each hit - where it is, its Message-ID, date, From, To, subject, size and attachments, and whether it is held now - and, separately, everything it could not read, with the reason, never dropping it as a non-match. Searches and exports are queued and run one at a time, on this server's maintenance queue; two servers sharing one database should run discovery on one of them.

What a search cannot see, said plainly:

  • 100,000 hits a search; it stops and says truncated. Narrow it and run it again.
  • Text inside a PDF, a Word document or a spreadsheet: only text/* attachments are read, up to 1 MB of characters a message. The attachment criterion still finds the PDF by name, type and size.
  • More than 80 MB a message is not read for its body; it is listed as not searched when the query asks about the body, and never dropped as a non-match.
  • Zero-access mailboxes and messages encrypted end to end are matched on their headers alone and marked so (Encryption for People).
  • The confidential and password-protected stores beside the mailbox, and archived copies of bounces that have no index row.

The full-text index (IndexerFullText, off by default) is a filter, never the answer: when it is on, a message it proves cannot contain a word is skipped, exactly as IMAP SEARCH skips it; every other message is read.

Holds

A hold is placed on an account (every message of it, and its archived copies), a domain (every account in it), or the archived copies a search matched. It lasts until it is lifted or the case is closed, and what it keeps is kept by any live hold of any case.

What a hold stops - every way this server deletes a message that is in a mailbox, found by reading the code:

Path Under a hold
The person's delete - IMAP EXPUNGE, POP3 DELE, the webmail's delete and empty folder, a folder deleted, APPEND with REPLACE, a recall - and an administrator's delete of a message The message leaves the folder and a copy is kept among the account's deleted items, whatever RecoverDeletedDays says (0 by default); if the copy cannot be made the delete is refused
An IMAP MOVE out of the account, into a public folder or another account's shared folder The original is kept among the held account's deleted items
Deleting every message of the account at once Refused
The account's own filters at delivery - a rule's Delete or Reject, a Sieve discard, reject or redirect, a forward that keeps no copy, the account's spam delete threshold The copy is kept among the deleted items instead of dropped; if it cannot be kept, the message is delivered (Rules and Sieve §12.1b)
The message retention policy, the recover-deleted sweep, deleting a deleted item for good Nothing of the held account is deleted
A mailbox restore that replaces, a full restore with the domains option Refused while the account or domain is held
Moving the account to another domain, removing its mailbox key (Message Storage), deleting the account or its domain Refused, with a sentence, over REST, SCIM and COM alike
The archive's retention sweep and an address's erasure A held archived copy keeps its file and its index row

What a hold does not reach: the server's and a domain's rules, which decide before a message is split between its recipients; spam and viruses refused or deleted before a message is the account's, and what the quarantine discards; the delivery queue; the confidential and password-protected stores; archived bounces with no index row; the full-text index and the message trace. A message moved to another folder of the same account is still in the mailbox, and is not copied.

The hold is not hidden from the custodian. The account's own recover-deleted listing says it is held and lists what the hold kept, and the webmail's Recover deleted items page offers no delete for good. Vault and Purview keep a hold silent; this server does not hide items from their owner.

It fails closed. When the holds cannot be read, every question is answered held, and the error log says so once a minute (HM7332): a hold that could not be read is not a hold that was released. A second server reading the same database learns of a new hold within five seconds.

Exports

An export of a finished search is a zip, downloaded from the case once it is done:

alice@example.com/INBOX/1-CAB12345@mail.example.com.eml
alice@example.com/INBOX.mbox
alice@example.com/Recoverable Items/INBOX/3-<message id>.eml
alice@example.com/Archive/Received/9-<message id>.eml
SHA256SUMS
manifest.json
  • Each hit as EML, the message's own bytes as the server stores them, byte for byte - and an mbox per folder beside them, in parts of at most 256 MB.
  • SHA256SUMS, so that sha256sum -c SHA256SUMS checks the unpacked export, and manifest.json: the case, the search as it was run, who ran it and who asked for the export, every item with its path, SHA-256 and size, and every item withheld, with the reason.
  • The zip's own SHA-256 is kept with the export and shown on the case, so the download can be checked against what the server wrote.
  • Withheld: a zero-access mailbox's messages (its administrative export is refused by design), and anything deleted, moved or swept since the search ran - search again to find it.
  • Limits: the zip format written is 32-bit, so no export past 4 GB (narrow the search and export each part); a download past 500 MB answers with where the zip is on the server's disk; and an export that would leave the data directory's volume with less than a gigabyte free is refused.

Why EML and mbox, and not PST. Writing PST correctly is a project of its own. Every review tool reads EML, and an examiner who needs PST converts the EML files with a third-party converter; the manifest's SHA-256s show that what was converted is what this server exported. (Since 6.3.4 the server reads PST, for migration - Migration - but it does not write it.)

Who may

Two permissions of the administrator roles (Sign-in and Identity §7):

Permission Allows Held by
view_discovery Reading the cases, their searches and hits, holds and exports The server administrator and the auditor
manage_discovery Opening and closing a case, running a search, placing and lifting a hold, making an export The server administrator only; a custom role may be given it

Downloading an export needs view_mailbox as well - it hands over the mail itself, including the custodians' deleted items, which no other administrative route hands over. A search that reads the archive needs view_archive as well. A credential confined to named domains names no custodian outside them, never *, and sees a case that reaches outside them as one that is not there. A domain administrator is not shown the Deck's view.

Every search - with its whole query - every hold placed and lifted, every export asked for and every download, with its SHA-256, is on the audit trail under whoever did it, while the trail is on (audit_trail_enabled, on by default; switching it off is itself recorded). The case keeps its own record of searches, holds and exports whatever the switch says; a download is recorded on the trail alone.

A person's data: the export and the erasure

The two halves of a data-subject request, as the server does them:

  • The export: one .zip of everything the server holds for an account - its mail as mbox, what the backup carries for it, the address book as vCard, the calendars, the webmail's preferences, the active Sieve script, and the log lines that name it. The person downloads it from the webmail's Your data card; the administrator from the Deck's account row, REST (/api/v1/accounts/{address}/export), hmctl privacy export or COM.
  • The erasure - POST /api/v1/addresses/erase, hmctl privacy erase, the Deck's Erase traces or the Control Panel's account dialog - removes what deleting an account deliberately leaves: quarantine entries, trace rows, greylisting, list memberships, aliases, other people's address-book entries and first-contact memory, the archive tree and, when asked, the log lines naming the address. Since 6.3.4 it also reaches the stores beside the mailbox: messages held for review that the address sent or would receive are discarded; it is taken out of the recipients of a message that expires, and one it sent is ended; password-protected messages to it are ended; and the recipients an account remembers writing to, the out-of-office memory, the booking policies of rooms and equipment (Rooms and Equipment) and correspondents' S/MIME certificates and OpenPGP keys lose it. Every list of addresses is matched whole, so erasing ann@ never touches joann@.

A legal hold outranks the erasure. An erasure never deletes a message from a mailbox, so a hold meets it only in the stores it deletes from, and there the hold wins: a held archived copy keeps its file and row, and a held account's messages held for review, messages that expire and password-protected messages are left as they are. The REST answer counts them as held beside removed, the log and the Deck say so, and the erasure is run again once the case is closed. eDiscovery's own record is not erased: a case's queries, holds and exports name the address, and keeping that record is what a case is for.

What an erasure leaves, by design: mail in mailboxes (the person's own and everybody else's), other people's calendars, configuration somebody made that names the address (a forward, a rule, a list's moderator), the hash-chained audit trail, which ages out by audit_retention_days, and backups. The full list, with each reason, is in PersonalData.md.

What it does not do

  • No PST export, and no text read from attachments but text/* - a word only inside a PDF is not found.
  • No sender or recipient criterion: words are looked for in the whole decoded header, and people are named as custodians.
  • No silent hold: the custodian can see what a hold kept.
  • No hold on the server's or a domain's rules, on spam and virus deletion, on the queue, or on the confidential and password-protected stores - and none on held archived copies against a full restore when ArchiveDir has been put inside the data directory; keep the archive outside it.
  • No export past 4 GB, and no download past 500 MB; nothing removes an export.
  • One search or export at a time, on one server; no saved queries, review tags, redaction or de-duplication across custodians - a message sent to two custodians is two hits.
  • Cases are not in the backup: a restored server has no holds until they are placed again.

The full documents are Discovery.md, with every route, field, refusal and what proves each claim, and PersonalData.md.

See also: The Control Deck · Sign-in and Identity · Rules and Sieve · Message Storage · Backup and Restore · Security Hardening

Home — full index

Start here

1. Install and run

2. Secure it

3. Operate it

4. Extend it

5. Contribute to it

Look it up — from any journey


About this wiki

Clone this wiki locally