docs: align operator and as-built docs with code after 1.9.1.
test / test (push) Waiting to run

Port 25, DMARC routes, restore Resync vs reload, image pin, and roadmap
index were stale relative to the running panel and Postfix config.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-19 00:54:11 +03:00
parent 4a51db277b
commit 72126b63af
7 changed files with 111 additions and 55 deletions
+13
View File
@@ -5,6 +5,19 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased]
### Changed
- Operator and as-built docs aligned with the code after a full pass —
[architecture.md](docs/architecture.md) (port 25 opens for inbound relay
and/or DMARC ingest; antispam requires inbound relay; DMARC pipe transport
and map paths; `/help` and `/dmarc*` routes; four panel roles including
rate-limit recalc; restore Resync vs manual reload); [guide.md](docs/guide.md)
and [README.md](README.md) (image pin `1.9.1`, port 25 conditions, combined
listener limits, reload vs Resync); [roadmap.md](docs/roadmap.md) (`panel-docs`
closed at `[1.8.0]`, next item inbound-antispam-panel); [development.md](docs/development.md)
version-cut table through `1.9.1`; [schema-migrations.md](docs/schema-migrations.md)
last-updated note. No behaviour change.
## [1.9.1] - 2026-08-19
Port-25 Postfix startup when inbound relay, DMARC ingest, or the inbound antispam
+7 -3
View File
@@ -24,6 +24,9 @@ send log and DNS checks in the panel, encrypted backups.
- Outbound SMTP (465/smtps; optional 587 submission) with per-domain DKIM signing
- Optional inbound relay on port 25 (backup-MX / forwarder; off by default)
- Optional DMARC aggregate report ingest on port 25 (off by default)
- In-panel Help drawer and `/help` page for Status and domain controls
- Per-application client IP allow-list for SMTP AUTH (optional)
- Web panel — domains, applications, deliveries, mail queue, system log, backup
- Multi-domain relay — each SASL application is bound to one sending domain
- DNS status checks (PTR, SPF, DKIM, DMARC) with in-panel re-check
@@ -108,7 +111,7 @@ docker run --rm -d --name selfpost-try \
-e SELFPOST_HOSTNAME=mail.local.test \
-e PANEL_COOKIE_SECURE=false \
-v selfpost-try-data:/data \
ghcr.io/mixeme/selfpost:1.7.0
ghcr.io/mixeme/selfpost:1.9.1
```
**Get the setup URL** (pick one):
@@ -149,8 +152,9 @@ deployment](docs/guide.md#full-deployment) section, with proxy-specific
commands under [Reverse proxy](docs/guide.md#reverse-proxy-mandatory).
The compose file always publishes **465**, **587**, and **25**; Postfix listens
on 587 only when `SUBMISSION_ENABLE=true`, and on 25 only when
`INBOUND_RELAY_ENABLE=true` (see [Ports](docs/guide.md#ports)). Bump the
on 587 only when `SUBMISSION_ENABLE=true`, and on 25 when
`INBOUND_RELAY_ENABLE=true` and/or `DMARC_REPORTS_ENABLE=true` (see
[Ports](docs/guide.md#ports)). Bump the
pinned image tag deliberately when upgrading, never `:latest` ([why](docs/guide.md#fixed-image-tag)). Optional
variables (`TRUSTED_PROXY_CIDR`, rate limits, retention): see [Environment
variables](docs/guide.md#environment-variables).
+51 -24
View File
@@ -27,7 +27,7 @@ Managed programs ([build/supervisord.conf](../build/supervisord.conf)):
| Program | User | Priority | Role |
|---|---|---|---|
| `opendkim` | root → `opendkim` (`UserID` in opendkim.conf) | 100 | DKIM signing milter |
| `panel` | panel | 200 | HTTP UI + journal-milter + log-tailer goroutine |
| `panel` | panel | 200 | HTTP UI + journal-milter + log-tailer + rate-limit recalc |
| `postfix` | root (wrapper) | 300 | MTA — started only after both milter sockets exist |
| `postfix-reload` | root | — | On-demand `postfix reload` (autostart off) |
| `cert-reload` | root | 400 | Daily `postfix reload` for renewed TLS certs |
@@ -54,30 +54,40 @@ Client ──TLS+SASL──► Postfix (465 smtps, optional 587 submission)
├─► journal-milter (send log + L2 rate limits, fail-open)
└─► outbound MX delivery (port 25 client)
Internet ──► Postfix smtp inet :25 (only when INBOUND_RELAY_ENABLE=true)
Internet ──► Postfix smtp inet :25 (when INBOUND_RELAY_ENABLE=true
│ and/or DMARC_REPORTS_ENABLE=true)
├─► optional antispam milter (inbound only)
─► smtp:[upstream]:port (transport_maps; no local delivery)
├─► optional antispam milter (inbound relay only)
─► DMARC aggregate ingest (pipe → dmarc-ingest)
└─► smtp:[upstream]:port (inbound relay only;
transport_maps; no local delivery)
```
The inbound listener is **absent** when the flag is off (`postconf -MX smtp/inet`
removes Debian's stock smtpd). Outbound delivery still uses the `smtp unix`
client; it is not the same service.
The smtp/inet listener is **absent** when both flags are off (`postconf -MX
smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
`smtp unix` client; it is not the same service.
### Postfix ([build/postfix-config.sh](../build/postfix-config.sh))
- **465/smtps** — implicit TLS, SASL required; primary listener.
- **587/submission** — only when `SUBMISSION_ENABLE=true`; STARTTLS with
`smtpd_tls_security_level=encrypt`.
- **25/smtp inet** — only when `INBOUND_RELAY_ENABLE=true`. No SASL, no
OpenDKIM, no journal-milter. Accepts only `relay_domains` +
`relay_recipient_maps` (`reject_unauth_destination`,
`reject_unlisted_recipient`). Maps under `/data/postfix/`
(`relay_domains`, `transport`, `relay_recipients`, `tls_policy`), written
atomically by [internal/postfix/inbound.go](../internal/postfix/inbound.go).
Domains with an empty upstream host are omitted from the maps. Optional
`INBOUND_ANTISPAM_MILTER` on this listener only; default
`milter_default_action` is fail-open (`accept`).
- **25/smtp inet** — when `INBOUND_RELAY_ENABLE=true` and/or
`DMARC_REPORTS_ENABLE=true`. No SASL, no OpenDKIM, no journal-milter.
**Inbound relay** (`INBOUND_RELAY_ENABLE=true`): accepts only
`relay_domains` + `relay_recipient_maps` (`reject_unauth_destination`,
`reject_unlisted_recipient`). Maps under `/data/postfix/` (`relay_domains`,
`transport`, `relay_recipients`, `tls_policy`), written atomically by
[internal/postfix/inbound.go](../internal/postfix/inbound.go). Domains with
an empty upstream host are omitted from the maps. Optional
`INBOUND_ANTISPAM_MILTER` only when inbound relay is enabled (not on
DMARC-only port 25); default `milter_default_action` is fail-open
(`accept`). **DMARC ingest** (`DMARC_REPORTS_ENABLE=true`): allow-listed
report addresses via `check_recipient_access` on `postfix/dmarc_recipients`;
messages pipe to `dmarc-ingest` (symlink to `panel`). When both flags are
on, one listener serves both paths; per-IP rate uses
`INBOUND_RATE_LIMIT_MESSAGES_PER_IP` and message size uses the greater of
`INBOUND_MESSAGE_SIZE_LIMIT` and `DMARC_MESSAGE_SIZE_LIMIT`.
- **No open relay** — `permit_sasl_authenticated`, `reject_unauth_destination`;
`smtpd_sender_login_maps` + `reject_sender_login_mismatch`.
- **Level-1 rate limit** — `smtpd_client_message_rate_limit` /
@@ -93,7 +103,7 @@ by the panel. Socket `/run/opendkim/opendkim.sock`.
### Panel binary ([cmd/panel](../cmd/panel))
One process, three roles:
One process, four roles:
1. **HTTP server**`:8080` (`PANEL_HTTP_ADDR`); HTTPS terminated by reverse
proxy only. On start it runs `postconf -h` once for the deferred-mail retry
@@ -116,6 +126,8 @@ One process, three roles:
3. **log-tailer** — follows `MAIL_LOG`, updates send-log delivery status by
queue-id. Send-log `queued → sent` transitions depend on this goroutine alone
(`UpdateStatus` is only called from [internal/logtail](../internal/logtail/logtail.go)).
4. **rate-limit recalc** — every six hours (and on demand from the domain
page), recomputes level-2 **Auto** rate limits from send-log statistics.
Milter chain in Postfix: OpenDKIM (tempfail) then journal (accept on failure).
@@ -200,11 +212,13 @@ below is a summary — HTMX fragment endpoints
| `/deliveries`, `/deliveries/{id}` | Send log with filters; scoped to assigned domains for domain-admins |
| `/mail-queue`, `/mail-queue/*` | **Global.** Postfix queue view; retry-policy card on the page (not the HTMX fragment) |
| `/system-log`, `/system-log/*` | **Global.** `mail.log` tail |
| `/reload` | **Global.** `POST` — reload OpenDKIM + Postfix maps |
| `/reload` | **Global.** `POST` — reload OpenDKIM tables, Postfix sender map, and inbound relay maps when enabled (not DMARC maps — see § Persistence restore) |
| `/backup`, `/backup/*` | **Global.** Full backup download (page also hosts the import form) |
| `/help` | In-panel operator help (any authenticated user) |
| `/settings` | Username/password for any user; DMARC report default is **global** only |
| `/users`, `/users/*` | **Global.** Panel user CRUD |
| `/inbound`, `/inbound/{id}`, `/inbound/{id}/*` | **Global.** Inbound relay domains. Registered only when `INBOUND_RELAY_ENABLE=true`; otherwise 404. |
| `/dmarc`, `/dmarc/reports/{id}`, `/dmarc/domains/{id}` | **Global** list and report detail; domain roll-up scoped like deliveries. Registered only when `DMARC_REPORTS_ENABLE=true`; otherwise 404. |
HTMX polling refreshes monitoring fragments (5 s while the operator is active on
the page, 30 s when the tab is visible but idle, none when hidden — scheduled in
@@ -271,6 +285,7 @@ flowchart TB
domainSvc["internal/domain"]
appSvc["internal/app"]
inboundSvc["internal/inbound"]
dmarcSvc["internal/dmarc"]
end
subgraph persistence ["Persistence"]
store["internal/store — SQLite, embedded migrations"]
@@ -293,6 +308,7 @@ flowchart TB
web --> domainSvc
web --> appSvc
web --> inboundSvc
web --> dmarcSvc
web --> backupPkg
web --> dnscheck
web --> health
@@ -300,15 +316,18 @@ flowchart TB
domainSvc --> store
appSvc --> store
inboundSvc --> store
dmarcSvc --> store
milterPkg --> store
logtail --> store
domainSvc --> postfix
appSvc --> postfix
inboundSvc --> postfix
dmarcSvc --> postfix
```
The three roles inside the `panel` process (HTTP server, journal-milter,
log-tailer goroutine) share one binary and one SQLite handle on purpose — see
The four roles inside the `panel` process (HTTP server, journal-milter,
log-tailer, rate-limit recalc) share one binary and one SQLite handle on
purpose — see
[Panel binary](#panel-binary-cmdpanel) for why, and *Persistence* below for the
single-connection trade-off that follows from it.
@@ -327,6 +346,9 @@ single-connection trade-off that follows from it.
| `postfix/transport` | Inbound next-hop `smtp:[host]:port` |
| `postfix/relay_recipients` | Inbound recipient allow-list or `@domain` catch-all |
| `postfix/tls_policy` | TLS policy for inbound next hops |
| `postfix/dmarc_recipients` | Allow-listed DMARC aggregate report addresses |
| `postfix/dmarc_transport` | Pipe transport for report ingest |
| `postfix/dmarc_relay_domains` | Domains accepted for DMARC report delivery |
| `postfix/queue/` | Postfix transit mail (deferred/active); survives container recreate |
| `log/mail.log` | Postfix delivery log + rotated copies (excluded from backups) |
| `manifest.json` | Backup version stamp (consumed on restore) |
@@ -344,10 +366,12 @@ under `/data`), `docker-compose.yml`, `.env`, and `certs/` when present;
version check on restore. Requires the project directory mounted read-only at
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` in the default compose file). On the
first successful boot after restore, the panel runs one **Resync** — OpenDKIM's
tables, Postfix's sender map, and (when `INBOUND_RELAY_ENABLE=true`) inbound
relay maps are re-derived from SQLite and both daemons are reloaded, so drift
between the extracted archive and the database is healed before mail flows
(same step as `POST /reload` on demand). Stopped-container
tables, Postfix's sender map, inbound relay maps (when
`INBOUND_RELAY_ENABLE=true`), and DMARC maps (when `DMARC_REPORTS_ENABLE=true`)
are re-derived from SQLite and both daemons are reloaded, so drift between the
extracted archive and the database is healed before mail flows. Manual
`POST /reload` on the Status page resyncs OpenDKIM, the sender map, and
inbound maps only — not DMARC maps. Stopped-container
`tar` of `./data` alone remains possible for state-only copies (see guide).
**Optional encryption** of the two secret-bearing downloads
@@ -398,6 +422,9 @@ unsupported rather than as a missing doc:
`POSTFIX_TLS_POLICY_MAPS` (`/data/postfix/tls_policy`) — same desync if
overridden without matching the panel writer in
[internal/postfix/inbound.go](../internal/postfix/inbound.go),
`POSTFIX_DMARC_RECIPIENTS` (`/data/postfix/dmarc_recipients`),
`POSTFIX_DMARC_TRANSPORT` (`/data/postfix/dmarc_transport`),
`POSTFIX_DMARC_RELAY_DOMAINS` (`/data/postfix/dmarc_relay_domains`),
`POSTFIX_QUEUE_DIR` (`/data/postfix/queue` — set in `build/postfix-config.sh`),
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` — operator project directory for
full backups; mount `.:/selfpost-deploy:ro` in compose).
+4 -1
View File
@@ -107,7 +107,7 @@ than the default «meaningful step» rule in [§ Commits and release build](#com
2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see
[§ Release image](#release-image)).
**Planned version cuts** (from pin `1.7.0`; adjust if semver changes mid-track):
**Planned version cuts** (from pin `1.9.1`; adjust if semver changes mid-track):
| Stage | ID | Cut |
|---|---|---|
@@ -117,6 +117,9 @@ than the default «meaningful step» rule in [§ Commits and release build](#com
| 4 | domain-stats-auto-ratelimit | `1.6.0` |
| 5 | dmarc-reports | `1.7.0` |
| 6 | panel-docs | `1.8.0` |
| 7 | application auth IPs | `1.9.0` |
| 8 | port-25 Postfix startup fix | `1.9.1` (PATCH) |
| 9 | inbound-antispam-panel | `1.10.0` (planned) |
Docs-only prep (checklists in plans, no product code) uses the same per-step
commit rule but **no** version cut until the next product stage ships.
+23 -17
View File
@@ -52,9 +52,10 @@ domains hosted on that instance — DNS, deliveries, rate limits, applications).
465 (smtps) is always active. Port **587** is published even when
`SUBMISSION_ENABLE=false`; nothing listens until you set it to `true`. Port
**25** is published even when `INBOUND_RELAY_ENABLE=false`; Postfix does not
accept inbound mail until you set it to `true` (see [Inbound
relay](#inbound-relay)). Harmless extra publishes can look like open ports in
external scans.
listen on port 25 until you set **`INBOUND_RELAY_ENABLE=true`** and/or
**`DMARC_REPORTS_ENABLE=true`** (see [Inbound relay](#inbound-relay) and
[DMARC reports](#dmarc-reports)). Harmless extra publishes can look like open
ports in external scans.
### Local trial
@@ -172,7 +173,7 @@ cat ./data/setup-token
#### Fixed image tag
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
deliberately never `:latest`. The current pin is `1.7.0`. Intermediate
deliberately never `:latest`. The current pin is `1.9.1`. Intermediate
CHANGELOG sections (`0.2.0``0.6.0`) record development cuts from before that
image was published. Pinning matters because of the backup version check (see
[Full backup and restore](#full-backup-and-restore)): the panel binary's
@@ -194,12 +195,12 @@ expected to set; defaults match the code exactly.
| `SUBMISSION_ENABLE` | When `true`, also listen on port 587 with STARTTLS (RFC 6409 submission) alongside the primary 465/smtps listener. | `false` | `.env` |
| `INBOUND_RELAY_ENABLE` | When `true`, accept mail on port 25 for domains configured under *Inbound* in the panel and forward them to the upstream you set. Off by default — the outbound path is unchanged. See [Inbound relay](#inbound-relay). | `false` | `.env` |
| `DMARC_REPORTS_ENABLE` | When `true`, accept DMARC aggregate reports on port 25 only for report addresses configured in the panel, parse gzip/XML, and show summaries under *DMARC*. Off by default. See [DMARC reports](#dmarc-reports). | `false` | `.env` |
| `DMARC_RATE_LIMIT_MESSAGES_PER_IP` | Per-client-IP cap on port 25 when DMARC ingest is on (shared listener with inbound relay if both are enabled). | `20` | `.env` |
| `DMARC_RATE_LIMIT_MESSAGES_PER_IP` | Per-client-IP cap on port 25 when DMARC ingest is on. When inbound relay is also enabled, **`INBOUND_RATE_LIMIT_MESSAGES_PER_IP`** applies to the shared listener instead. | `20` | `.env` |
| `DMARC_MESSAGE_SIZE_LIMIT` | Maximum report message size in bytes when DMARC ingest is on. | `5242880` (5 MiB) | `.env` |
| `INBOUND_ANTISPAM_MILTER` | Optional milter on the inbound listener only (not 465/587). Empty = off. Format `inet:host:port` or `unix:/path`. Example with [deploy/antispam/docker-compose.antispam.yml](../deploy/antispam/docker-compose.antispam.yml): `inet:antispam:11332`. | *(empty)* | `.env` |
| `INBOUND_ANTISPAM_MILTER` | Optional milter on the inbound relay listener only (requires `INBOUND_RELAY_ENABLE=true`; not 465/587 or DMARC-only port 25). Empty = off. Format `inet:host:port` or `unix:/path`. Example with [deploy/antispam/docker-compose.antispam.yml](../deploy/antispam/docker-compose.antispam.yml): `inet:antispam:11332`. | *(empty)* | `.env` |
| `INBOUND_ANTISPAM_MILTER_ACTION` | What Postfix does if that milter is down: `accept` (fail-open) or `tempfail` (defer). | `accept` | `.env` |
| `INBOUND_RATE_LIMIT_MESSAGES_PER_IP` | Coarse per-client-IP cap on inbound smtpd (`smtpd_client_message_rate_limit`). Uses the same window as `RATE_LIMIT_WINDOW_SECONDS`. | `20` | `.env` |
| `INBOUND_MESSAGE_SIZE_LIMIT` | Maximum message size in bytes on inbound smtpd (`message_size_limit`). | `26214400` (25 MiB) | `.env` |
| `INBOUND_MESSAGE_SIZE_LIMIT` | Maximum message size in bytes on inbound smtpd (`message_size_limit`). When both inbound relay and DMARC ingest are on, the shared listener uses the **greater** of this and `DMARC_MESSAGE_SIZE_LIMIT`. | `26214400` (25 MiB) | `.env` |
| `RATE_LIMIT_MESSAGES_PER_IP` | Level-1 backstop: maximum messages one client IP may submit per window (Postfix `smtpd_client_message_rate_limit`). See [Rate limiting — level 1](#rate-limiting--level-1-ip-backstop). | `100` | `.env` |
| `RATE_LIMIT_WINDOW_SECONDS` | Level-1 window length in seconds (Postfix `anvil_rate_time_unit`). | `3600` | `.env` |
| `SEND_LOG_RETENTION_DAYS` | Initial default for how many days of send-log history are kept before the background sweep deletes rows — the main driver of `/data` growth over time. After the first panel start, change retention on **Settings** (global administrator); the env value is only used to seed SQLite when the setting has never been saved. | `90` | `.env` |
@@ -342,7 +343,8 @@ against the PTR record the internet publishes for this server's IP
DNS. The **Reload configuration** button re-applies OpenDKIM tables and the
Postfix sender map from the database (and inbound relay maps when
`INBOUND_RELAY_ENABLE=true`) — use it if daemons drifted from what the panel
shows after manual edits under `/data`.
shows after manual edits under `/data`. It does **not** rebuild DMARC Postfix
maps; a full restore Resync does (see [Restore](#restore)).
### Mail queue and System log
@@ -376,8 +378,8 @@ restart). Application SASL logins are separate and are not changed here.
There are two roles:
- **Global administrator** — full access to every page and every domain,
including Users, Backup, Status, Mail queue, System log, and Inbound (when
the inbound relay flag is on).
including Users, Backup, Status, Mail queue, System log, Help, Inbound (when
the inbound relay flag is on), and DMARC (when `DMARC_REPORTS_ENABLE=true`).
- **Domain-admin** — scoped to one or more domains assigned by a global
administrator. Sees only those domains' pages, applications, and
Deliveries rows; cannot add or delete domains. `/users`, `/backup`,
@@ -547,10 +549,11 @@ refuses to start otherwise and tells you which tag to use. On the first
successful start after restore, `data/manifest.json` from the archive is
**deleted** — it guards only that one boot, so a later in-place upgrade is not
blocked. On that same first boot the panel also runs one **Resync** — OpenDKIM's
tables and Postfix's sender map are re-derived from SQLite (and inbound relay
maps when `INBOUND_RELAY_ENABLE=true`) and both daemons are reloaded, healing
any drift between the extracted files and the database (the Status page's
*Reload configuration* button runs the same step on demand). This is why the
tables and Postfix's sender map are re-derived from SQLite (inbound relay maps
when `INBOUND_RELAY_ENABLE=true`; DMARC maps when `DMARC_REPORTS_ENABLE=true`)
and both daemons are reloaded, healing any drift between the extracted files and
the database. The Status page's *Reload configuration* button resyncs OpenDKIM,
the sender map, and inbound maps only — not DMARC maps. This is why the
compose file pins a fixed tag rather than `:latest`: without a known version,
there'd be no way to tell which image restoring a given backup actually requires
(see [Fixed image tag](#fixed-image-tag)).
@@ -688,8 +691,10 @@ you configure. It is **not** mailboxes, IMAP, or webmail — SelfPost never
stores the message locally.
**Off by default.** Set `INBOUND_RELAY_ENABLE=true` in `.env` and recreate the
container. Until then there is no `smtp inet` listener, no Inbound item in
the nav, and `/inbound` is 404. Outbound 465/587 is unchanged.
container. Until then there is no inbound-relay `smtp inet` listener, no
*Inbound* item in the nav, and `/inbound` is 404. (Port 25 can still listen
when only `DMARC_REPORTS_ENABLE=true` — see [DMARC reports](#dmarc-reports).)
Outbound 465/587 is unchanged.
**Panel** (`/inbound`, global administrator only): add a domain, set the
upstream host/port and TLS to that hop (opportunistic / required / off), and
@@ -710,7 +715,8 @@ Prefer an explicit recipient list so unknown addresses are refused at RCPT
and never generate a bounce (backscatter).
**Anti-spam.** SelfPost does not ship a filter. To attach one, set
`INBOUND_ANTISPAM_MILTER` (inbound listener only) and merge
`INBOUND_ANTISPAM_MILTER` (requires inbound relay enabled; not on DMARC-only
port 25) and merge
[deploy/antispam/docker-compose.antispam.yml](../deploy/antispam/docker-compose.antispam.yml)
the same way as the nginx/Caddy fragments:
+11 -9
View File
@@ -29,18 +29,17 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
| ID | Topic | Status | Progress | Plan |
|---|---|---|---|---|
| contributing | `CONTRIBUTING.md` | candidate | — | — |
| panel-docs | In-panel operator documentation | agreed | 6/6 | [plans/panel-docs.md](plans/panel-docs.md) |
| inbound-antispam-panel | Inbound antispam journal + allow/deny lists | agreed | 1/12 | [plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) |
| inbound-quarantine | Inbound spam quarantine (hold / review / release) | candidate | — | [plans/inbound-quarantine.md](plans/inbound-quarantine.md) |
| schema-squash | Squash SQLite migrations into a 2.x baseline | **2.x** | — | — |
**Recommended order** (not binding): the next feature is **panel-docs** once
agreed. dmarc-reports shipped in
[CHANGELOG.md](../CHANGELOG.md) `[1.7.0]` (security review of the ingest path
pending); domain-stats-auto-ratelimit in `[1.6.0]`; send-log-retention in
`[1.5.0]`; inbound-relay in `[1.4.0]`; queue-retries in `[1.3.1]`; the
2026-08-13 full-tree review follow-ups are in `[1.3.0]`. Candidates need
explicit agreement before they join the queue.
**Recommended order** (not binding): the next feature is
**inbound-antispam-panel** (`1.10.0`). panel-docs shipped in
[CHANGELOG.md](../CHANGELOG.md) `[1.8.0]`; dmarc-reports in `[1.7.0]`
(security review of the ingest path pending); domain-stats-auto-ratelimit in
`[1.6.0]`; send-log-retention in `[1.5.0]`; inbound-relay in `[1.4.0]`;
queue-retries in `[1.3.1]`; the 2026-08-13 full-tree review follow-ups are in
`[1.3.0]`. Candidates need explicit agreement before they join the queue.
After a context reset, pick an item marked `agreed` or `in progress`, then work
the **Implementation checklist** in its linked plan. The `Progress` column above
@@ -69,6 +68,9 @@ priority.
## panel-docs
**Status:** done — shipped in `[1.8.0]` (2026-08-18). Plan:
[plans/panel-docs.md](plans/panel-docs.md) (checklist complete).
**Goal:** built-in operator documentation in the panel — short pages (or a
help drawer) that explain what each Status check and other controls mean,
without sending the operator out to `docs/guide.md`.
@@ -87,7 +89,7 @@ read the git tree for day-to-day meaning of a card.
**Dependencies / risks:** copy ownership and translation; keeping help in sync
when checks change; not bloating every page with a second column of prose.
**Version:** `1.x` MINOR; `candidate` until explicitly agreed.
**Version:** shipped `1.8.0`.
---
+2 -1
View File
@@ -23,7 +23,8 @@ Documentation.
| Database file | `/data/selfpost.db` (bind mount) |
| Compatibility | 1.x MINOR releases must boot a `1.0.0` data directory |
Last updated with release **1.9.0** (`0009_application_auth_ips`).
Last updated with release **1.9.0** (`0009_application_auth_ips`); **1.9.1**
had no schema change.
---