Storage
[storage]
storage_type = "log" # "log" (default) or "fs" (legacy filesystem)
base_path = "/var/spool/hedwig" # Base directory for email storage
[storage.cleanup]
bounced_retention = "7d" # Remove bounced messages after 7 days
deferred_retention = "2d" # Remove deferred messages after 2 days
interval = "1h" # Run the cleanup task hourly
- All keys inside
[storage.cleanup]are optional; omit them to disable specific cleanups - Retention values accept human-readable durations (e.g.,
"24h","5m") - The cleanup task runs on a background interval and also executes once during startup
Log queue backend
storage_type = "log" — the default when storage_type is omitted —
selects the durable log queue: complete messages are
stored in segmented append-only logs under <base_path>/spool/, and SMTP
250 OK is returned as soon as the record is written — inbound acceptance
is bounded by disk append throughput, not by outbound delivery speed.
Retries are scheduled by due time (no periodic spool scans), and retries
after a partial multi-recipient failure re-send only to the recipients that
have not yet accepted the message.
[storage]
storage_type = "log"
base_path = "/var/spool/hedwig" # spool/ (queue) + bounced/ (archive)
[queue] # all optional; defaults shown
append_writers = 1 # log shards; change requires an empty queue
pending_append_bytes = 134217728 # in-memory admission buffer bound (bytes)
segment_target_bytes = 67108864 # seal active segments at this size
compaction_dead_ratio = 0.5 # compact sealed segments this dead
compaction_min_age = "60s" # leave fresh segments alone
disk_reserve_bytes = 1073741824 # reject mail when free disk drops below
checkpoint_interval_bytes = 8388608 # checkpoint cadence per shard
Durability tradeoff: acknowledged mail is durable against process
crashes and restarts, but the queue does not fsync per message — a machine
crash or power loss can lose the most recently acknowledged messages. This
is a deliberate throughput tradeoff. Destructive operations (segment
deletion, compaction, checkpoint truncation) do use fsync barriers, so a
power loss can never destroy older queued mail.
Disk behavior: disk use is proportional to the live backlog plus
not-yet-reclaimed garbage. Fully delivered segments are deleted as soon as
their last message completes; partially dead segments are compacted once
compaction_dead_ratio is exceeded. When free space falls below
disk_reserve_bytes, new mail is rejected with a transient 452.
Bounced messages are archived as regular files under bounced/ (same layout
as the filesystem backend) and honor [storage.cleanup] retention.
Inspect a spool — including a live one — with the built-in CLI:
hedwig queue list --spool /var/spool/hedwig/spool
hedwig queue show --spool /var/spool/hedwig/spool <MESSAGE_ID>
hedwig queue stats --spool /var/spool/hedwig/spool
To migrate an existing filesystem spool, stop the server, set
storage_type = "log", then run hedwig queue migrate --config <config>;
legacy queued/ and deferred/ directories are preserved as timestamped
.migrated-* backups.