mdbox Alt Storage (Cold Tiering)
yarilo mdbox supports moving messages to a secondary (cold) storage tier — functionally equivalent to the reference's mail_alt_path + doveadm altmove. Messages moved to alt storage remain fully accessible; the Fetch path transparently falls back to the alt directory when a file is not found in primary.
Configuration
storage:
mail_driver: mdbox
mail_home: "%d/%n"
mail_alt_path: "/mnt/cold/%d/%n" # "" = disabled (default)mail_alt_path supports the same template variables as mail_home:
| Variable | Expands to |
|---|---|
%u | Full username ([email protected]) |
%Lu | Lowercased full username |
%n | Local part (alice) |
%Ln | Lowercased local part |
%d | Domain (example.com) |
%Ld | Lowercased domain |
The alt directory mirrors the primary mdbox layout:
/mnt/cold/example.com/alice/
storage/
m.1001, m.1002, ... ← moved m.<N> files (same naming scheme)File IDs are global across both tiers — the same m.<N> ID never exists in both primary and alt simultaneously.
Helm
# values.yaml
imap:
storage:
mdboxAltStoragePath: "/mnt/cold/%d/%n"A separate PVC (or NFS export) should be mounted at the cold path — typically a cheaper storage class (HDD-backed, object-gateway, etc.).
Triggering a move
# Move all messages older than 2025-01-01 to cold storage:
yarctl backend mdbox altmove [email protected] \
--before 2025-01-01T00:00:00Z
# Move all messages (no date filter):
yarctl backend mdbox altmove [email protected]
# Move back from cold to primary (reverse):
yarctl backend mdbox altmove [email protected] \
--before 2025-01-01T00:00:00Z \
--reverse--before accepts RFC 3339 timestamps. Messages whose InternalDate (the R field in the dbox v2 trailer — set when the message was first saved) is strictly before the cutoff are eligible.
Operational notes
Fetch transparency
When a session fetches a message whose primary m.<N> file has been moved, the driver automatically opens the alt file. The IMAP client sees no difference. The fallback only fires on ENOENT — any other open error (permissions, I/O) is returned as-is.
Refcount and COPY
O(1) IMAP COPY works identically across both tiers: Copy() only bumps the refcount; the copied map_uid continues to point at whichever tier holds the physical body. A subsequent altmove on the copied map_uid moves both the original and the copy together (they share the same map_uid).
Partial file moves
When a source m.<N> contains both eligible and ineligible records, altmove splits the file: eligible records go to a new alt m.<N>, ineligible records stay in a new primary m.<N>. Two new files are created; the old source is unlinked. The map index is updated atomically.
Interplay with Purge
Run purge before altmove to avoid compacting zero-ref records into the alt tier unnecessarily:
yarctl backend mdbox purge [email protected]
yarctl backend mdbox altmove [email protected] --before 2025-01-01T00:00:00ZAutomation via CronJob
# k8s CronJob — monthly cold-tier sweep for messages older than 90 days
apiVersion: batch/v1
kind: CronJob
metadata:
name: mdbox-altmove
spec:
schedule: "0 2 1 * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: altmove
image: ghcr.io/yarilomail/yarilo:latest
command:
- yarctl
- backend
- mdbox
- altmove
- [email protected]
- --before
- "{{ now | date \"2006-01-02T15:04:05Z\" | dateModify \"-2160h\" }}"
restartPolicy: OnFailureIn practice, drive the user list from the SQL passdb iterate_query and loop per user.
Wire compatibility with the reference
The on-disk format of moved m.<N> files is identical to primary files — same dbox v2 record layout. The reference's mail_alt_path and yarilo's mail_alt_path are interchangeable at the filesystem level, enabling live migration between the two servers without data conversion.