Files
selfpost/docs/roadmap.md
T
mix 0730b2090e release: 1.3.0: 1.3.0-1.3.1: security pass, queue persist, full backup layout
[1.3.0]
Security and quality after 1.2.5: domain-admin send-log authorization, fail-closed sign-in and application delete, level-2 rate-limit race fix, restore Resync, expanded tests, operator docs, release CI, and OFL for IBM Plex. Upgrading from 1.2.x is a tag bump; no migration.
Added:
  - licence: the SIL Open Font License 1.1 text now travels with the IBM Plex
  - docs: agreed roadmap item **queue-retries** — show this Postfix's retry
Security:
  - The independent security review of the send-log authorization and
Fixed:
  - test (e2e): send-log status scrapers follow the badge markup in
  - security (panel): the Deliveries list is scoped to a domain administrator's
  - mail (level-2 rate limit): the ceiling is no longer overshot by messages that
  - panel (sign-in): a session that cannot be written to the database no longer
  - panel (applications): deleting an application removes its SASL credentials
  … (2 more in CHANGELOG)
Changed:
  - docs: operator and as-built docs aligned with the code after a full
  - docs: [guide.md](docs/guide.md) reorganised into **Installation**, **Instance
  - docs: the 2026-08-13 full-tree review plan is complete — every phase (P0–P7)
  - licence: [NOTICE](NOTICE) tells modifiers to update `SourceURL` in
  - ci: the release image is published only for a **published** GitHub Release
  … (9 more in CHANGELOG)
[1.3.1]
Retry policy in the panel, persistent Postfix queue, and self-contained full backups. Mail queue and delivery history show this Postfix's first retry, backoff cap, and queue lifetime. Deferred mail survives container recreate. Full backups now archive `data/`, compose, `.env`, and `certs/`; the archive layout breaks 1.3.0 flat backups (unpack those with `tar xzf backup.tar.gz -C ./data` as before). Upgrading from 1.3.0 is a tag bump; no schema migration.
Added:
  - Mail queue and a delivery's history show this Postfix's retry policy
  - docs: **Plan checklists** in [development.md](docs/development.md) — format,
  - docs: **Implementation checklists** with model routing in
  - docs: roadmap candidates **send-log-retention** (panel Settings for delivery
  - Postfix queue under `/data/postfix/queue` — deferred and active mail survive
  … (1 more in CHANGELOG)
Changed:
  - **Breaking:** full-backup archive layout — paths are prefixed with `data/`;
  - docs: operator guide, architecture, security, and backup UI updated for
  - docs: HTML mockups for a full panel UI refresh
Changed:
  - docs: panel UI mockups after review — Status restored to live readings
  - docs: panel UI design system and mockups rebuilt as separate pages
  - docs: panel UI mockups — add-domain sits in the list card (Domains and
Fixed:
  - ci (GHCR): per-arch package tags (`X.Y.Z-amd64`, `X.Y.Z-arm64`) are dropped

Squashed CHANGELOG sections: [1.3.0], [1.3.1].

Full detail: CHANGELOG.md (1.3.0, 1.3.1).
2026-09-22 15:24:52 +03:00

9.3 KiB
Raw Blame History

Roadmap: open work (1.x+)

Status: a working tracker of extensions to the v1.0 boundary, each taken up only after explicit agreement (product.md, .cursor/rules/agent-rules.mdc). Detailed design lives in plans/. Items marked candidate need an OK before any code is written.

Reading this from outside the project: nothing here is a commitment or a release promise. There are no dates, the order is a recommendation rather than a schedule, and an item can be dropped or reshaped once its plan is written. What the project will not do is a separate question, answered in product.md — an item's absence from this file does not mean it is planned but unlisted.

Versioning: SemVer MINOR in the 1.x+ line by default (1.1.0…), as long as defaults and migrations stay compatible with 1.0.0. A major 2.x only for an explicit break. One such break, when 2.x is cut for any reason, is schema-squash — replacing the 1.x SQLite migration chain with a baseline. That item does not by itself justify a major.

Process: development.md. The history of closed phases is in git log and CHANGELOG.md.


Index

ID Topic Status Progress Plan
inbound-relay Inbound relay (backup-MX / forwarding) agreed 0/15 plans/inbound-relay.md
send-log-retention Send-log retention days in panel Settings candidate 0/8 plans/send-log-retention.md
domain-stats-auto-ratelimit 30-day send stats + auto level-2 rate limit candidate 0/11 plans/domain-stats-auto-ratelimit.md
contributing CONTRIBUTING.md candidate — —
dmarc-reports DMARC aggregate report ingestion and panel UI candidate 0/8 plans/dmarc-reports.md
panel-docs In-panel operator documentation candidate 0/6 plans/panel-docs.md
schema-squash Squash SQLite migrations into a 2.x baseline 2.x — —

Recommended order (not binding): the next feature is inbound-relay. send-log-retention can land before or beside domain-stats-auto-ratelimit (panel retention ≥ 30 days helps the stats window). queue-retries shipped in CHANGELOG.md [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 is done/total checklist steps in that plan (development.md § Plan checklists).


inbound-relay

Goal: optional acceptance of mail on port 25 for explicitly configured domains, forwarded to an upstream (backup-MX / relay-forwarder). Off by default (INBOUND_RELAY_ENABLE=false); without the flag the outbound path is unchanged.

Boundary: an extension of v1.0 — product.md excludes inbound mail and mailboxes. This is relay/forward, not IMAP/POP3/webmail; an anti-spam engine stays outside the image, only the attachment point is provided.

Done when: see the criteria in plans/inbound-relay.md.

Dependencies / risks: a finished outbound path; open relay and backscatter; a wider attack surface (port 25 accepting mail). Version: target bump 1.x; 2.x possible — to be settled once the implementation lands.


send-log-retention

Goal: global administrator sets how many days of delivery journal rows (send_log, /deliveries) are kept, from /settings — stored in SQLite settings, with SEND_LOG_RETENTION_DAYS as bootstrap default only.

Boundary: instance-wide retention; not per-domain. Does not change mail.log rotation. Today retention is env-only (default 90 days); the panel has no control.

Done when: see plans/send-log-retention.md.

Dependencies / risks: log-tailer must re-read settings each prune cycle; delivery UI must stop hardcoding «ninety days». Version: 1.x MINOR; candidate until explicitly agreed.


domain-stats-auto-ratelimit

Goal: 30-day sending statistics per domain and application (total, peak and average msg/h) on the domain page, plus optional auto level-2 rate limits computed as ceil(avg_hourly × multiplier) over the level-1 window.

Boundary: extension of v1.0 manual L2 limits; does not change Postfix level 1. Stats exclude level-1 refusals (not in send log). Auto with zero traffic stays inactive.

Done when: see plans/domain-stats-auto-ratelimit.md.

Dependencies / risks: send log data and preferably send-log-retention ≥ 30 days; aggregate query cost on large logs; rate-limit path needs security review. Version: 1.x MINOR; candidate until explicitly agreed.


contributing

Goal: CONTRIBUTING.md in the root — the dev loop, the checks to run before a PR, the commit protocol; development.md links to it rather than repeating it.

Boundary: process documentation; worth writing once there is an external flow of PRs.

Done when: the file is in the root and development.md does not duplicate it.

Dependencies / risks: with a single developer and no PRs, this is low priority. Version: no bearing on semver.


dmarc-reports

Goal: SelfPost receives DMARC aggregate reports (RFC 7489) on SMTP, parses the gzip/XML payloads, and shows pass/fail summaries in the panel — so the operator does not need an external DMARC service or a separate mailbox workflow.

Boundary: an extension of v1.0 — not IMAP/webmail and not a general inbound relay. A dedicated inbound path for report messages only; forensic reports (ruf=) out of scope for v1.

Done when: see plans/dmarc-reports.md.

Dependencies / risks: inbound SMTP in the image (may share infrastructure with inbound-relay but must not require backup-MX); storage and retention of parsed summaries; the dmarc_report_email setting (migration 0005 moved it off the old admin table into settings) and domains.dmarc_rua added in the DMARC template work must stay the source of truth for rua= in DNS guidance.

Order: after the DMARC rua= settings ship; may follow or overlap with inbound-relay depending on how port 25 acceptance is structured.

Version: 1.x MINOR.


panel-docs

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.

Boundary: in-panel help only; not a second copy of the full operator guide. Seed content includes the Status blurbs removed from the cards in favour of a denser layout — Machine (kernel counters / rate window), TLS certificate (port 465, reverse-proxy mount), Hostname / reverse DNS (forward-confirmed reverse DNS, PTR at the hosting provider), and similar notes for other panel surfaces as they lose inline commentary.

Done when: an operator can open help from the panel for those topics; the removed Status blurbs are preserved there (or equivalent); no requirement to 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.


schema-squash

Goal: when 2.x is cut, stop shipping the 1.x migration files (0001_init.sql … 0005_panel_users.sql) in the binary and replace them with one baseline that is the schema as of user_version = 5. Fresh 2.x data directories no longer create-then-drop the historical admin table.

Boundary: 1.x keeps the full chain so a 1.0.0 data directory still boots. Do not delete, rename, or reorder those files while MINOR compatibility with 1.0.0 holds. migrate() maps file order to PRAGMA user_version (target = i + 1); dropping a file in 1.x would skip or mis-apply steps on existing databases. Git history keeps the old files either way; only the embedded set in the 2.x image changes.

Upgrade gate (required with the squash):

user_version 2.x behaviour
0 (empty file) Apply the baseline; set user_version to the new chain’s head
>= 5 (fully migrated 1.x) Skip; schema is already the baseline
1…4 (mid-chain 1.x) Refuse to start — boot the last 1.x once, then 2.x

Restore remains a separate lock: the backup manifest version must match the running binary (architecture.md § Persistence). It does not replace this gate.

Done when: 2.x embeds a single baseline (plus any 2.x-only migrations after it); the gate above is tested; the operator guide says a 2.x image will not open an unfinished 1.x database.

Dependencies / risks: a decided 2.x cut (another breaking change, or an explicit major). Squashing five short files is not a reason to cut 2.x on its own. A missed gate leaves a user_version = 3 database silently stuck. Version: 2.x major only; not a 1.x item.