From 295d72e7210d6537e27fdbcdcadd304cdf12e3b2 Mon Sep 17 00:00:00 2001 From: Mix Date: Fri, 16 Oct 2026 14:11:06 +0300 Subject: [PATCH] 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 --- docs/plans/inbound-upstream-result.md | 66 +++++++++++++++++++++++++++ docs/roadmap.md | 1 + 2 files changed, 67 insertions(+) create mode 100644 docs/plans/inbound-upstream-result.md diff --git a/docs/plans/inbound-upstream-result.md b/docs/plans/inbound-upstream-result.md new file mode 100644 index 0000000..ea747b7 --- /dev/null +++ b/docs/plans/inbound-upstream-result.md @@ -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 `: to=<…>, relay=[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`. diff --git a/docs/roadmap.md b/docs/roadmap.md index 23e3f51..77e5d0d 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -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) |