Let an installation seed and lock user settings #230

Closed
opened 2026-09-02 17:50:09 +00:00 by jcoffey-dev · 0 comments
Owner

Refs #207 — the first two of the three tiers the reporter laid out. Tier three is deliberately absent; see the bottom.

A school wanting "warn about outside senders" on for three thousand pupils cannot ask three thousand pupils. The reporter is right that this is a company policy rather than a preference.

Two powers

  • defaults seed an account that has never had settings of its own. The reader can change any of them afterwards — a starting point, not a rule.
  • enforced are reapplied on every load and cannot be changed at all.

Enforced controls stay visible and go dead, with a line saying why. The issue asked for exactly that: a control that is simply missing reads as a bug to somebody who has used ihasmail without a policy.

The lock is in the store, not on the controls

There is one door — update — and putting the lock there means these are covered by construction rather than by remembering:

  • an imported settings file (Settings → Import),
  • a settings file synced from a device that signed in before the policy existed,
  • reset, which goes back to the installation's answer rather than ihasmail's,
  • and any control someone adds later and forgets to check.

The controls are disabled too, because that is what the reader sees — but the store is what makes it true.

Configuration

-e SETTINGS_DEFAULTS='{"externalSenderBanner":true}' \
-e SETTINGS_ENFORCED='{"externalRecipientConfirm":true}'
# or
-e SETTINGS_POLICY_FILE=/etc/ihasmail/policy.json

Both forms, because ihasmail's own production runs read-only with no volume — an installation that cannot mount a file can still set a variable. Same names and values a settings export uses, so exporting a configured account is the quickest way to write one.

Two deliberate strictnesses: keys this build does not have are dropped (the same rule importJson already applies — a policy written against a newer ihasmail must not put a setting nothing reads into everyone's synced settings file), and malformed JSON stops the server at startup rather than quietly doing nothing, since a policy that silently did not apply is indistinguishable from the feature not working.

Tests

Twelve: unknown keys dropped from both sections, defaults seeding only an account with none, defaults still being changeable afterwards, and enforcement surviving an update, a partial update alongside free settings, a hydrate from another device, a reset, and an import.

npm run typecheck, npm test (968 web + 122 server), npm run build, i18n:check (no new stale keys) pass. One new string in all nine catalogues.

Not in this PR

Tier three — enforcing a setting once while still letting readers change it afterwards. It needs a decision that hasn't been made yet, and it is the only tier that stores anything new. Being put to the reporter on #207.

Merged 2026-09-02 as coffey-labs/ihasmail@6821d6a93f

Rebuilt from: git history, session transcript.

Refs #207 — the first two of the three tiers the reporter laid out. Tier three is deliberately absent; see the bottom. A school wanting "warn about outside senders" on for three thousand pupils cannot ask three thousand pupils. The reporter is right that this is a company policy rather than a preference. ## Two powers - **`defaults`** seed an account that has never had settings of its own. The reader can change any of them afterwards — a starting point, not a rule. - **`enforced`** are reapplied on every load and cannot be changed at all. Enforced controls **stay visible and go dead**, with a line saying why. The issue asked for exactly that: a control that is simply missing reads as a bug to somebody who has used ihasmail without a policy. ## The lock is in the store, not on the controls There is one door — `update` — and putting the lock there means these are covered by construction rather than by remembering: - an imported settings file (`Settings → Import`), - a settings file synced from a device that signed in before the policy existed, - `reset`, which goes back to the *installation's* answer rather than ihasmail's, - and any control someone adds later and forgets to check. The controls are disabled too, because that is what the reader sees — but the store is what makes it true. ## Configuration ```bash -e SETTINGS_DEFAULTS='{"externalSenderBanner":true}' \ -e SETTINGS_ENFORCED='{"externalRecipientConfirm":true}' # or -e SETTINGS_POLICY_FILE=/etc/ihasmail/policy.json ``` Both forms, because ihasmail's own production runs read-only with no volume — an installation that cannot mount a file can still set a variable. Same names and values a settings export uses, so exporting a configured account is the quickest way to write one. Two deliberate strictnesses: **keys this build does not have are dropped** (the same rule `importJson` already applies — a policy written against a newer ihasmail must not put a setting nothing reads into everyone's synced settings file), and **malformed JSON stops the server at startup** rather than quietly doing nothing, since a policy that silently did not apply is indistinguishable from the feature not working. ## Tests Twelve: unknown keys dropped from both sections, defaults seeding only an account with none, defaults still being changeable afterwards, and enforcement surviving an update, a partial update alongside free settings, a hydrate from another device, a reset, and an import. `npm run typecheck`, `npm test` (968 web + 122 server), `npm run build`, `i18n:check` (no new stale keys) pass. One new string in all nine catalogues. ## Not in this PR **Tier three** — enforcing a setting once while still letting readers change it afterwards. It needs a decision that hasn't been made yet, and it is the only tier that stores anything new. Being put to the reporter on #207. **Merged** 2026-09-02 as coffey-labs/ihasmail@6821d6a93ffc <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.