Part of hq issue 688 (mailu stopped resolving and handling mail and the mesh raised nothing; the operator found it).
What was seen
On 2026-10-14 mailu on novox could not resolve any name, so inbound mail was refused (450) and outbound could not leave, while all 13 containers stayed healthy and no condition was raised. Two checks lied:
the resolver's image check dig @127.0.0.1 exits 0 on SERVFAIL: fixed by moving to Mailu 2024.06.63 in #284 (hq issue 687), whose image check fails on anything but NOERROR;
the front's image check is curl http://127.0.0.1:10204/health && kill -0 dovecot: it never looks at mail.
Change
mailu-front's health goes from runtime to exec (ADR 0240), keeping the image's two tests and adding:
SMTP on port 25 greets: python3 smtplib connects to 127.0.0.1:25, EHLO, QUIT (no mail sent). Port 25 is a plain listener here (no PROXY_PROTOCOL_25 in mailu.env).
The served certificate holds 14 more days: openssl x509 -checkend 1209600 on /certs/letsencrypt/live/mailu/nginx-chain.pem, the file Mailu's config.py serves for TLS_FLAVOR=letsencrypt. certbot renews at 30 days left, so this says a renewal stuck for 16 days (as it is today while DNS fails) well before expiry. Currently served: CN=mail.novox.be, notAfter 2026-12-25.
Timing: every 60 s, timeout 15 s, 3 looks, grace 120 s (120 + 180 = 300 s, within ADR 0240's five minutes).
Checked in the pinned image (ghcr.io/mailu/nginx@sha256:36f98897…, run locally): curl, /app/venv/bin/python3 with smtplib, /usr/bin/openssl (3.3.7), kill and cat are present. --health-cmd runs in the container's shell, so $(…) and && work.
Effect on the operator
mailu's upgrade policy is record: this waits for a person's go and recreates mailu-front (seconds without IMAP/SMTP). A failure then raises module.mailu.novox.unhealthy ("mailu not working on novox"), a warning, said on the desktop.
Not in this pull request
An end-to-end check that mail flows (a deferred queue that grows, a probe message through the whole path) is a tool check; tracked separately.
Whether mail's unhealthy is urgent (every channel, Telegram too) is a controller rule (module_health.go: every module unhealthy is a warning); asked of the operator.
Acceptance
After the walk, docker_inspect mailu-front shows the new Healthcheck and healthy; with port 25 or the certificate broken it turns unhealthy within five minutes and mesh-controller.conditions holds module.mailu.novox.unhealthy.
Part of hq issue 688 (mailu stopped resolving and handling mail and the mesh raised nothing; the operator found it).
## What was seen
On 2026-10-14 mailu on novox could not resolve any name, so inbound mail was refused (450) and outbound could not leave, while all 13 containers stayed `healthy` and no condition was raised. Two checks lied:
- the resolver's image check `dig @127.0.0.1` exits 0 on SERVFAIL: fixed by moving to Mailu 2024.06.63 in #284 (hq issue 687), whose image check fails on anything but NOERROR;
- the front's image check is `curl http://127.0.0.1:10204/health && kill -0 dovecot`: it never looks at mail.
## Change
`mailu-front`'s `health` goes from `runtime` to `exec` (ADR 0240), keeping the image's two tests and adding:
- **SMTP on port 25 greets**: `python3 smtplib` connects to 127.0.0.1:25, EHLO, QUIT (no mail sent). Port 25 is a plain listener here (no `PROXY_PROTOCOL_25` in mailu.env).
- **The served certificate holds 14 more days**: `openssl x509 -checkend 1209600` on `/certs/letsencrypt/live/mailu/nginx-chain.pem`, the file Mailu's `config.py` serves for `TLS_FLAVOR=letsencrypt`. certbot renews at 30 days left, so this says a renewal stuck for 16 days (as it is today while DNS fails) well before expiry. Currently served: CN=mail.novox.be, notAfter 2026-12-25.
Timing: every 60 s, timeout 15 s, 3 looks, grace 120 s (120 + 180 = 300 s, within ADR 0240's five minutes).
Checked in the pinned image (`ghcr.io/mailu/nginx@sha256:36f98897…`, run locally): curl, `/app/venv/bin/python3` with smtplib, `/usr/bin/openssl` (3.3.7), kill and cat are present. `--health-cmd` runs in the container's shell, so `$(…)` and `&&` work.
## Effect on the operator
mailu's upgrade policy is `record`: this waits for a person's go and recreates `mailu-front` (seconds without IMAP/SMTP). A failure then raises `module.mailu.novox.unhealthy` ("mailu not working on novox"), a warning, said on the desktop.
## Not in this pull request
- An end-to-end check that mail flows (a deferred queue that grows, a probe message through the whole path) is a tool check; tracked separately.
- Whether mail's unhealthy is urgent (every channel, Telegram too) is a controller rule (`module_health.go`: every module unhealthy is a warning); asked of the operator.
## Acceptance
After the walk, `docker_inspect mailu-front` shows the new Healthcheck and `healthy`; with port 25 or the certificate broken it turns unhealthy within five minutes and `mesh-controller.conditions` holds `module.mailu.novox.unhealthy`.
mesh/merge-gate head 4ded07bc635f merged into trunk b15846dd6e78 — pass: builds mailu, sent nowhere; no bus step; 1 wait(s) for a person; every machine com…
mesh/repo-check head 4ded07bc635f merged into trunk b15846dd6e78 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
The image's own check asks nginx's /health and that dovecot runs, so the
front stayed healthy while mail could not flow (issue 688). It now also
needs port 25 to greet over SMTP and the served certificate to hold for
14 more days; certbot renews at 30, so a renewal stuck for two weeks is
said long before the certificate expires.
Deliverynovox/mesh-catalog@4ded07bc635f — delivered since 2026-10-14T13:07:52Z
Delivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
tier 0: mailu
novox: waits for a person: mailu
mailu waits for a person: its policy records (module)
mailu provides smtp: its consumers are sent again after it
mailu keeps data (ADR 0233): what it holds is backed up before it moves
Transitions
2026-10-14 00:20 (new) → proposed (announced): novox/mesh-catalog#285's head announced
2026-10-14 00:22 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-14 00:22 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-14 05:06 ready → proposed (trunk moved): out of date: judged against trunk ebf7f3773d, the trunk is now e846c289c878; judged again on a recheck or when it is merged
2026-10-14 13:05 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
2026-10-14 13:05 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-14 13:07 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-14 13:07 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
2026-10-14 13:07 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-catalog@4ded07bc635f` — **delivered** since 2026-10-14T13:07:52Z
**Delivery plan** — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
- tier 0: mailu
- novox: waits for a person: mailu
- mailu waits for a person: its policy records (module)
- mailu provides smtp: its consumers are sent again after it
- mailu keeps data (ADR 0233): what it holds is backed up before it moves
**Transitions**
- 2026-10-14 00:20 (new) → proposed (announced): novox/mesh-catalog#285's head announced
- 2026-10-14 00:22 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-14 00:22 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-14 05:06 ready → proposed (trunk moved): out of date: judged against trunk ebf7f3773dc7, the trunk is now e846c289c878; judged again on a recheck or when it is merged
- 2026-10-14 13:05 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
- 2026-10-14 13:05 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-14 13:07 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-14 13:07 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
- 2026-10-14 13:07 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Merge check at 4ded07bc
Judged: head 4ded07bc635f merged into trunk ebf7f3773dc7; read beside it: mesh-controller ca3f43c400ba, mesh-controller-main ca3f43c400ba, mesh-host 1a2677882380, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
tier 0: mailu
novox: waits for a person: mailu
mailu waits for a person: its policy records (module)
mailu provides smtp: its consumers are sent again after it
mailu keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on ace as build-1791937214253059941 (builds --log build-1791937214253059941).
--- the gate: mailu
mailu: ok, keeps mail (valuable), dkim (valuable), data (valuable), dav (valuable), webmail (valuable), queue (valuable), filter (rebuildable), certs (rebuildable), automx (rebuildable), fetchmail (rebuildable), clamav (cache), redis (cache), ready: resolver by its image's own check every 30s; admin by its image's own check every 30s; imap by its image's own check every 30s; smtp by its image's own check every 30s; antispam by its image's own check every 30s; webmail by its image's own check every 30s; webdav by its image's own check every 30s; fetchmail by its image's own check every 30s; front by exec every 1m0s; WARNING: config-env ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: redis, antivirus, automx stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 3
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T23:54:14Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T23:54:14Z
modules the change moves:
- mailu (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mailu
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 46786 strings of 718 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.190s
modules/mailu is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/mailu (TypeScript)
testing modules/mailu (TypeScript)
✔ the vault's apiKey is used, a setting of it is ignored and said once, without its value (1.892196ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 69.515778
**Merge check** at `4ded07bc`
Judged: head `4ded07bc635f` merged into trunk `ebf7f3773dc7`; read beside it: mesh-controller `ca3f43c400ba`, mesh-controller-main `ca3f43c400ba`, mesh-host `1a2677882380`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
- tier 0: mailu
- novox: waits for a person: mailu
- mailu waits for a person: its policy records (module)
- mailu provides smtp: its consumers are sent again after it
- mailu keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on ace as `build-1791937214253059941` (`builds --log build-1791937214253059941`).
```
--- the gate: mailu
mailu: ok, keeps mail (valuable), dkim (valuable), data (valuable), dav (valuable), webmail (valuable), queue (valuable), filter (rebuildable), certs (rebuildable), automx (rebuildable), fetchmail (rebuildable), clamav (cache), redis (cache), ready: resolver by its image's own check every 30s; admin by its image's own check every 30s; imap by its image's own check every 30s; smtp by its image's own check every 30s; antispam by its image's own check every 30s; webmail by its image's own check every 30s; webdav by its image's own check every 30s; fetchmail by its image's own check every 30s; front by exec every 1m0s; WARNING: config-env ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: redis, antivirus, automx stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 3
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-13T23:54:14Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-13T23:54:14Z
modules the change moves:
- mailu (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mailu
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.002s
words: 12 retired words and 70 daemons' lines, none in the 46786 strings of 718 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.012s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.012s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.006s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 66, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.190s
modules/mailu is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/mailu (TypeScript)
testing modules/mailu (TypeScript)
✔ the vault's apiKey is used, a setting of it is ignored and said once, without its value (1.892196ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 69.515778
```
Merge check at 4ded07bc
Judged: head 4ded07bc635f merged into trunk b15846dd6e78; read beside it: mesh-controller 535c7641ac41, mesh-controller-main 535c7641ac41, mesh-host 1a2677882380, mesh-tools 954e1a53eef9
mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)
mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
Delivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
tier 0: mailu
novox: waits for a person: mailu
mailu waits for a person: its policy records (module)
mailu provides smtp: its consumers are sent again after it
mailu keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as build-1791982919914284993 (builds --log build-1791982919914284993).
--- the gate: mailu
mailu: ok, keeps mail (valuable), dkim (valuable), data (valuable), dav (valuable), webmail (valuable), queue (valuable), filter (rebuildable), certs (rebuildable), automx (rebuildable), fetchmail (rebuildable), clamav (cache), redis (cache), ready: resolver by its image's own check every 30s; admin by its image's own check every 30s; imap by its image's own check every 30s; smtp by its image's own check every 30s; antispam by its image's own check every 30s; webmail by its image's own check every 30s; webdav by its image's own check every 30s; fetchmail by its image's own check every 30s; front by exec every 1m0s; WARNING: config-env ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: redis, antivirus, automx stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 3
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-14T12:37:05Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-14T12:37:05Z
modules the change moves:
- mailu (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mailu
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.030s
words: 12 retired words and 70 daemons' lines, none in the 46860 strings of 718 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.016s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 62, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.442s
modules/mailu is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/mailu (TypeScript)
testing modules/mailu (TypeScript)
✔ the vault's apiKey is used, a setting of it is ignored and said once, without its value (2.471855ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 102.341833
**Merge check** at `4ded07bc`
Judged: head `4ded07bc635f` merged into trunk `b15846dd6e78`; read beside it: mesh-controller `535c7641ac41`, mesh-controller-main `535c7641ac41`, mesh-host `1a2677882380`, mesh-tools `954e1a53eef9`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
**Delivery plan** — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
- tier 0: mailu
- novox: waits for a person: mailu
- mailu waits for a person: its policy records (module)
- mailu provides smtp: its consumers are sent again after it
- mailu keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as `build-1791982919914284993` (`builds --log build-1791982919914284993`).
```
--- the gate: mailu
mailu: ok, keeps mail (valuable), dkim (valuable), data (valuable), dav (valuable), webmail (valuable), queue (valuable), filter (rebuildable), certs (rebuildable), automx (rebuildable), fetchmail (rebuildable), clamav (cache), redis (cache), ready: resolver by its image's own check every 30s; admin by its image's own check every 30s; imap by its image's own check every 30s; smtp by its image's own check every 30s; antispam by its image's own check every 30s; webmail by its image's own check every 30s; webdav by its image's own check every 30s; fetchmail by its image's own check every 30s; front by exec every 1m0s; WARNING: config-env ask(s) for a setting and do(es) not say whether it is trusted, so it counts as trusted: set at the terminal alone; say "trusted" false where nothing trusts it (novox/hq issue 339); WARNING: redis, antivirus, automx stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 3
files asking for a setting without saying whether it is trusted: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 1 module(s) changed against 4 machine(s), as the snapshot of 2026-10-14T12:37:05Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-14T12:37:05Z
modules the change moves:
- mailu (changed)
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 1 module(s) in 1 tier(s): mailu
--- its merge-check.sh (go toolchain)
ok git.novox.be/novox/mesh-catalog/checks/words 0.030s
words: 12 retired words and 70 daemons' lines, none in the 46860 strings of 718 files
ok git.novox.be/novox/mesh-catalog/checks/agenthome 0.016s
agent-home: no archive and no file written into under the agent's home
ok git.novox.be/novox/mesh-catalog/checks/secrets 0.013s
secrets: no setting can replace a vault secret in a mergeable file (1 known, nested)
ok git.novox.be/novox/mesh-catalog/checks/paths 0.007s
paths: no module names another's state on its machine (3 known, listed to go)
long-running resources without health: 62, as kept
ok git.novox.be/novox/mesh-catalog/checks/deps 0.442s
modules/mailu is TypeScript: merge-check-typescript.sh type-checks and tests it
--- its merge-check-typescript.sh (typescript toolchain)
type-checking modules/mailu (TypeScript)
testing modules/mailu (TypeScript)
✔ the vault's apiKey is used, a setting of it is ignored and said once, without its value (2.471855ms)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 102.341833
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of hq issue 688 (mailu stopped resolving and handling mail and the mesh raised nothing; the operator found it).
What was seen
On 2026-10-14 mailu on novox could not resolve any name, so inbound mail was refused (450) and outbound could not leave, while all 13 containers stayed
healthyand no condition was raised. Two checks lied:dig @127.0.0.1exits 0 on SERVFAIL: fixed by moving to Mailu 2024.06.63 in #284 (hq issue 687), whose image check fails on anything but NOERROR;curl http://127.0.0.1:10204/health && kill -0 dovecot: it never looks at mail.Change
mailu-front'shealthgoes fromruntimetoexec(ADR 0240), keeping the image's two tests and adding:python3 smtplibconnects to 127.0.0.1:25, EHLO, QUIT (no mail sent). Port 25 is a plain listener here (noPROXY_PROTOCOL_25in mailu.env).openssl x509 -checkend 1209600on/certs/letsencrypt/live/mailu/nginx-chain.pem, the file Mailu'sconfig.pyserves forTLS_FLAVOR=letsencrypt. certbot renews at 30 days left, so this says a renewal stuck for 16 days (as it is today while DNS fails) well before expiry. Currently served: CN=mail.novox.be, notAfter 2026-12-25.Timing: every 60 s, timeout 15 s, 3 looks, grace 120 s (120 + 180 = 300 s, within ADR 0240's five minutes).
Checked in the pinned image (
ghcr.io/mailu/nginx@sha256:36f98897…, run locally): curl,/app/venv/bin/python3with smtplib,/usr/bin/openssl(3.3.7), kill and cat are present.--health-cmdruns in the container's shell, so$(…)and&&work.Effect on the operator
mailu's upgrade policy is
record: this waits for a person's go and recreatesmailu-front(seconds without IMAP/SMTP). A failure then raisesmodule.mailu.novox.unhealthy("mailu not working on novox"), a warning, said on the desktop.Not in this pull request
module_health.go: every module unhealthy is a warning); asked of the operator.Acceptance
After the walk,
docker_inspect mailu-frontshows the new Healthcheck andhealthy; with port 25 or the certificate broken it turns unhealthy within five minutes andmesh-controller.conditionsholdsmodule.mailu.novox.unhealthy.Delivery
novox/mesh-catalog@4ded07bc635f— delivered since 2026-10-14T13:07:52ZDelivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
Transitions
ebf7f3773d, the trunk is now e846c289c878; judged again on a recheck or when it is mergedThe commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
4ded07bcJudged: head
4ded07bc635fmerged into trunkebf7f3773dc7; read beside it: mesh-controllerca3f43c400ba, mesh-controller-mainca3f43c400ba, mesh-host1a2677882380, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
Run by the build seat on ace as
build-1791937214253059941(builds --log build-1791937214253059941).Merge check at
4ded07bcJudged: head
4ded07bc635fmerged into trunkb15846dd6e78; read beside it: mesh-controller535c7641ac41, mesh-controller-main535c7641ac41, mesh-host1a2677882380, mesh-tools954e1a53eef9mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mailu)mesh/repo-check: PASS — its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)Delivery plan — builds mailu, sent nowhere; no bus step; 1 wait(s) for a person
Run by the build seat on novox as
build-1791982919914284993(builds --log build-1791982919914284993).