---
title: "Quotas, retention and the archive: how much mail the server keeps, and for how long"
url: https://www.hmailserver.co.uk/topic/28236/quotas-retention-and-the-archive-how-much-mail-the-server-keeps-and-for-how-long
author: "Progressiverobot"
published: 2026-09-27T19:18:13.731Z
updated: 2026-09-27T19:18:13.731Z
category: "Guides & tutorials"
tags: ["administration","smtp","imap","webmail","rest-api"]
site: hMailServer Community Forum
---

# Quotas, retention and the archive: how much mail the server keeps, and for how long

This topic covers how much mail an hMailServer 6.3.4 server keeps, and for how long: each mailbox's **limit** and the quota policy 6.3.4 adds around it; **retention**, the administrator's and each person's, with the recover-deleted window; and the **server archive** under `ArchiveDir`. The one thing to know first: **out of the box nothing is deleted because of its age.** A new account has no limit (`0`), no domain has a retention policy, the archive is off, and once it is on, `ArchiveRetentionDays` is `0`, keep for ever.

### The limit, and what a full mailbox does

**Where it is set.** `Account.MaxSize` over COM, `max_size_mb` over REST, in megabytes; `0`, the default, is no limit. Set it on the Control Panel's account dialog (**Quota (MB)**, Windows only), in the Control Deck's account editor, with `PUT /api/v1/accounts/{address}`, in the `max_size_mb` column of an accounts CSV file (since 6.3.4), or with `Set-HmAccount`. A domain's **Limits** tab caps the whole domain (**Maximum domain size (MB)**, `0` for none; once reached, delivery to its mailboxes fails) and sets the quota new accounts get.

**What counts** is each message's original size: compression does not lower it, and a mailbox encrypted at rest keeps its sizes in the clear. The kept copy of a message the person sent to expire counts against their quota (413, `quota_exceeded`).

| Where | At the limit |
|---|---|
| SMTP `RCPT TO`, with `RejectFullMailboxAtRcpt` on (`1`, the default) | A mailbox already at or over its limit, and past any grace (below), is answered `452 4.2.2 Mailbox is full.`, a temporary refusal: the sending server keeps the message and retries |
| A message that arrives while there is room and would overflow it | Caught at delivery, with a non-delivery report to the envelope sender |
| `RejectFullMailboxAtRcpt` at `0` | Accepted, then bounced to an envelope sender that spam has usually forged. Leave it on |
| IMAP `APPEND`, `COPY`, `MOVE` | Refused with `[OVERQUOTA]`, so a client can tell a full mailbox from any other refusal |
| `APPEND` into a folder shared from another mailbox | Since 6.3.4 charged to the folder's owner, as `COPY` and `MOVE` were |
| A migration or PST import job | Pauses with *the account has no room left under its quota* and tries again; raise the limit or make room |

**A full disk is a different refusal.** Below `MinimumFreeDiskSpaceMB` (100) free on the store's volume, `MAIL FROM` gets `452 4.3.1 Insufficient system storage.` and IMAP `APPEND` `NO [UNAVAILABLE]`, for every mailbox. The log warns first, below `DiskSpaceWarningThresholdMB` (1024).

**What the person sees.**

- The webmail's **Storage** page (since 6.3.2, `GET /api/v1/me/storage`): the share used, every folder by size, the twenty largest messages, and a clean-up of Trash and Junk older than 7, 30 or 90 days, or everything. See [The webmail portal at /portal](/topic/28191).
- `GET /api/v1/me` answers `quota` with `limit_mb` and `used_bytes`.
- **IMAP QUOTA**: `QUOTA=RES-STORAGE` is advertised; `GETQUOTA` and `GETQUOTAROOT` answer one unnamed root for the whole account with `STORAGE` in kilobytes, and an empty list for an account with no limit. `SETQUOTA` is refused: the limit is the administrator's. It is on as seeded; the switch is `imap_quota_enabled` on `PUT /api/v1/settings` (`IMAPQuotaEnabled` over COM).

### The quota policy (since 6.3.4)

The server-wide warning at `QuotaWarningPercent` (90) has been there since 6.2.22-pre6. Schema 6061 adds a domain's own warning share and words, a grace and a send-stop, each a share of the limit; schema 6088 lets an account hold its own shares too.

| Point | Server | Domain (`-1` takes the server's) | What it does |
|---|---|---|---|
| Warning | `QuotaWarningPercent`, 90; `0` warns nobody | `quota_warning_percent` | The delivery that takes the mailbox across this share sends its owner one notice, from the domain's postmaster |
| Grace | `quota_grace_percent`, 0 | `quota_grace_percent` | How far past the limit mail from other people is still accepted |
| Send-stop | `quota_send_stop_percent`, 0 (never) | `quota_send_stop_percent` | The share at which the account can no longer send |

Over COM (Windows): `Settings.QuotaGracePercent`, `Settings.QuotaSendStopPercent`, and `Domain` properties of the same names.

**The warning is sent once**, by the delivery that crosses the threshold; nothing records who was warned, so a mailbox emptied and filled again is warned again.

**The grace is for mail other people send**, the mail otherwise lost. A 1 GB mailbox with a 50% grace receives until it holds 1.5 GB; the owner's own saves and copies over IMAP and the webmail stop at the limit.

**The send-stop refuses the account, not the address.** An authenticated `MAIL FROM` is refused with `552 5.2.2` and the reason, keyed on the account that signed in, even under a domain alias, not the address it named. Receiving goes on. [QuotaPolicy.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/QuotaPolicy.md) describes the refusal at SMTP only; test the webmail's send and the send API before relying on it there.

**The words** are the `QUOTA_WARNING_SUBJECT` and `QUOTA_WARNING` server messages, with `%MACRO_MAILBOX%`, `%MACRO_QUOTA%` (MB), `%MACRO_USED%` (MB) and `%MACRO_PERCENT%`. Server messages are server-wide (the Control Panel's **Mail flow & delivery → Server messages**, the Deck's Server messages page, `PUT /api/v1/settings/messages/{name}`). A domain sets its own subject and text, `quota_warning_subject` and `quota_warning_text` (`Domain.QuotaWarningSubject`, `Domain.QuotaWarningText`), so its people can be warned in their own language. They are chosen apart: a domain that sets only the text keeps the server's subject.

**Levels.** The warning is a *preference*: the deepest level holding a value wins. The grace and the send-stop are *limits*: an account's own value may tighten its domain's, never loosen it, while a domain's may sit either side of the server's. The domain's shares are the domain administrator's, and a domain-restricted key's. `<name>_effective` and `<name>_source` on the domain and account routes say why a mailbox was not warned. An account's own quota shares are tested only through what the routes answer, and only on Windows. The mechanism is in [Settings: what is not stock, when a save takes effect, and the three levels](/topic/28222).

**Where it is set.**

- **Control Deck:** the domain editor's *Quota policy* group; the server's grace and send-stop on the Settings page.
- **Control Panel** (Windows): the domain dialog's **Limits** tab; the server's grace and send-stop under SMTP, beside `QuotaWarningPercent`.
- **REST:** the five domain keys on `PUT /api/v1/domains/{name}`; the server's two on `PUT /api/v1/settings`.
- **An account's own value:** the **Levels** tab of the Control Panel's account dialog, the Deck account editor's levels card, `hmctl account set`, or `PUT /api/v1/accounts/{address}`.

`QuotaWarningPercent` is a key of the settings store: set it in the Control Panel (Windows) on **Connections & protocols → Protocols**, SMTP tab, with `PUT /api/v1/settings/ini/{name}` or `hmctl ini set`, which answer to the administrator password alone (never an API key, nor an account holding a role), or, on the Linux server itself, `hmailserver --set-setting`, not by editing `hMailServer.ini`. Every value here applies when it is saved through the server, at every level; [when a save takes effect](/topic/28222) explains why a line edited in the file does not hold and when a value stored by `--set-setting` arrives. A backup taken before the policy restores with the server's values.

One domain warned at 85%, stopped from sending at the limit and given a 20% grace, with a German subject; then one account put back on its domain's grace:

```bash
hmctl domain set example.com quota_warning_percent 85
hmctl domain set example.com quota_send_stop_percent 100
hmctl domain set example.com quota_grace_percent 20
hmctl domain update example.com --set "quota_warning_subject=Ihr Postfach ist fast voll"
hmctl account set ann@example.com quota_grace_percent inherit
```

### Who is nearest their limit

`GET /api/v1/reports/quota` lists every mailbox with a limit and at least one message, nearest first: `megabytes`, `quota_megabytes`, `percent`, the `warns_at` and `sending_stops_at` points that apply to it, and a `state` of `ok`, `warned`, `stopped` or `over`. It takes `domain`, `top` (1 to 200, default 10) and `format=csv`, and ignores the date window. Its test has run on the Windows bench only. The Deck's **Reports** page shows it as *Near their quota*. Other than the administrator password, a credential needs `view_configuration` and `view_message_trace`. The sections `hmctl report` documents do not include it:

```bash
curl -u Administrator:your-admin-password \
  "http://127.0.0.1:8443/api/v1/reports/quota?domain=example.com&top=200&format=csv"
```

Nothing records what a mailbox held yesterday; per-domain storage by day is the `storage` section, in [Alerts, the audit trail, syslog and the journal, and per-domain reports](/topic/28273).

### Retention: the administrator's policy

`MessageRetentionDays` (since 6.2.25) is set per domain and per account; there is no server value. On a domain, `0` (the default) is no policy and a number deletes mail older than that many days. On an account, `0` (the default) takes the domain's, a number is its own, and `-1` keeps for ever whatever the domain says.

**The sweep.** `MailboxRetentionTask` runs at start-up and every six hours. It removes delivered messages whose stored creation time is older than the window, in **every folder** of the mailbox, through the same deletion an IMAP `EXPUNGE` performs, so the quota, the QRESYNC record and an open IMAP session all see it. `Utilities.RunMessageRetention` (COM, Windows only) runs it now and returns the count; no document names a REST route or `hmctl` verb for it, so on Linux wait for the next pass.

**Where it is set.** On Windows the Control Panel's domain dialog (**Limits** tab) and account dialog (General tab); the Deck's domain and account editors; `message_retention_days` on the domain and account routes; `hmctl domain set` and `hmctl account set`. **A domain's retention is the server administrator's** (COM refuses a domain administrator, the domain route a restricted key); an account's own value is the domain administrator's, so an account's `-1` can switch its domain's policy off for that account.

**Before switching a domain's policy on:**

1. Give `-1` to each account that must keep everything.
2. Decide whether what the sweep removes should be recoverable for a while (`recover_deleted_days`, below). Off, a removed message is gone.
3. Set the domain's days. It applies when saved; the next pass, at most six hours away, deletes.

**Easy to miss.** A **restore** of the built-in backup does not carry a domain's retention days: the wiki's Backup and Restore page lists them as silently reset to `0`, so re-enter them. And a **legal hold** outranks retention: a held account is left alone by the policy and by the person's own choices, and the log says so.

### Each person's own retention (since 6.3.4)

Three choices on the webmail's preferences page: *Empty Trash after*, *Empty Junk after* and *Archive Inbox mail after*, each never, 7, 30 or 90 days, or a year, kept as `retention_trash_days`, `retention_junk_days` and `retention_archive_days`. The same six-hourly sweep runs them after the administrator's policy. Trash and Junk (the folders designated `\Trash` and `\Junk`, or named as clients name them) are emptied of what is older; Inbox mail older than the choice is copied to the `\Archive` folder, made as a top-level `Archive` if there is none, then removed from the Inbox. That is a folder of the person's mailbox, not the server archive. A value that is not a whole number of days from 1 to 3650 is off.

### The recover-deleted window (since 6.3.4)

`recover_deleted_days` on `PUT /api/v1/settings` or the Deck's Settings view (0 to 365; `0`, off, by default) keeps a copy of what a person deletes, and of what either retention removes, for the person to put back from the webmail's *Recover deleted items*. A domain or an account may keep copies for fewer days than the level above, never more. The copies, under `<data directory>/Recoverable/<account id>/`, **count against no quota** and are **not in the backup**, so size the window with the disk in mind; the six-hourly task ends those past the window, and all of them at `0`. The person's side is in [The webmail in depth: search, offline mail, expiring messages, send-as and recovery](/topic/28254).

### The server archive

With `ArchiveDir` set, the server writes a copy of every message received by SMTP into a tree of its own, as received and in the clear: encryption at rest does not cover it, and the mailbox retention policy does not reach it. The layout, as the Settings Reference writes it (Windows separators, names lower-cased):

| Message | Where the copy goes |
|---|---|
| From a local sender | `ArchiveDir\<senderdomain>\<sendername>\Sent-<file>` |
| From anybody else | `ArchiveDir\Inbound\<file>` |
| For each local recipient | `ArchiveDir\<recipientdomain>\<recipientname>\<file>`, made from the first copy when there is one |
| A bounce or report with a null sender | The archive's `Error` folder, with no index row |

The archive's settings:

| Key | Default | What it does |
|---|---|---|
| `ArchiveDir` | empty (off) | The folder the copies go to |
| `ArchiveDomains` | empty (every message) | Comma-separated: a message is archived only when its local sender or a recipient is in a listed domain, and only the listed domains' copies are made |
| `ArchiveHardLinks` | `0` | Hard-link instead of copy: a promise that nothing outside the server edits a message file in place |
| `ArchiveRetentionDays` | `0` (keep for ever) | Deletes archived copies older than this many days |

**Its sweep** runs at start-up and every twelve hours. It removes only `.eml` files, follows no symbolic link or junction, is depth-capped, and asks the index first: a **held** copy stays however old.

**The index** (since 6.2.25, schema 6029): a row per copy in `hm_archiveindex`, with who, when, what, where and whether it is held; copies made before schema 6029 have none. Search it in the Deck's **Archive** view (since 6.3.4), with `GET /api/v1/archive` (`domain`, `mailbox`, `sender`, `recipient`, `subject`, `since`, `until`, `hold=1`, `limit` 1 to 1000, default 200, newest first), or with `Utilities.SearchArchive` over COM (Windows). A role needs `view_archive`; a key restricted to named domains must name one of its own, and the Inbound copies are the administrator's alone. Searching the words of archived mail is an eDiscovery case's work.

**A hold on one copy**, `POST /api/v1/archive/{id}/hold` or `Utilities.SetArchiveHold`, keeps its file and row from the sweep and from an address's erasure; `DELETE` lifts it. Holds on whole accounts and domains are eDiscovery's: see [Compliance: eDiscovery, legal holds, personal data, erasure and the audit trail](/topic/28242).

```bash
curl -u Administrator:your-admin-password \
  "http://127.0.0.1:8443/api/v1/archive?domain=example.com&sender=alice@example.com&since=2026-09-01&limit=50"
curl -u Administrator:your-admin-password -X POST http://127.0.0.1:8443/api/v1/archive/4471/hold
```

**Where it is set.** On Windows, the Control Panel's **Maintenance → Advanced**, *Copies of mail* tab (`ArchiveDir`, `ArchiveHardLinks`, `ArchiveDomains`), and `ArchiveRetentionDays` on the SMTP tab of **Connections & protocols → Protocols**. The Deck has no page for these keys: use `PUT /api/v1/settings/ini/{name}` or `hmctl ini set <name> <value>` (the administrator password only), which take effect when saved, or `hmailserver --set-setting NAME=VALUE` (with `sudo`) on the Linux server itself, which, while the service runs, waits for the next save, a reinitialise (`POST /api/v1/server/reinitialize`) or a restart.

**Setting one up:**

1. Choose a folder **outside the data directory**: a full restore with the messages option replaces that directory, and an archive inside it too, held copies included.
2. On Windows, a new installation runs the service as `NT SERVICE\hMailServer`, which writes only where it is granted: approve the folder by running `hMailServer.exe /GrantServiceAccess` with `--approve-modify "<folder>"` from an elevated Command Prompt in the installation's `Bin` folder. It must be a full path on a fixed local drive, outside the program folder, Program Files, the Windows folder and the profiles folder: never a share, a drive's root, or a path through a junction or link.
3. On Linux the service runs as `User=hmailserver`, and the packaged unit lets it write only under `/var/lib/hmailserver`, the data directory itself, and `/var/log/hmailserver`. Every other folder is read-only to it (`/home` is not even visible), and an `ArchiveDir` it cannot write stops archiving. Make the folder writable by the `hmailserver` user, add it to the unit with a drop-in (`sudo systemctl edit hmailserver`, then under `[Service]` a `ReadWritePaths=` line naming the folder) and restart the service. [SecureDefaults.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SecureDefaults.md) gives this drop-in for a data folder; no document gives it for the archive.
4. Set `ArchiveDomains` first if only some domains are archived, then `ArchiveDir`.
5. Send a test message and find its row in the Deck's Archive view.

**Before relying on it.**

- **The backup does not carry it.** The backup copies the data directory, and `hm_archiveindex` is not carried: back up the archive folder, and the database with its own tool for the index.
- **Moving it strands the index.** `hm_archiveindex.archivepath` is matched as an exact string by the sweep, holds and erasure; do not rewrite it in bulk. If the folder moves, move it back or accept that older rows point at the old place.
- **A mailbox move leaves the archive under the old address**, and an address's erasure with the archive included leaves the copies in other people's trees and in `Inbound` until `ArchiveRetentionDays` ages them out.
- **It is not journaling**, which sends a copy elsewhere and keeps nothing here: [Journaling and dual delivery](/topic/28271).
- **SMTP, as documented.** No 6.3.4 document says whether mail the server queues itself, such as the webmail's send, or mail a fetch account collects, is archived.

### What it does not do

- **No message-count quota**: storage is the only quota enforced.
- **No server-wide retention policy**, and no documented way to run the retention sweep on demand on Linux.
- **No write-once archive**: a hold is a flag an administrator can lift.
- **The archive only grows** while `ArchiveRetentionDays` is `0`, and the recover-deleted store is bounded by its window alone: watch the disk they are on.

### Where to read more

- Documents at v6.3.4: [QuotaPolicy.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/QuotaPolicy.md), [SettingLevels.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingLevels.md), [Reports.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Reports.md), [RecoverDeleted.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/RecoverDeleted.md), [Discovery.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Discovery.md), [Backup.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Backup.md), [ServiceAccount.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/ServiceAccount.md), [RelocatingAnInstallation.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/RelocatingAnInstallation.md), [PersonalData.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/PersonalData.md), [Journaling.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/Journaling.md), [SecureDefaults.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SecureDefaults.md), [SettingsApply.md](https://gitlab.com/hmailserver/hmailserver/-/blob/v6.3.4/hmailserver/docs/SettingsApply.md).
- Wiki: [Settings Reference](https://gitlab.com/hmailserver/hmailserver/-/wikis/Settings-Reference) (section 9.13), [Your First Domain and Mailbox](https://gitlab.com/hmailserver/hmailserver/-/wikis/Your-First-Domain-and-Mailbox), [Compliance and eDiscovery](https://gitlab.com/hmailserver/hmailserver/-/wikis/Compliance-and-eDiscovery), [Backup and Restore](https://gitlab.com/hmailserver/hmailserver/-/wikis/Backup-and-Restore), [APIs Reference](https://gitlab.com/hmailserver/hmailserver/-/wikis/APIs-Reference).
- The documentation page: [Storage, retention and archive](https://www.progressiverobot.com/hmailserver-documentation/#storage-retention-and-archive), [Queue, quarantine and archive](https://www.progressiverobot.com/hmailserver-documentation/#2411-queue-quarantine-and-archive).
- On this forum: [Backup and restore](/topic/28221), for what a restore resets; [Relocating an installation, and moving or renaming a mailbox](/topic/28244), for the archive index; [the service account on Windows](/topic/28227) and [the Linux unit](/topic/28245), for the folders the service may write; [The Control Deck](/topic/28192).
