The first DMARC report to reach the test server (Google, 2026-10-11
09:41 UTC) was accepted on port 25 and then sat in the queue:
postfix/pipe: fatal: missing user= command-line attribute
postfix/qmgr: warning: private/dmarc-ingest socket: malformed response
Three things stood between an accepted report and the panel, all since
the feature shipped in 1.7.0 - no report had ever been delivered:
- The service was addressed as dmarc-ingest/unix-pipe. Its type is unix:
postconf -P answered "unmatched service_name/type" for flags=, user=
and argv= (the warnings in the container log at every start), which
are arguments of pipe(8) anyway and not -o parameters, and the service
stayed a bare "pipe". For the same reason postconf -MX never removed
it. It is dmarc-ingest/unix now, with the arguments in the service line.
- pipe(8) gives the command export_environment and nothing else, so the
command would have refused to run: it reads DMARC_REPORTS_ENABLE (and
the data directory, when it is not the default) from the environment.
Those are now named in export_environment.
- The command took the recipient from RECIPIENT, which local(8) sets and
pipe(8) does not. With no recipient the check that a report came to the
address of its own domain is skipped. It is now ${recipient} in argv,
one recipient per run.
When DMARC ingest is off the service and both parameters are removed.
e2e: every port-25 combination now asks Postfix for the service and the
two parameters (postconf -M / -h), and for their absence when ingest is
off - which is how the wrong type was found. make e2e on the test
server: ok. go test ./cmd/panel/ ./internal/dmarc/ ./internal/postfix/:
ok.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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).
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).