Frostmail maild
The maild extension adds maild, the Frostmail mail engine, to a Frostyard bootc image. It runs as lodge’s Mail app, with the app id mail. A server that runs the suite’s services runs Floe. Frostmail’s own app page is Frostmail. Every image carries the extension’s definition, switched off. The extension itself is not in the image: Updex turns it on and downloads it.
Before you start
Section titled “Before you start”You need:
- A Frostyard bootc image built on Base.
- Root access to enable, update, and remove the extension.
- Network access to
https://repository.frostyard.org/ext/maild/. - Free space in
/varfor the image.
maild has no published test results on Frostyard images yet. Try it on a machine you can spare first.
You reach Mail in a browser through lodge. Enable lodge first. The maild extension declares no dependency on lodge, so enabling maild does not enable lodge.
Enable maild
Section titled “Enable maild”See what Updex found, then enable the feature and download it now:
updex features list --component=maildsudo updex features enable maild --nowmaild appears as a component, with its feature disabled. The enable command
downloads the image, checks its checksum and signature, links it, and refreshes
the merge. With Updex v2.1.3 and later, the refresh also creates the users,
groups, and directories that the extension declares, before systemd reloads.
Upholds= then starts the units, with no reboot. updex --version shows your
version.
If updex features list does not show the feature, the image is older than the
extension. Update the image first.
With an Updex older than v2.1.3, the units cannot start after the enable: their
users and groups do not exist yet. Reboot. At boot the image merges the
extensions, runs systemd-sysusers and systemd-tmpfiles --create, reloads
systemd, and Upholds= starts the units. To start them without a reboot:
sudo systemd-sysuserssudo systemd-tmpfiles --createsudo systemctl reset-failed frostmail.socket frostmail.servicesudo systemctl start frostmail.socket frostmail.serviceCheck the merge and the units:
systemd-sysext statussystemctl status frostmail.socket frostmail.servicesudo -u frostmail mailctl --socket /run/frostmail/maild.sock hellosystemd-sysext status lists maild, systemctl status shows both units
active, and hello prints maild <version> (protocol N). hello exits
non-zero when maild does not answer.
If a unit shows failed with start-limit-hit, systemd stopped starting it.
Run sudo systemctl reset-failed frostmail.socket frostmail.service, then
sudo systemctl start frostmail.socket frostmail.service, or reboot. Then run the checks again.
If enable --now exits with a refresh error (refresh_error in JSON), the
download worked, but the activation did not. If the output says
activation did not finish, the extension is merged, but its users and groups
can be missing: reboot. Otherwise the merge state is unknown: reboot. A
sudo systemd-sysext refresh by hand does not create the extension’s users
and groups. Then run the checks above again.
What runs
Section titled “What runs”frostmail.socket and frostmail.service run on the host. maild’s rules and
Send Later run without a client, so the service is upheld too.
The drop-in /usr/lib/systemd/system/multi-user.target.d/10-maild.conf has
Upholds= for both units, and the preset
/usr/lib/systemd/system-preset/40-maild.preset enables both. Both start at
boot. If one stops, even with exit status 0, systemd starts it again. To stop it
for longer, remove the extension or mask the unit.
extension-release.maild sets EXTENSION_RELOAD_MANAGER=1, so a merge or
refresh reloads systemd’s unit files.
frostmail.service runs /usr/bin/maild --lodge as the system user
frostmail. Its data is in /var/lib/frostmail, its cache in
/var/cache/frostmail, and mailctl reaches it on the private socket
/run/frostmail/maild.sock.
frostmail.socket listens on /run/lodge/apps/mail.sock as owner frostmail,
group lodge, mode 0660.
At first start, frostmail-secrets-key.service makes the key that seals maild’s
secrets, /etc/credstore.encrypted/frostmail-secrets-key, with
systemd-creds encrypt --with-key=auto. It skips when the key exists, and never
replaces a key. frostmail.service pulls it with Requires= and After=. The
unit is not upheld and not in the preset. maild refuses a missing or wrong key.
The key is in /etc; the extension never ships it.
The sysusers file /usr/lib/sysusers.d/frostmail.conf creates the user
frostmail and the groups lodge and lodge-apps, with frostmail in
lodge-apps. The user is never in group lodge.
The file also creates the user lodge, with the same line as lodge’s own
sysusers file. The daemon looks up the user lodge when it starts, so it
starts without lodge’s extension.
The files are /usr/bin/maild, /usr/bin/mailctl,
/usr/libexec/frostmail/make-secrets-key, and
frostmail-secrets-key.service. The lodge app files are in
/usr/share/lodge/apps/mail/: app.json and web/.
Update, roll back, and remove
Section titled “Update, roll back, and remove”Back up maild’s state before you update. maild migrates its database when it starts, and an older maild refuses a database with a newer schema. A rollback does not undo a migration.
Stop maild for the backup. Systemd starts upheld units again, so mask them while you copy. While maild is stopped, the Mail tab does not work, rules and Send Later do not run, and lodge shows the app as down. The archive holds the mail database, the messages, and the sealed account secrets, so keep it root-only:
sudo systemctl mask --now frostmail.socket frostmail.servicesudo sh -c 'umask 077; tar -czpf /root/frostmail-state-$(date -u +%Y%m%dT%H%MZ).tar.gz /var/lib/frostmail'sudo systemctl unmask frostmail.socket frostmail.servicesudo systemctl start frostmail.socket frostmail.servicetar exits 0, and systemctl status frostmail.socket frostmail.service shows
both units active again.
The key that opens the sealed secrets, /etc/credstore.encrypted/frostmail-secrets-key,
is not in the archive. It is bound to the host, so a restore of the archive
works on this host only.
Check for an update, then install it:
updex features check --component=maildsudo updex features update --component=maildBy default, the update merges the new files at once, and systemd reloads its unit files. Updex v2.1.3 and later also create new users, groups, and directories at once. An older Updex leaves them for the next boot. The running maild keeps its old binary until it restarts. A reboot applies everything.
The new files wait for the next boot when you pass --no-refresh, when the
automatic update timer installs the update, or when the output says
refresh skipped: Updex skips the refresh when it cannot refresh safely. Then
reboot. See Update.
With an Updex older than v2.1.3, run these commands first. updex --version
shows your version.
sudo systemd-sysuserssudo systemd-tmpfiles --createsudo systemctl daemon-reloadTo run the new version now, restart maild, then let lodge read the new manifest:
sudo systemctl restart frostmail.socket frostmail.servicesudo lodgectl reloadCheck the version that runs:
sudo -u frostmail mailctl --socket /run/frostmail/maild.sock hellosudo lodgectl appshello prints maild <version> (protocol N) with the new version. lodgectl apps
lists mail up, with the new version. lodge polls each app every 15 seconds,
so if mail shows down at once, run lodgectl apps again after 15 seconds.
Roll back to the previous version:
sudo updex features rollback maildThe rollback takes effect at the next boot. To apply it now, add --now, then
restart maild as above. See Roll back.
When the older version runs, read the journal:
journalctl -u frostmail.service -n 50maild 0.3.0 migrates the database of 0.2.0, so a rollback from 0.3.0 to 0.2.0
needs a restore. If the journal shows database schema N is newer than this maild,
restore the state from your backup. Replace ARCHIVE with the path of the
archive:
sudo systemctl mask --now frostmail.socket frostmail.servicesudo rm -rf /var/lib/frostmailsudo tar -xzpf ARCHIVE -C / var/lib/frostmailsudo systemctl unmask frostmail.socket frostmail.servicesudo systemctl start frostmail.socket frostmail.serviceThe restore loses what changed after the backup.
Remove maild:
sudo updex features disable maild --now --forceUpdex unmerges all extensions on the host, deletes the image, and merges the
rest again. Reboot when the command finishes. Without --force, Updex refuses
while the image is merged.
What ships
Section titled “What ships”| Item | What you get |
|---|---|
| Package | frostyard-maild, the .deb attached to the app’s GitHub release, installed into the image with dpkg. The build checks the download against a pinned SHA-256, and checks that its dependencies are met before it installs. Its GitHub build-provenance attestation is checked when the pin is set and in every CI build, before the image is built |
| Feature | Mail (Frostmail mail engine and Lodge mail app) |
| Commands | /usr/bin/maild and /usr/bin/mailctl |
| Service | frostmail.service, which runs /usr/bin/maild --lodge as the system user frostmail |
| Socket | frostmail.socket on /run/lodge/apps/mail.sock, owner frostmail, group lodge, mode 0660 |
| Private socket | /run/frostmail/maild.sock, for mailctl |
| Data and cache | /var/lib/frostmail and /var/cache/frostmail |
| Secrets key | frostmail-secrets-key.service and /usr/libexec/frostmail/make-secrets-key, which make /etc/credstore.encrypted/frostmail-secrets-key at first start |
| User and group | frostmail, groups lodge and lodge-apps, frostmail in lodge-apps, and the user lodge, from /usr/lib/sysusers.d/frostmail.conf |
| Lodge app | /usr/share/lodge/apps/mail/, with app.json and web/ |
| Activation | /usr/lib/systemd/system/multi-user.target.d/10-maild.conf with Upholds=, and /usr/lib/systemd/system-preset/40-maild.preset |
| Version | The image file name carries the frostyard-maild package version, which is the app’s release version |
| Image file | maild_<version>_13_x86-64.raw.zst, where 13 is the OS version ID; .raw.xz, .raw.gz, and .raw also work |
| Download | From https://repository.frostyard.org/ext/maild/, signature required (Verify=true), into /var/lib/extensions.d/ |
Each build checks the commands, the units, the secrets-key helper /usr/libexec/frostmail/make-secrets-key, the sysusers file, the lodge app manifest and web page, and the preset, activation drop-in and extension-release files. A build that is missing one fails. Each build also checks that every upheld unit has no Condition*= line, or is a oneshot that stays active.
See also
Section titled “See also”- System extension catalog
- Manage system extensions
- lodge
- Frostmail
- Mail in lodge: the app contract with lodge.
- Accounts, sign-in and providers: how to add an account, and what each provider needs.
Sources
Section titled “Sources”Reviewed 2026-10-29. snosi commit
e423112908540aa3a8045fa9d8022a3742e3cd17;
core commit
cd2720e0c55d08f3ba8f28a3b55dd5796a5c425a;
Frostmail v0.3.0 commit
89243ae6753d6da11947c6fd7023c84ad0ce59d7;
Updex v2.1.3 commit
2661e987faac986f2594d1d9bbbadf1bf3053f5b.
mkosi.images/maild/mkosi.confmkosi.images/maild/mkosi.postinst.chrootmkosi.images/maild/required-paths.txtmkosi.images/maild/mkosi.extra/usr/lib/systemd/system-preset/40-maild.presetmkosi.images/maild/mkosi.extra/usr/lib/systemd/system/multi-user.target.d/10-maild.confmkosi.images/maild/mkosi.extra/usr/lib/extension-release.d/extension-release.maildmkosi.images/base/mkosi.extra/usr/lib/sysupdate.maild.d/maild.featuremkosi.images/base/mkosi.extra/usr/lib/sysupdate.maild.d/maild.transfershared/download/sysext-checksums.jsondocs/design/sysexts.md- snosi#1169 (merged 2026-10-28), snosi#1173 (merged 2026-10-28) and snosi#1175 (merged 2026-10-28)
- core
docs/adr/0058-ship-suite-desktop-apps-as-flatpaks-and-services-as-sysexts.md - Frostmail
packaging/lodge/frostmail.service,packaging/lodge/frostmail.socket,packaging/lodge/frostmail-secrets-key.service,packaging/lodge/make-secrets-key.sh,packaging/lodge/sysusers.d-frostmail.conf,cmd/mailctl/hello.go - Updex v2.1.3
README.md(accounts and paths: a live refresh runssystemd-sysusersandsystemd-tmpfiles --create; a link on an older image shows an update, andfeatures updaterepoints it) - Updex v2.1.3
cmd/updex/features.go(features updaterefreshes the merge unless--no-refresh) - snosi
docs/adr/0024-verify-attestations-of-first-party-sysext-debs.md(attestation checked at pin time and in every CI build) - Murbella’s review (frostyard-org#57)
- lodge#41 (merged 2026-10-28)
- lodge
docs/specs/app-contract.md(each app’ssysusers.dentry declares userlodge, with the line in lodge’s ownpackaging/sysusers.d/lodge.conf) - lab
docs/plans/2026-10-28-snow-sysext-probe-run.md(runjvzfn: before the packages declared the userlodge, a unit failed in the enable boot) and lab#164 (merged 2026-10-28) - lab run
dev-snow-bootc-lifecycle-9f8w4(2026-10-28, labmainat4250354, with the settle window of lab#166; not yet recorded in the lab repository): with notesd 0.3.0 and maild 0.3.0 and Updex v2.1.3, the userlodgeexists in the enable boot, andfrostnotes-lodge.socket,frostmail.socket,frostnotes.serviceandfrostmail.servicestay active with no restart; the reboot boot matches - Updex
docs/adr/0019-apply-merged-sysusers-and-tmpfiles-on-a-live-refresh.md(Updex does not restart units; a unit at its start limit stays failed untilsystemctl reset-failedor a reboot) - Updex v2.1.3
updex/features.go(activation did not finish: the images are merged, and a reboot finishes the activation) - Updex v2.1.3 release
- lodge
docs/specs/lodge-http.md(lodgectl reloadrereads the manifests),internal/apps/apps.go(PollEvery, 15 seconds) - Frostmail
internal/store/store.go(the schema refusal),internal/store/migrations/0019_store_info.sql(the migration that v0.3.0 adds),internal/config/paths.go(blobs in the data directory),docs/adr/0044-seal-the-servers-secrets-with-a-systemd-credential.md(secrets.sealedand its host-bound key),cmd/maild/main.go(hellonamesmaild <version>) src/data/extensions.json