test-email: a test message from Server › Health
Decided by the owner on 2026-10-13 (out of preflight); the sender is his
choice of 2026-10-16 ("Да, адрес годится"); mockups e92769f, e3b2501,
8f0ccf9, the route guard 75f97c9.
Card 09 on Health, in the right stack's static end (the left stack is
polled whole): one recipient and Send. HandleTestMessage (global role)
takes one address (validate.AccountEmail - no list, nothing that reads as
an option), at most ten an hour counted from the send log, writes the
message's send-log row first (store.InsertTestMessage: queued, the
hostname as domain, no application, no queue id), awaits its Message-ID
(logtail.AwaitMessageID) and hands it to sendmail as notifications go
(notify.Mailer.SendWithID: postmaster@ the hostname, signed by the local
socket with the key of the server's own name). The browser lands on the
row; the tailer gives it its queue id from cleanup's message-id= line
(src/cleanup/cleanup_message.c:639) and from then on its delivery lines
its status. Postfix refusing the hand-over removes the row; sendmail out
of time keeps it (postdrop may have queued it). The sweep closes a row
whose id was never seen once it is no longer awaited
(CloseUnmatchedTestMessages); an id awaited past ten minutes is not taken.
Reviewed for security before the commit (2026-10-17): no high or medium;
its three low findings are fixed in this commit (the unmatched row's
close, the awaited id's age, the timeout keeping the row) and the
security.md bullet says how the queue id is matched.
Tests: TestTestMessage (refusals, the row and the page, Postfix refusing,
the hourly ten), TestTestMessageTimeoutKeepsTheRow, TestTestMessageRows,
TestCloseUnmatchedTestMessages, TestAwaitedMessageID,
TestRunGivesTheTestMessageItsQueueID, TestHealthTestMessageCard; e2e sends
one to the sink: the row is sent with a long queue id, the sink has it
signed d=mail.e2e.test. go vet + go test (26 packages) and make e2e on the
test server for this tree.
Docs: help (word for word with the mockup), guide § Overview and Health,
architecture § Log tailer, security.md, CHANGELOG [Unreleased] § Added,
roadmap row done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -590,6 +590,19 @@ inbound journal's. The tail of a line is the far end's words (a reply, a HELO
|
||||
name), and a pattern that could start there let a stranger's HELO name file a
|
||||
delivery (audit 2026-10-12, M12).
|
||||
|
||||
**The test message** (test-email, `2.12.0`) is the one row the panel writes
|
||||
itself: Server › Health hands it to `sendmail(1)`, which tells nothing of the
|
||||
queue id, and the journal-milter does not see local mail. The row is written
|
||||
first, queued and with no queue id, and its Message-ID is awaited
|
||||
(`logtail.AwaitMessageID`, in memory, ten minutes); cleanup's line
|
||||
`<id>: message-id=<…>` (Postfix 3.10.13, `src/cleanup/cleanup_message.c:639`)
|
||||
gives the row its queue id, and from then on its delivery lines update it as
|
||||
any row's. A restart in the second between leaves it without one, and the
|
||||
reconcile sweep closes it once the id is no longer awaited
|
||||
(`CloseUnmatchedTestMessages`); an id awaited longer than that is not taken
|
||||
any more. sendmail out of time keeps the row and the id - postdrop may have
|
||||
queued the message all the same.
|
||||
|
||||
**The upstream's answer** on the inbound journal (inbound-upstream-result,
|
||||
`2.10.0`). Mail accepted on port 25 is handed to its upstream by the same
|
||||
`smtp` client as outbound mail, and its delivery lines look alike; what tells
|
||||
|
||||
@@ -514,6 +514,14 @@ readings, in the order of what stops mail first:
|
||||
if the daemons drifted from what the panel shows after manual edits under
|
||||
`/data`. A step that fails is named on the page, and the panel's log says
|
||||
why.
|
||||
- **Test message** — sends one message to an address you type, from
|
||||
`postmaster@` the hostname, the way notifications go (through Postfix,
|
||||
signed with the key of the server's own name). The page that opens is its
|
||||
row in **Outbound › Log**, which follows it to the receiving server: sent,
|
||||
put off or refused, with the lines of `mail.log`. It tests the server's
|
||||
own path - TLS, its address, the records of its name - not a domain's,
|
||||
which only an application's mail does; and whether it reached the inbox
|
||||
only the mailbox can say. Ten an hour, global role.
|
||||
|
||||
### Queue and System log
|
||||
|
||||
|
||||
+8
-1
@@ -30,7 +30,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
|
||||
|---|---|---|---|---|
|
||||
| inbound-upstream-result | Inbound › Log shows what the upstream answered: sent, deferred, bounced, with its last reply | shipped `2.10.0` | 7/7 | [plans/inbound-upstream-result.md](plans/inbound-upstream-result.md) |
|
||||
| health-setup-checks | Server › Health also checks the certificate's name, Postfix's greeting and the trusted proxy (was preflight) | shipped `2.11.0` | — | — |
|
||||
| test-email | Send a test message from the panel (was preflight) | agreed (2026-10-13) | — | — |
|
||||
| test-email | Send a test message from the panel (was preflight) | done 2026-10-17, unreleased | — | — |
|
||||
| inbound-antispam-panel | Inbound antispam journal + allow/deny lists | shipped `2.1.0` | 12/12 | [plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) |
|
||||
| origin-strict | Refuse a state-changing request that carries neither `Sec-Fetch-Site` nor `Origin` (was csrf-tokens) | shipped `2.10.4` | — | — |
|
||||
| backup-reauth | Ask for the account password before a backup or a domain export is downloaded | shipped `2.10.3` | — | — |
|
||||
@@ -211,6 +211,13 @@ the mockup; mockup-first.
|
||||
rate-limited and refuses a recipient list, and it passes a security review -
|
||||
a form that sends mail is a new surface.
|
||||
|
||||
**Done (2026-10-17):** from `postmaster@` the hostname (the owner,
|
||||
2026-10-16: "Да, адрес годится"), card 09 on Health in the static stack
|
||||
(mockups `e92769f`, `e3b2501`, `8f0ccf9`). The row is written before the
|
||||
hand-over and learns its queue id from cleanup's Message-ID line
|
||||
(`logtail.AwaitMessageID`); the browser lands on it. One address
|
||||
(`validate.AccountEmail`), ten an hour from the send log, global role.
|
||||
|
||||
**Version:** `2.x` MINOR; `agreed`.
|
||||
|
||||
---
|
||||
|
||||
@@ -686,6 +686,22 @@ deferred item from the roadmap.
|
||||
a dozen times a day at most by someone who flaps the zone; a domain's
|
||||
limits once a day; and no more than 60 messages leave in any hour whatever
|
||||
the rules arrive at.
|
||||
- **The test message is a form that sends mail** (test-email, `2.12.0`):
|
||||
Server › Health sends one message to an address a global user types. It is
|
||||
bounded as notifications are - the same `sendmail(1)` call, a fixed argument
|
||||
list, the address after `--` - and further: the global role only; one
|
||||
address, held by `validate.AccountEmail` (no list, no separator, nothing
|
||||
that reads as an option, any public host); a fixed subject and body the
|
||||
panel composes; ten an hour, counted from the send log whoever asks; the
|
||||
origin check of every form. It is written to the Outbound log before it
|
||||
goes, so each one is on record with its recipient and who asked is in the
|
||||
panel's log. It cannot be made to say anything but the panel's own text,
|
||||
and the sender is always `postmaster@` the hostname. Its row learns its
|
||||
queue id from the head of cleanup's `message-id=` line; cleanup logs a
|
||||
client's own Message-ID the same way, but the panel's is 96 random bits,
|
||||
known to nobody before the line is read, taken once and not after ten
|
||||
minutes - a message of someone else's cannot take the row (security
|
||||
review, 2026-10-17).
|
||||
- **Whatever is submitted with sendmail(1) is signed as the server.** The
|
||||
signer's local socket, the whole of `non_smtpd_milters`, signs every
|
||||
message submitted locally with the key of the server's own name, whatever
|
||||
|
||||
Reference in New Issue
Block a user