plan: inbound-upstream-result - the upstream's answer in the Inbound log

The owner, 2026-10-16: the Inbound log lacks the upstream's answer; the
result in the row only, a page per message later if needed. One stage,
2.10.0: migration 0012, the milter keeps the queue id, the tailer hands
port-25 delivery lines to the journal, an Upstream column (mockup 5348330).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mix
2026-10-16 14:11:06 +03:00
co-authored by Claude Opus 5.5
parent 5348330d2b
commit 295d72e721
2 changed files with 67 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
# inbound-upstream-result
**Status:** agreed 2026-10-16; one stage, `2.10.0`.
## Problem
The owner, 2026-10-16: "Нам не хватает в журнале для Inbound ответа
upstream. Не понятна судьба письма: принято, отклонено, хост не доступен и
пр."
Inbound › Log shows what port 25 decided - accept, reject, tempfail - and
nothing after it. Its help says so ("Accept says what this server did, not
what the upstream did with the message afterwards"). The answer is in
`mail.log`: Postfix's `smtp` client logs every attempt to hand a message to
its upstream as `<id>: to=<…>, relay=<host>[ip]:port, …, dsn=…, status=sent
(250 …)` - or `deferred (451 4.7.1 Greylisted …)`, `deferred (connect to …:
Connection refused)` with `relay=none`, `bounced (… said: 550 …)` (Postfix
3.10.13, `src/global/log_adhoc.c`; `relay=none` when there was no session,
`src/smtp/smtp_trouble.c:273, 421`). The tailer reads those lines for the
outbound log and, finding no send-log row for a port-25 queue id, drops
them; the journal row does not even hold the queue id.
## Decision
The owner, asked whether the result goes in the row or on a page of its own
per message: "Пока только результат. Отдельная страница в план в случае
появления потребности." So: the row carries the result and the last reply;
no stored `mail.log` lines for port-25 mail (delivery-log-storage left them
out on purpose: a stranger sets the rate). A page per inbound message, with
every line, is for later, if the result is not enough.
## Design
- **Schema, migration 0012:** `inbound_spam_log` gains `queue_id`,
`upstream_status` (`''`, `queued`, `sent`, `deferred`, `bounced`),
`upstream_relay`, `upstream_reply` and `upstream_at`; an index on
`queue_id` for rows that have one. Rows written before have none and show
"—".
- **The milter** writes an accepted row with the queue id it already reads
at end-of-message (`{i}`, for the filter's signature) and `queued`. The
row is in the database before the message is queued, so before any
delivery line.
- **The tailer:** a delivery line whose queue id has no send-log row is
offered to the journal: the rows of that queue id and recipient take the
status, the relay (empty for `none`), the reply (printable, at most 255
bytes) and the line's time. One update per attempt, no new row; the
journal's caps stand. qmgr's `status=expired, returned to sender` makes a
queued or deferred row `bounced`, its reply saying it expired after the
last one.
- **The page:** an Upstream column after Decision (mockup `5348330`): a
plain `queued` tag, or a tag opening an "Upstream" sheet - host, reply,
when, and what follows for deferred and bounced. Refused rows "—".
- **Docs:** help (word for word with the mockup), guide § Inbound log,
architecture (journal and tailer), security (what is stored from the
upstream and at what rate), CHANGELOG.
## Steps
- [ ] Migration 0012, store: write the queue id, update by queue id and
recipient, read the result back.
- [ ] Milter: the queue id and `queued` on accepted rows.
- [ ] Tailer: delivery and expiry lines of port-25 mail to the journal.
- [ ] View, template, help; tests (store, milter, tailer, view).
- [ ] e2e: a relayed message's row shows `sent` with the sink's `250`; an
upstream that refuses connections shows `deferred` with the reason.
- [ ] Docs; roll out, read the server's journal; cut `2.10.0`.
+1
View File
@@ -28,6 +28,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| ID | Topic | Status | Progress | Plan |
|---|---|---|---|---|
| inbound-upstream-result | Inbound › Log shows what the upstream answered: sent, deferred, bounced, with its last reply | agreed (2026-10-16) | 0/6 | [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) | agreed (2026-10-13) | — | — |
| test-email | Send a test message from the panel (was preflight) | agreed (2026-10-13) | — | — |
| 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) |