Follows the design commit before it (owner's words quoted there).
The lists' tables are Value, Kind, Matched and Remove. The kind tag
opens the entry's sheet: note, day added, how often it decided a
message in the days the log keeps, and the last match.
Matches are read off the journal on each view, nothing is stored
(inbound.Lists.Usage, store.ListSpamListHits). A row counts for an
entry when the list decided the message - a rejected row whose reply is
the deny rule's message, an accepted row whose verdict carries the
allow rule's symbol for that kind - and the entry fits the row's sender
or client address the way rspamd matches it: an address whole, a domain
as the sender's domain, a network containing the client (an
IPv4-mapped address as IPv4). A failure to count shows the lists with
"never" and logs why.
For that the journal milter keeps the lists' own symbols ahead of its
six-symbol cap: rspamd writes symbols heaviest first, so an allow
entry's -100 came last and was cut behind any six others.
The plan gains "Filter list usage": what counting from the journal
implies, and - the owner asked for it in writing - the conditions under
which a counter kept with each entry becomes worth a migration (the
journal cut by its row cap and not by age; a short retention; counts
that must outlive the journal; a slow page). multimap.conf says which
of its texts SelfPost reads back.
Tests: Usage over every kind on both lists and the rows that are
nobody's; the milter's symbol order; the store query; the page and its
sheets; a handler test from journal rows to the numbers on the page.
Seen: the fixture page with a sheet open, headless Edge at 1360 px -
the same as the accepted drawing.
Checked on the test server, uncommitted tree: go vet ./... ok,
go test ./... ok (22 packages), make e2e ok.
Not seen in a real run: a count above zero. It needs an entry added in
the panel, which I cannot sign in to, and then mail that the list
decides; in particular the deny rule's message arriving in mail.log as
the reply of a milter-reject line is what the trial with rspamc showed,
not yet what Postfix logged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 5/12 of docs/plans/inbound-antispam-panel.md.
Store: AddSpamListEntry, ListSpamListEntries, DeleteSpamListEntry.
internal/inbound/lists.go validates and normalises an entry - sender
address, sender domain, client address or network (canonical text, an
IPv4-mapped address as IPv4, /0 refused) - and writes the lists as six
map files under /data/antispam (ANTISPAM_DIR), each atomically, 0644 in
a 0755 directory: the filter is another user in another container. All
six are written on every change and on every start with the relay on,
empty ones included. An entry whose maps could not be written is taken
back out of the table. The writer refuses a stored value with
whitespace, a newline or # in it.
The plan's allow.map / deny.map became one file per list and entry
type: to rspamd an IP map and a sender map are different map types.
deploy/antispam/rspamd/multimap.conf holds the rules; the fragment
mounts it and ./data/antispam into the sidecar. Deny is a prefilter
reject, allow a symbol weighing -100. Tried in a throwaway rspamd 3.11
container on the test server:
- with allow as a prefilter accept, an address on both lists was
accepted and an IP on both was refused - the rule that ran first
decided; with the weight, deny won in all five mixed cases;
- IPv4, IPv6, address (any case), domain (not its subdomains) match;
- a rewritten file is reread in about ten seconds, no restart;
- files that appear after rspamd started are loaded the same way.
No page edits the lists yet (step 6).
Checked on the test server, uncommitted tree: go vet ./... ok,
go test ./... ok (22 packages), make e2e ok - with the relay on
/data/antispam is 755 and holds the six maps, 644, empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 4/12 of docs/plans/inbound-antispam-panel.md.
The log tailer writes an inbound_spam_log row for mail port 25 refused
(internal/logtail/inbound.go): a "reject:" line of the port-25 smtpd is
Postfix's own policy; a milter-reject / milter-hold / milter-discard
line of cleanup is the filter's verdict. Only a recipient at an inbound
domain is a row, so "Relay access denied" for a stranger's domain is
not. The row is dated by its log line; the reply text is cut to 255
bytes of printable ASCII; a reply starting with 4 is a tempfail.
To tell port 25 from 465 / 587 the listener now logs as
postfix/inbound (syslog_name). cleanup logs under one name for every
listener, so its verdict counts only for a queue id the port-25 smtpd
was seen to open (4096 remembered). Lines are matched from the head,
after timestamp and host: their tail is the sender's own text.
Retention: PruneInboundSpam every six hours - older than the
inbound_spam_log_retention_days setting (seeded from
INBOUND_SPAM_LOG_RETENTION_DAYS, 90, range 7-365), then down to 10 000
rows. The setting has no field in the panel yet.
Known limits, in architecture.md: a log line names one recipient, so a
message refused at end-of-message is one row; a line re-read after an
unclean stop would be a second row.
Checked on the test server, uncommitted tree: go vet ./... ok,
go test ./... ok (22 packages), make e2e ok - the unknown recipient at
the inbound domain refused on port 25 has its row (reject by postfix),
the stranger's domain has none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 3/12 of docs/plans/inbound-antispam-panel.md.
The panel serves a second milter while the inbound relay is on
(internal/milter/inbound.go, socket INBOUND_JOURNAL_MILTER_SOCKET,
default /run/selfpost/inbound-journal.sock). build/postfix-config.sh
puts it last on port 25, after the optional antispam milter, with
default_action=accept; postfix-wrapper.sh waits for its socket. At
end-of-message it writes one inbound_spam_log row per recipient at an
inbound domain - other port-25 recipients (DMARC report addresses) get
none - and always accepts. With a filter attached it reads the verdict
from X-Rspamd-Action and X-Spamd-Result (action, score, up to six
weighted symbols, only what the patterns recognise, 255 bytes); without
one those headers are the sender's and are not read. The plan named
X-Rspamd-Score, which rspamd does not have.
The antispam compose fragment mounts rspamd/milter_headers.conf into
the sidecar so that it adds the two headers (checked in rspamd 3.11:
both routines exist and remove a sender's own headers of those names).
Store: InsertInboundSpam, IsInboundDomain.
Checked on the test server, uncommitted tree: go vet ./... ok,
go test ./... ok (22 packages), make e2e ok - the relayed message of
inbound_relay_delivers_and_refuses has its journal row (accept by
selfpost) and the DMARC report sent through the same listener has none.
Not yet seen in a real run: a verdict written by rspamd reaching this
milter. The e2e stand has no filter, and the test server's only inbound
domain has no upstream, so it accepts no mail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A patch on 2.0.0, all of it found on the test server after the cut:
- a message that is not a report this relay keeps is dropped at once
instead of being retried for days;
- a report is bounded before it is stored, and reports are kept per
domain (security review of the ingest path, nothing Critical or High);
- Full backup from the panel works (it had answered 200 with ten bytes
since 1.3.0);
- no backup holds the Postfix queue, and the panel says why.
Cut: CHANGELOG [Unreleased] -> [2.0.1]; image pin 2.0.1 in compose,
README, guide; version table.
On the test server, on dd0fefd: go vet ./... ok, go test ./... ok
(22 packages), make e2e ok.
Full detail: CHANGELOG.md (2.0.1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The panel redesign (docs/plans/panel-redesign.md, 27/27). A breaking
release: every panel URL changed and the database starts from a single
baseline - a 1.x /data is not upgraded.
Since the last cut also fixed, all found on the test server:
- the panel can read the mail queue;
- port 25 answers with DMARC report ingest on, also next to the
inbound relay;
- DMARC reports reach the panel (the delivery service never worked
since 1.7.0);
- Postfix start-up warnings about the queue directory.
Cut: CHANGELOG [Unreleased] -> [2.0.0]; image pin 2.0.0 in compose,
README, guide; roadmap, plan and version table.
On the test server, on 252e5d1: go vet ./... ok, go test ./... ok
(22 packages), make e2e ok.
Full detail: CHANGELOG.md (2.0.0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI-only cut. The release workflow deleted the X.Y.Z-amd64 / X.Y.Z-arm64
package versions after the manifest merge; those versions are the images the
X.Y.Z manifest points at, so pulling 1.3.0 failed with a missing manifest.
No product code changes.
Fixed:
- release.yml: the per-arch tags stay in GHCR. GHCR cannot drop a tag
without deleting the image behind it.
- release.yml: the merge job now removes only untagged package versions
that no tagged manifest references (leftovers of publishing a version
again); any failed lookup aborts the step before anything is deleted.
- development.md release description and version table, image pin 1.9.5
in compose, README, guide.
Full detail: CHANGELOG.md (1.9.5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan-only cut, owner-requested. The redesign goes first as 2.0.0 and
inbound-antispam-panel follows as 2.1.0 on the 2.0 baseline (migration
0002_inbound_spam_log.sql), built from the redesign's component kit so its
two screens are styled once. No product code changes.
Changed:
- panel-redesign plan: Component kit section and stage 0 (partials with
typed inputs in components.go, one panel.css, /server/components kit
page, acceptance before any page is restyled); staged checklist (5/27);
Health decided inside 2.0, queue pages left as old content in the new
shell.
- panel-redesign plan: Against resurrected pages - shrink-only
legacyPages ratchet in the guard tests enforced by CI, the outline
golden, the old-path test and a history-blob check, so an old template
from git history cannot pass as a redesigned page.
- roadmap: new candidate delivery-log-storage - the Delivery log box on a
message page is empty for anything older than the last daily rotation
because the lines are grepped from the current mail.log on each view
and never stored.
- development.md version table, image pin 1.9.4 in compose, README, guide.
Full detail: CHANGELOG.md (1.9.4).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Panel-redesign prep, docs only: stage 1-2 mockups on all 33 screens, the accepted header/type-scale/contrast corrections, the account e-mail and inbound-delegation decisions, and the routes/quarantine/preflight roadmap screens. Nothing in `internal/web` changes; the design stays `candidate` (3/21) until the redesign itself ships as `2.0.0`.
Added:
- Panel redesign, account e-mail and notifications: a user's e-mail is its own
- Panel redesign, contrast: a sweep of every text node against its real ground
- Panel redesign, header (owner's choice: variant A, with the perforation on a
- Panel redesign, type scale: about 40 % of the panel's text measured under
- Panel redesign, header comparison sheet
… (8 more in CHANGELOG)
Full detail: CHANGELOG.md (1.9.3).
Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump.
Added:
- SQLite migration `0010_send_log_app_login_index.sql` —
- DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML
- e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration
Changed:
- README hero line and a **Positioning** block in `docs/product.md` record the
Fixed:
- The setup account's password is hashed at bcrypt cost `12` like every other
- Login/setup rate limiter: protecting blocked buckets from eviction is now
- `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of
- [schema-migrations.md](docs/schema-migrations.md): the schema section was
- postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second
… (11 more in CHANGELOG)
Changed:
- [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code
- Extracted `internal/configsafe` — the empty-value and forbidden-character
- Build, deploy and migration comments no longer cite the archived
- Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through
- Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and
… (7 more in CHANGELOG)
Removed:
- Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs
- Plan documents for already-shipped features: `domain-stats-auto-ratelimit`,
Full detail: CHANGELOG.md (1.9.2).
Port-25 Postfix startup when inbound relay, DMARC ingest, or the inbound antispam milter are enabled. Schema-migration reference and roadmap plans for inbound antispam and quarantine. Upgrading from 1.9.0 is a tag bump; no migration.
Added:
- [docs/plans/inbound-antispam-panel.md](docs/plans/inbound-antispam-panel.md) —
- [docs/plans/inbound-quarantine.md](docs/plans/inbound-quarantine.md) — candidate
- [docs/schema-migrations.md](docs/schema-migrations.md) — living reference for the
Changed:
- [docs/roadmap.md](docs/roadmap.md) — `inbound-antispam-panel` (agreed, 1/12)
- [docs/development.md](docs/development.md) — plan checklists may override
- [docs/development.md](docs/development.md), [docs/roadmap.md](docs/roadmap.md),
Fixed:
- `build/postfix-config.sh`: enabling inbound relay, DMARC ingest, and/or the
Full detail: CHANGELOG.md (1.9.1).
Application sending controls: client IP allow-list for authorization, and level-2 rate limits that override the domain ceiling per application.
Added:
- Per-application **client IP allow-list** on the domain page: when enabled,
- SQLite migration `0009_application_auth_ips.sql` (moves legacy trusted IPs
Changed:
- Application level-2 rate limit **overrides** the domain limit for that login
- Domain export/import carries `auth_ip_restrict` / `auth_allowed_ips`; legacy
- Panel help, Settings, and [guide.md](docs/guide.md) describe override semantics
- [guide.md](docs/guide.md): full-backup section documents all four capture
Full detail: CHANGELOG.md (1.9.0).
Built-in operator documentation in the panel: a CSS-only help drawer and Help page so Status checks and domain controls are explained without leaving the panel.
Added:
- **Help** navigation entry and `/help` index page.
- CSS-checkbox help drawer (no extra script): seeded topics for Status (Machine,
- «?» entry points on Status and domain card heads open the matching drawer
Changed:
- [guide.md](docs/guide.md) documents the boundary: in-panel help for card
Full detail: CHANGELOG.md (1.8.0).
Optional DMARC aggregate report ingest: accept `rua=` mail on port 25, parse gzip/XML into SQLite, and show summaries in the panel.
Added:
- Optional DMARC aggregate report ingest (`DMARC_REPORTS_ENABLE`): port 25
- Per-domain **SelfPost hosted** `rua=` mode when ingest is enabled
Full detail: CHANGELOG.md (1.7.0).
30-day sending statistics per domain and application, plus optional auto level-2 rate limits derived from average send rate.
Added:
- Domain page **Sending statistics** card: total messages, peak and average
- Level-2 rate limit **Manual** / **Auto** mode on domain and application
- Domain export JSON now includes rate limits (`mode`, `auto_multiplier`,
Changed:
- Auto mode uses the level-1 window (`RATE_LIMIT_WINDOW_SECONDS`); manual
Full detail: CHANGELOG.md (1.6.0).
Optional inbound relay (backup-MX / forwarder) on port 25, off by default. Mail is accepted only for domains configured in the panel and forwarded to an upstream; there are still no mailboxes. A dedicated security review of the inbound path is still open on the plan (Fable).
Added:
- `INBOUND_RELAY_ENABLE` (default `false`): when true, Postfix listens on
- Panel **Inbound** pages (global administrator): domains, upstream host/port
- Optional `INBOUND_ANTISPAM_MILTER` on the inbound listener only, plus
- Inbound rate limit (`INBOUND_RATE_LIMIT_MESSAGES_PER_IP`, default 20) and
Changed:
- Compose publishes host port 25 even when the flag is off (nothing listens
- Full-backup restore Resync also rebuilds inbound maps when the flag is on.
Full detail: CHANGELOG.md (1.4.0).
[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).
[1.2.0]
The second MINOR after 1.0.0: domain administrators with per-domain scope, a panel visual refresh on the SelfPost palette, and refinements to navigation and the send log. Upgrading runs one SQLite migration (`0005_panel_users`); the single administrator becomes a global user.
Added:
- panel: **domain-admin role** — global administrators manage panel users and
Fixed:
- Docker build: `LICENSE` is no longer excluded by `.dockerignore`, so the
- panel: on signed-in pages with a narrow card (**Settings**, the user form)
Changed:
- panel: navigation session icons are distinct — **Settings** uses a gear,
- panel: `/` redirects domain administrators to `/domains`; global users still
- panel: **Sign out** in the navigation column uses the same type size and
- panel: **visual style** brought in line with the SelfPost mark — brick accent
- panel: the send log's **status is a badge**, in the same ok/warn/error/unknown
… (1 more in CHANGELOG)
[1.2.1]
Panel refinements after 1.2.0: navigation icons, status-badge centreing, drill-down back-link placement, and safeguards for the sole global administrator. Upgrading is a tag bump; no migration.
Fixed:
- panel: status badge text sat low in the box (and below the heading or label
- panel: the **Users** navigation icon was two full silhouettes with staggered
- panel: the **Settings** navigation icon was a sun-with-rays (circle plus
- panel: the user create/edit form placed «Back to users» at the bottom of the
Changed:
- panel: the user edit form disables role change and delete for the only global
- panel: the user create/edit form hides **Assigned domains** when the role is
- panel: **Settings** shows panel credentials and DMARC aggregate reports side
[1.2.2]
Status page layout after 1.2.1: paired cards in a wide column, denser machine and check copy, and a user-form checkbox fix. Upgrading is a tag bump; no migration.
Changed:
- panel: **Status** is wide again so paired cards fill the column. Layout:
Fixed:
- panel: cards inside `.split` used `margin: 0 auto`, which in a CSS grid
- panel: on the user create/edit form, **Assigned domains** checkboxes stacked
[1.2.3]
Domain detail layout and panel polish after 1.2.2. Upgrading is a tag bump; no migration.
Changed:
- panel: the domain detail page is wide with paired cards (DKIM+SPF ‖ DMARC;
- panel: Domains list — **Add domain** sits beside the domain field; the
- panel: page URLs, browser titles, and headings are aligned — **Settings** is
Fixed:
- image: `mail.log` rotation no longer silently stops when the build context
[1.2.4]
Level-2 rate-limit semantics inverted after 1.2.3, plus a small DNS field height fix. Upgrading is a tag bump; no migration.
Changed:
- rate limiting (level 2): domain ceilings apply to every client IP (no IP
Fixed:
- panel: on the domain DNS status grid, the Type (TXT) field height matches
[1.2.5]
Rate-limit form polish after 1.2.4. Upgrading is a tag bump; no migration.
Changed:
- panel: rate-limit forms refined — level-1 backstop as muted copy on the
Squashed CHANGELOG sections: [1.2.0], [1.2.1], [1.2.2], [1.2.3], [1.2.4], [1.2.5].
Full detail: CHANGELOG.md (1.2.0, 1.2.1, 1.2.2, 1.2.3, 1.2.4, 1.2.5).
The first MINOR after 1.0.0: send-only DMARC guidance in the panel, AGPL packaging on every page, and an internal split of `internal/web` ahead of domain-admin work. Upgrading runs one SQLite migration (empty defaults; existing DNS guidance is unchanged until you set a report address).
Changed:
- `internal/web` split into subpackages (`web/view`, `web/auth`, `web/validate`,
Added:
- panel: DMARC guidance for send-only relays — the suggested `_dmarc` record
- AGPL packaging hygiene: [NOTICE](NOTICE) names the copyright holder and the
- [docs/roadmap.md](docs/roadmap.md) — candidate item **visual-style** (panel
Full detail: CHANGELOG.md (1.1.0).
[1.0.0]
Added:
- A **Delivery log** on each delivery's page (`/deliveries/{id}`): the
- A **History** block on the same page: the journal's two timestamps stated as
Fixed:
- Postfix config: copy TLS cert/key from the (often `:ro`, host-owned) mount
- Postfix config: allow `maillog_file` under `/data` via
- E2e: read `/data/setup-token` via `docker compose exec` (file is `0600`
- Entrypoint: check `SELFPOST_HOSTNAME` before `/data` setup (so a bad identity
- E2e gate: wait for host-published `/healthz` before panel setup, and stop
… (3 more in CHANGELOG)
Changed:
- Document what `.spbk` and `.spde` stand for (SelfPost backup / SelfPost domain
- Docs aligned with the code: setup URL is `/setup/<token>` (README and
- Full backups no longer carry `/data/log`. It is Postfix's raw log plus its
- Monitoring screens (status, mail queue, system log, deliveries) use adaptive
- Documentation package consolidated into `docs/development.md`: Documentation
… (4 more in CHANGELOG)
[1.0.1]
A documentation and packaging release: no change to the mail path, the database, or the on-disk layout. Upgrading is a tag bump.
Added:
- `SECURITY.md` — how to report a vulnerability privately (GitHub private
- [docs/plans/](docs/plans/) — one document per agreed extension: the optional
Changed:
- The panel's **Account** entry is now called **Settings** — nav link, page
- The signed-in name in the panel's nav is now labelled `User:`, so it reads as
- [docs/product.md](docs/product.md) reframes the future line: the inbound
- [docs/security.md](docs/security.md) is now in English, matching the rest of
- The README documentation table now points at `SECURITY.md` for reporting a
… (2 more in CHANGELOG)
Fixed:
- The panel's static assets are served with a content ETag and
Squashed CHANGELOG sections: [1.0.0], [1.0.1].
Full detail: CHANGELOG.md (1.0.0, 1.0.1).
A bounce could be recorded as a successful delivery. The log-tailer's
Fixed:
- A bounce could be recorded as a successful delivery. The log-tailer's
- Log-tailer resumes where it stopped instead of jumping to end-of-file on
- Level-2 rate limit no longer overshoots under concurrency: messages that
Changed:
- The project has a single public home: `github.com/mixeme/selfpost`. Codeberg
- Code comments no longer cite the archived specification. References like
- `docs/code-review.md` is gone. Its plan is finished — phases 0 (bar the
- `docs/architecture.md` gained a *Code layers* section: a diagram of
- Phase 1 doc/code hygiene (`docs/code-review.md`): removed ~30 stale
… (1 more in CHANGELOG)
Security:
- Pre-release security review (plan § D, model Fable, 2026-08-06): full pass
- `saslpasswd2` argv: the application login is now passed after a `--`
Added:
- Optional password encryption for the two secret-bearing downloads (plan
- Domain import accepts an encrypted export: the envelope is detected by its
- `selfpost-backup` writes encrypted archives and reads them back:
- `TestParseDelivery` covers the exotic mail.log shapes the review asked for
- docs: README *Encrypting a backup or export*; `docs/security.md` §
… (14 more in CHANGELOG)
Removed:
- docs: `security.md` — accepted risk «restore old backup revives session rows»
Changed:
- docs: `implementation-plan.md` trimmed to the sole open v1.x gate — pre-release
- docs: `architecture.md` — sessions (SQLite, idle renew, password change),
- docs: `development.md` — expanded e2e stack and `release.yml` matrix workflow.
- docs: `security.md` — accepted risk for send-log rows stuck at `queued` after
- docs: `roadmap.md` — optional send-log / `mail.log` follow-ups under v1.x tail.
… (10 more in CHANGELOG)
Full detail: CHANGELOG.md (0.5.0).
panel: the PTR (reverse DNS) check no longer reports a correctly published
Fixed:
- panel: the PTR (reverse DNS) check no longer reports a correctly published
Changed:
- panel: the three monitoring pages now live at URLs that match their nav
- panel: each entry in the navigation bar now carries an icon beside its label,
- panel: the navigation bar is laid out as two rows on purpose — the signed-in
Full detail: CHANGELOG.md (0.3.0).
Initial feature-complete implementation of the v1.0 specification (phases 0-11 of `docs/implementation-plan.md`).
Added:
- Panel (Go, single static binary) with SQLite persistence, one-time
- Domain management with per-domain DKIM (RSA-2048, generated in pure Go) and
- Application (sender identity) management: SASL credentials via `sasldb2`,
- Full Postfix relay config generated from env at container start: SMTPS 465,
- Journal milter (pure Go, `go-milter`) recording every send to `send_log`;
… (6 more in CHANGELOG)
Full detail: CHANGELOG.md (0.1.0).