message page: a row the sweep closed says the panel closed it (stage 2b)

The History step of every bounced row said the receiving server refused
the message permanently or Postfix gave up, "the reason is in the delivery
log below" - for a row the reconcile sweep closed (reason "reconcile")
neither is known and the log has nothing (review of stage 1). Such a row's
step is now "Closed by the panel - Postfix no longer held the message and
mail.log never said what became of it ... rather than show a delivery it
cannot evidence." The help topic log names the case (design df96690).
Pictured for the owner with the real templates (dev/shots); the variant has
no mockup screen of its own, as the refused one.

Test: the page of a row the sweep closed says "Closed by the panel" and not
"the receiving server refused".

CHANGELOG [Unreleased], audit plan line ticked, roadmap 29/62.
Evidence: the message-page tests of ./internal/web/handlers ok locally; the
server run follows before the push.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-14 17:18:15 +03:00
co-authored by Claude Opus 5.5
parent ac985d03a8
commit 415334c2cc
5 changed files with 31 additions and 2 deletions
@@ -211,6 +211,12 @@ func deliveryEvents(row store.SendLogRow, policy postfix.RetryPolicy, sendsAs vi
fmt.Sprintf("The receiving server could not take the message yet. Postfix retries: first after %s, then with increasing gaps up to %s, for up to %s. There is no fixed attempt count — a deferred message stays in the queue until it is delivered or that lifetime runs out.",
policy.FirstRetry(), policy.BackoffCap(), policy.QueueLifetime())))
case store.StatusBounced:
if row.Reason == store.ReasonReconcile {
// Neither the receiving server nor Postfix said so: the panel
// closed the row (audit 2026-10-12, stage 2b).
return append(events, step(row.UpdatedAt, "error", "Closed by the panel",
"Postfix no longer held the message and mail.log never said what became of it - the log rotated away while the panel was down, or could not be read. The panel closed it as a failure rather than show a delivery it cannot evidence."))
}
return append(events, step(row.UpdatedAt, "error", "Bounced",
fmt.Sprintf("Delivery failed for good: the receiving server refused the message permanently, or Postfix gave up after %s in the queue. The reason is in the delivery log below.",
policy.QueueLifetime())))
@@ -186,3 +186,21 @@ func TestARefusedMessageShowsPostfixsLine(t *testing.T) {
}
}
}
// A message the reconcile sweep closed does not say a receiving server
// refused it: neither did (audit 2026-10-12, stage 2b).
func TestAMessageTheSweepClosedSaysSo(t *testing.T) {
h, st := inboundHandlers(t)
root := userWith(t, st, "root", store.RoleGlobal, store.Reach{})
if err := st.InsertQueued(store.SendLogEntry{QueueID: "GONEQ", Domain: "example.org", AppLogin: "a", To: "x@example.net", Subject: "lost"}); err != nil {
t.Fatal(err)
}
rows, _ := st.QuerySendLog(store.SendLogFilter{AllDomains: true}, 1, 0)
if _, err := st.CloseUnreported(rows[0].ID); err != nil {
t.Fatal(err)
}
body := send(h.HandleDelivery, &root, "GET", "/outbound/log/"+idStr(rows[0].ID), map[string]string{"id": idStr(rows[0].ID)}, nil).Body.String()
if !strings.Contains(body, "Closed by the panel") || strings.Contains(body, "the receiving server refused") {
t.Fatalf("the page of a row the sweep closed:\n%s", body)
}
}