Show this Postfix's retry policy on Mail queue and delivery history.
test / test (push) Waiting to run

Numbers come from a one-shot postconf -h at panel start so a manual override is visible after restart, without inventing an attempt count.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 22:12:03 +03:00
parent 46191172d6
commit 705239568b
17 changed files with 669 additions and 30 deletions
+4
View File
@@ -663,6 +663,10 @@ h1.subject { overflow-wrap: anywhere; }
wider than the column it shares. */
.fact-value { display: block; margin-top: 0.1rem; overflow-wrap: anywhere; }
.fact-value.mono { font-family: var(--font-mono); font-size: 0.85rem; }
/* Retry-policy tiles carry a short phrase (`doubling, cap about 1 hour 7
minutes`) rather than a domain or a queue id, so they need a slightly
wider minimum than the delivery-page facts before wrapping to one column. */
.facts.retry-facts { grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
/* A message's history: the steps down a line, each with the time it happened,
the status it reached and what that means. The line is the list's own left
border and the dots sit on top of it, so nothing has to be positioned against
+32 -2
View File
@@ -1,10 +1,40 @@
{{/* A table of queue ids, sizes, senders and recipients: the whole column
rather than the reading measure (see the "wide" block in layout.html). */}}
{{/* Mail queue is two cards in the whole column: the retry-policy snapshot
(static, outside the HTMX poll) and the live postqueue listing. */}}
{{define "wide"}}wide{{end}}
{{define "content"}}
<h1>Mail queue</h1>
<div class="card" id="retry-policy">
<h2>How delivery retries work</h2>
<p class="muted">This Postfix's policy, read once at panel start. There is
no maximum attempt count — only time. A deferred message stays in this
listing until it is delivered or the queue lifetime runs out.</p>
{{if .RetryFromDefaults}}
<p class="muted">Could not read the effective Postfix configuration;
showing compiled-in defaults. A live change is visible after the next
panel restart.</p>
{{end}}
<div class="facts retry-facts">
<div class="fact">
<span class="fact-label">First retry</span>
<span class="fact-value">{{.FirstRetry}}</span>
</div>
<div class="fact">
<span class="fact-label">Later retries</span>
<span class="fact-value">doubling, cap {{.BackoffCap}}</span>
</div>
<div class="fact">
<span class="fact-label">Kept in queue</span>
<span class="fact-value">{{.QueueLifetime}}</span>
</div>
<div class="fact">
<span class="fact-label">Then</span>
<span class="fact-value">bounced</span>
</div>
</div>
</div>
<div class="card">
<h2>Pending messages</h2>
{{template "mail_queue_body" .}}
+36
View File
@@ -527,3 +527,39 @@ func forEachTemplate(t *testing.T, fn func(name, body string)) {
fn(e.Name(), string(body))
}
}
// The retry-policy card is static HTML on mail_queue (outside the HTMX
// fragment), so rendering the page with a fixture must print those strings
// rather than falling back to empty template fields.
func TestMailQueuePageRendersRetryPolicy(t *testing.T) {
engine, err := New("test")
if err != nil {
t.Fatalf("New: %v", err)
}
var buf bytes.Buffer
if err := engine.Page("mail_queue").ExecuteTemplate(&buf, "layout.html", map[string]any{
"Title": "t", "User": "admin", "Active": "mail_queue", "Version": "test",
"Copyright": "Copyright © 2026 Mikhail Yenuchenko",
"SourceURL": "https://github.com/mixeme/selfpost",
"FirstRetry": "10 minutes", "BackoffCap": "about 1 hour 7 minutes",
"QueueLifetime": "2 days",
}); err != nil {
t.Fatalf("execute mail_queue: %v", err)
}
out := buf.String()
for _, want := range []string{
"How delivery retries work",
"10 minutes",
"about 1 hour 7 minutes",
"2 days",
`id="retry-policy"`,
`hx-get="/mail-queue/body"`,
} {
if !strings.Contains(out, want) {
t.Errorf("mail_queue is missing %q:\n%s", want, out)
}
}
if strings.Contains(out, "compiled-in defaults") {
t.Error("RetryFromDefaults was unset; the fallback note should stay off")
}
}