Compare commits
78
Commits
2dded921f5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0cfde72ae1 | ||
|
|
839a752341 | ||
|
|
91dadf7b35 | ||
|
|
d17fee8f81 | ||
|
|
b1882b84f8 | ||
|
|
853e0cabf3 | ||
|
|
6edf8314bc | ||
|
|
6db3704711 | ||
|
|
8525e8ee8d | ||
|
|
629de5e33a | ||
|
|
2f60be9a3c | ||
|
|
2c23f4c003 | ||
|
|
30fcf978e0 | ||
|
|
f20c942d64 | ||
|
|
6b4106f60e | ||
|
|
c10cd78eff | ||
|
|
554e007f22 | ||
|
|
8b2dc8f40f | ||
|
|
c736a7e144 | ||
|
|
8eec6728d4 | ||
|
|
58356fb4d4 | ||
|
|
32f6ed626d | ||
|
|
4717796da6 | ||
|
|
ecfe23504e | ||
|
|
ec1c3a6138 | ||
|
|
08087795cd | ||
|
|
4f999f4f95 | ||
|
|
5c5f49bad1 | ||
|
|
820b1bb968 | ||
|
|
c09368c544 | ||
|
|
68493dc097 | ||
|
|
bde6a4000e | ||
|
|
668e299c69 | ||
|
|
dd950364a3 | ||
|
|
a327962008 | ||
|
|
8aad5b6223 | ||
|
|
4433688500 | ||
|
|
3a8e19b71c | ||
|
|
1bafd78364 | ||
|
|
17b7049311 | ||
|
|
ff3a118b2f | ||
|
|
e9d2cbbe28 | ||
|
|
fd29468e00 | ||
|
|
b81ff9f135 | ||
|
|
7effc98e5d | ||
|
|
626bd337be | ||
|
|
c50314fb4c | ||
|
|
d254a9952d | ||
|
|
2c8e981f89 | ||
|
|
8820294472 | ||
|
|
5210cb19f2 | ||
|
|
da2e8b2ffe | ||
|
|
683fdafc6d | ||
|
|
f2ef705470 | ||
|
|
a37aba6594 | ||
|
|
2c27275970 | ||
|
|
71f307d4ca | ||
|
|
44bf09d7c2 | ||
|
|
22bfcdb5e4 | ||
|
|
25813316b3 | ||
|
|
1fe90c40f8 | ||
|
|
7289aec60b | ||
|
|
0eaf69a830 | ||
|
|
2bf9056d20 | ||
|
|
9b33b805d8 | ||
|
|
de4274ccc9 | ||
|
|
1eec1c6011 | ||
|
|
9aebe1efe4 | ||
|
|
3d23c41ae7 | ||
|
|
b8e156c9f4 | ||
|
|
09edc0fac6 | ||
|
|
b7bbb2ca14 | ||
|
|
2f5f5b2b93 | ||
|
|
b7f04217f2 | ||
|
|
ddafc96bab | ||
|
|
f1cc3f3b37 | ||
|
|
c5edbd84c7 | ||
|
|
be98efa549 |
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# base-packages.sh: compare the Debian packages the mail path runs on with
|
||||||
|
# what debian:trixie-slim would install today (security audit 2026-10-17,
|
||||||
|
# I5). The image installs without pinning, and published tags are never
|
||||||
|
# rebuilt, so a security update of postfix, Cyrus SASL, OpenSSL or Berkeley
|
||||||
|
# DB reaches operators only with the next release. This fails when one of
|
||||||
|
# them has moved past the version the last release was built with - the
|
||||||
|
# table between the base-package-versions markers in docs/development.md -
|
||||||
|
# as the nudge to cut a patch release (and then to update the table).
|
||||||
|
#
|
||||||
|
# Run by .github/workflows/base-packages.yml every week; needs docker.
|
||||||
|
set -eu
|
||||||
|
doc=docs/development.md
|
||||||
|
table=$(sed -n '/<!-- base-package-versions -->/,/<!-- \/base-package-versions -->/p' "$doc" |
|
||||||
|
sed -n 's/^| `\([^`]*\)` | `\([^`]*\)` |$/\1 \2/p')
|
||||||
|
[ -n "$table" ] || { echo "no base-package-versions table in $doc" >&2; exit 2; }
|
||||||
|
names=$(echo "$table" | cut -d' ' -f1 | tr '\n' ' ')
|
||||||
|
# apt-cache policy's "Candidate:" is what an install would take now.
|
||||||
|
now=$(docker run --rm debian:trixie-slim sh -c "apt-get update -qq >/dev/null && for p in $names; do printf '%s %s\n' \"\$p\" \"\$(apt-cache policy \"\$p\" | sed -n 's/^ *Candidate: //p')\"; done")
|
||||||
|
moved=0
|
||||||
|
echo "$table" | while read -r name built; do
|
||||||
|
cand=$(echo "$now" | awk -v n="$name" '$1 == n { print $2 }')
|
||||||
|
if [ "$cand" != "$built" ]; then
|
||||||
|
echo "::warning::$name: the last release was built with $built, trixie now installs ${cand:-nothing}" >&2
|
||||||
|
echo moved
|
||||||
|
fi
|
||||||
|
done | grep -q moved && moved=1
|
||||||
|
if [ "$moved" = 1 ]; then
|
||||||
|
echo "A package the mail path runs on has moved: cut a patch release, then update the table in $doc." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Every watched package is as the last release was built."
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: base packages
|
||||||
|
|
||||||
|
# Every Monday: has a Debian package the mail path runs on moved since the
|
||||||
|
# last release was built? A red run is the nudge to cut a patch release
|
||||||
|
# (security audit 2026-10-17, I5; .github/scripts/base-packages.sh).
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "17 5 * * 1"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
watch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
- name: Compare with trixie
|
||||||
|
run: sh .github/scripts/base-packages.sh
|
||||||
@@ -66,14 +66,14 @@ jobs:
|
|||||||
arch: arm64
|
arch: arm64
|
||||||
runs-on: ${{ matrix.runner }}
|
runs-on: ${{ matrix.runner }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
with:
|
with:
|
||||||
ref: v${{ needs.prepare.outputs.version }}
|
ref: v${{ needs.prepare.outputs.version }}
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Build image (native, loaded locally for the e2e gate)
|
- name: Build image (native, loaded locally for the e2e gate)
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: build/Dockerfile
|
file: build/Dockerfile
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
VERSION=${{ needs.prepare.outputs.version }}
|
VERSION=${{ needs.prepare.outputs.version }}
|
||||||
tags: selfpost:e2e
|
tags: selfpost:e2e
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26"
|
go-version: "1.26"
|
||||||
cache-dependency-path: test/e2e/go.sum
|
cache-dependency-path: test/e2e/go.sum
|
||||||
@@ -110,8 +110,22 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# The binaries about to be pushed, as built in the image - with the Go
|
||||||
|
# of golang:1.26-trixie, which may lag the runner's: a standard-library
|
||||||
|
# advisory fixed in a later patch fails the release until the base tag
|
||||||
|
# moves, which is the point (security audit 2026-10-17, I4).
|
||||||
|
- name: Vulnerabilities in the image's binaries
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
id=$(docker create selfpost:e2e)
|
||||||
|
trap 'docker rm -f "$id" >/dev/null' EXIT
|
||||||
|
for bin in panel selfpost-backup; do
|
||||||
|
docker cp "$id:/usr/local/bin/$bin" "/tmp/$bin"
|
||||||
|
go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 -mode=binary "/tmp/$bin"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Log in to ghcr.io
|
- name: Log in to ghcr.io
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -152,14 +166,14 @@ jobs:
|
|||||||
needs: [prepare, build]
|
needs: [prepare, build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
with:
|
with:
|
||||||
ref: v${{ needs.prepare.outputs.version }}
|
ref: v${{ needs.prepare.outputs.version }}
|
||||||
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Log in to ghcr.io
|
- name: Log in to ghcr.io
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ on:
|
|||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
# The job reads the repository and nothing else (security audit 2026-10-17,
|
||||||
|
# review of stages 2-6).
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
with:
|
with:
|
||||||
# design-first.sh reads each commit's parent and the template history.
|
# design-first.sh reads each commit's parent and the template history.
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -20,7 +25,7 @@ jobs:
|
|||||||
HEAD: ${{ github.event.pull_request.head.sha || github.sha }}
|
HEAD: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
run: bash .github/scripts/design-first.sh "$BASE" "$HEAD"
|
run: bash .github/scripts/design-first.sh "$BASE" "$HEAD"
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26"
|
go-version: "1.26"
|
||||||
cache: true
|
cache: true
|
||||||
@@ -51,6 +56,13 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
|
# Known vulnerabilities in the module's dependencies and in the
|
||||||
|
# standard library of this Go, reached from SelfPost's code (security
|
||||||
|
# audit 2026-10-17, I4: twelve stdlib advisories went unnoticed until
|
||||||
|
# the base image's tag happened to move). Pinned like staticcheck.
|
||||||
|
- name: Vulnerabilities
|
||||||
|
run: go run golang.org/x/vuln/cmd/govulncheck@v1.9.0 ./...
|
||||||
|
|
||||||
# The e2e module runs only in release.yml, after a release is published;
|
# The e2e module runs only in release.yml, after a release is published;
|
||||||
# a type error there would first show up then. Compile it here without
|
# a type error there would first show up then. Compile it here without
|
||||||
# running it: `go test -run` would still run TestMain, which brings up
|
# running it: `go test -run` would still run TestMain, which brings up
|
||||||
|
|||||||
+327
-10
@@ -5,6 +5,323 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- **Machine requirements by what is switched on** (guide § Installation,
|
||||||
|
README § Requirements), measured on the test installation instead of the
|
||||||
|
old unmeasured "1 vCPU, 512 MB-1 GB, 8-10 GB": 512 MB of host memory for
|
||||||
|
outbound mail with or without port 25; with the spam filter 768 MB when
|
||||||
|
port-25 messages are kept small and 1 GB for several 25 MiB messages at
|
||||||
|
once (rspamd measured at 118 MB at rest, 232 MB on one 25 MiB message,
|
||||||
|
424 MB on three); the proxy containers' own; one vCPU. Building the image needs 1.5 GB of memory - a build held to 1 GB was
|
||||||
|
killed in the Go compiler - and about 3 GB of free disk. The send log
|
||||||
|
grows about 2-3 KB a message for its retention.
|
||||||
|
|
||||||
|
## [2.16.0] - 2026-10-18
|
||||||
|
|
||||||
|
The queues as the 2.0 mockups drew them: Outbound › Queue a table, Inbound ›
|
||||||
|
Queue built. No migration; schema 14.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Inbound › Queue** (`/inbound/queue`, global role, with the inbound relay
|
||||||
|
on): mail accepted on port 25 that its upstream has not yet taken, a row
|
||||||
|
per recipient with the upstream's last reply - the screen `in-queue`, drawn
|
||||||
|
for 2.0 and not built until now. Help has its topic.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Outbound › Queue is the table the mockup draws** instead of
|
||||||
|
`postqueue -p`'s text: a row per recipient, oldest first - queue id, when
|
||||||
|
it arrived, sender, recipient, and the last reply of the receiving server
|
||||||
|
or why no connection was made - with *Details* to the message in the
|
||||||
|
Outbound log when it has a row there. The box head counts the messages and
|
||||||
|
their size; past 200 recipients the oldest 200 are listed and a line says
|
||||||
|
how many wait. Read from `postqueue -j` (Postfix 3.10.13,
|
||||||
|
`src/postqueue/showq_json.c`: `message_size`, and a recipient's
|
||||||
|
`delay_reason` when it has one). A recipient of an inbound domain is on
|
||||||
|
Inbound › Queue instead.
|
||||||
|
- **Overview's queue card** is *Queues*, "Both empty", "outbound and inbound"
|
||||||
|
while the inbound relay is on, as the mockup has it: Postfix keeps one queue
|
||||||
|
for both.
|
||||||
|
- `TestUnbuiltScreensAreOnTheRoadmap`: a screen the guards let pass unbuilt
|
||||||
|
must be named in `docs/roadmap.md`. The two queues had been deferred on
|
||||||
|
2026-10-09 with no roadmap row and were forgotten; development.md says so.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
Two gaps a comparison of every mockup with the panel found (the rest
|
||||||
|
matches):
|
||||||
|
|
||||||
|
- **Server › Users, the user form:** with *All outbound domains* (or *All
|
||||||
|
inbound domains*) ticked, that list's single domains are disabled, as the
|
||||||
|
mockup draws them - the server ignored them already, which the help line
|
||||||
|
says. panel.js does it; without scripts they stay clickable.
|
||||||
|
- **Server › Backup, Import a domain:** "the domain's settings" links to
|
||||||
|
Outbound › Domains, where each domain's settings are, as the mockup's link
|
||||||
|
does.
|
||||||
|
|
||||||
|
## [2.15.0] - 2026-10-18
|
||||||
|
|
||||||
|
The two proposals the security audit left as product choices. Migration
|
||||||
|
0014; schema 14.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Verified TLS to an inbound domain's upstream** (upstream-tls-verify; the
|
||||||
|
security audit's M7): a fourth choice beside Opportunistic, Required and
|
||||||
|
Off - Postfix's `secure` level, which also checks that the upstream's
|
||||||
|
certificate was issued by a trusted authority for its name. The upstream
|
||||||
|
is then given by name. Postfix's SMTP client now trusts Debian's
|
||||||
|
`ca-certificates`; mail to the recipients' MX is unchanged. Migration
|
||||||
|
0014 widens the stored TLS mode; schema 14.
|
||||||
|
- **A DMARC record for the server's own name** (server-dmarc-hint; the
|
||||||
|
security audit's S3): Server › Settings › Notification mail gives
|
||||||
|
`v=DMARC1; p=reject` at `_dmarc.<hostname>` beside the DKIM and SPF
|
||||||
|
records, checks it, and Health's card shows it. Without one a receiver
|
||||||
|
does not refuse mail forged in the server's name, a false password-reset
|
||||||
|
message among it; `p=none` is shown as weak.
|
||||||
|
|
||||||
|
## [2.14.5] - 2026-10-18
|
||||||
|
|
||||||
|
A line less in the mail log. No migration; schema 13.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **ETRN is discarded from the EHLO reply silently**: since `2.14.4` Postfix
|
||||||
|
logged "discarding EHLO keywords: ETRN" for every connection on every port
|
||||||
|
(seen on the test server after the rollout); `silent-discard` keeps the
|
||||||
|
discard and drops the line.
|
||||||
|
|
||||||
|
## [2.14.4] - 2026-10-17
|
||||||
|
|
||||||
|
The follow-ups of the security audit of 2026-10-17
|
||||||
|
([docs/security-audit-2026-10-17.md](docs/security-audit-2026-10-17.md)),
|
||||||
|
stages 0-6: its one high (A2), its seven mediums and the lows. No migration;
|
||||||
|
schema 13. Read *Upgrading* below: the TLS key's group, and copy
|
||||||
|
`antispam/` again where the sidecar runs.
|
||||||
|
|
||||||
|
### Upgrading
|
||||||
|
|
||||||
|
- **Give the TLS private key to group 10997** (audit I1): `chgrp 10997
|
||||||
|
certs/privkey.pem` - or certbot's `archive/<name>/privkey*.pem` - and the
|
||||||
|
same in whatever renews it. Group 10998, which the guide named until now,
|
||||||
|
has Postfix in it; the backup keeps working with either, but only 10997
|
||||||
|
keeps the key from Postfix's daemons.
|
||||||
|
- **With the antispam sidecar, copy `antispam/` again** (audit S4): rspamd
|
||||||
|
4.2.2, and a third file, `antispam/rspamd/worker-proxy.inc`, that the
|
||||||
|
fragment now mounts; `docker compose ... up -d` recreates the sidecar.
|
||||||
|
- **Behind Apache on the host, set `TRUSTED_PROXY_CIDR`** if it is not set
|
||||||
|
(audit A1): the guide's install step prints the gateway to put there; the
|
||||||
|
panel's log says so after the first requests when it is missing.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- **A stolen `global` session no longer takes a full backup** (security
|
||||||
|
audit 2026-10-17, A2, high): the user form set the signed-in user's own
|
||||||
|
password without the current one, which then passed the backup's password
|
||||||
|
check. Adding a user, setting a password, changing an e-mail or a role on
|
||||||
|
the user form, and deleting a user now ask for your own account password,
|
||||||
|
as a backup does, under the same limit on wrong guesses - which now
|
||||||
|
counts wrong passwords only.
|
||||||
|
- **One stranger behind a shared proxy no longer locks everyone out of
|
||||||
|
signing in** (audit A1, A8, A9): sign-in counts failures per address and
|
||||||
|
username (a hundred per address alone), a right password no longer
|
||||||
|
counts, a link's page is limited per link, a reset request per address
|
||||||
|
and name, and an IPv6 client is one per /64. With every request coming
|
||||||
|
from one private address with `X-Forwarded-For` and `TRUSTED_PROXY_CIDR`
|
||||||
|
empty, the log says what to set. Refused sign-ins are logged.
|
||||||
|
- **Reset links pause for a day after five** (audit A3): anyone who knew a
|
||||||
|
username could keep a live reset link arriving in that mailbox five times
|
||||||
|
an hour. After five in a day the owner is told once that further requests
|
||||||
|
are ignored for 24 hours.
|
||||||
|
- **`/healthz` is not answered through the reverse proxy** (audit I6):
|
||||||
|
only a request with no `X-Forwarded-For` from loopback or a private
|
||||||
|
address gets the liveness answer - the container's own check, the host,
|
||||||
|
a monitor next to it; the internet gets `404`.
|
||||||
|
- **Every container has a process ceiling** (audit I3): `pids_limit` 1024
|
||||||
|
for SelfPost, 256 for the proxies and rspamd, 64 for certbot.
|
||||||
|
- **A save that changes no map no longer reloads Postfix, and DNS
|
||||||
|
re-checks are limited** (audit W6/M9): every application, upstream,
|
||||||
|
recipient or report-address save reloaded Postfix, and *Re-check DNS*
|
||||||
|
bypassed the cache without limit. Now only a changed file is written and
|
||||||
|
reloaded (Health's *Reload configuration* always re-reads), and a user
|
||||||
|
re-checks twenty times in 15 minutes.
|
||||||
|
- **A domain import is bounded** (audit W4, W5, W8): a DKIM key over 4096
|
||||||
|
bits, more than 500 applications, and a domain named as the server
|
||||||
|
itself - whose key signs the panel's own mail - are refused.
|
||||||
|
- **The build is pinned and checked** (audit I4, I5): GitHub Actions by
|
||||||
|
commit, certbot and Caddy by release; govulncheck on the source in CI and
|
||||||
|
on the image's binaries before a release is pushed; a weekly check says
|
||||||
|
when a Debian package the mail path runs on has moved, so that a patch
|
||||||
|
release follows.
|
||||||
|
- **The signature covers `Content-Type`, `MIME-Version` and `Message-ID`**
|
||||||
|
when the message has them (audit M6), and a header line too long to be
|
||||||
|
delivered unbroken is named in the log as a body line is (M8).
|
||||||
|
- **A session made under a longer `PANEL_SESSION_IDLE_DAYS` is cut to the
|
||||||
|
current window** at its next request (audit A10), not kept until its old
|
||||||
|
expiry.
|
||||||
|
- **The antispam sidecar runs rspamd 4.2.2, hardened** (audit S4): from
|
||||||
|
3.11.1 (December 2024; the line between fixed a heap overflow, a
|
||||||
|
use-after-free on reload and parser crashes), with no Linux capability,
|
||||||
|
`no-new-privileges`, and only the milter on the compose network - the
|
||||||
|
scanner and the controller, whose default password is public, stay on the
|
||||||
|
sidecar's loopback. A third file, `antispam/rspamd/worker-proxy.inc`, is
|
||||||
|
mounted; copy it with the fragment.
|
||||||
|
- **The TLS private key no longer has to be readable by Postfix** (audit
|
||||||
|
I1): the guide gave it to group 10998 for the panel's backup, and
|
||||||
|
`postfix` is in that group, so every Postfix daemon - an smtpd talking to
|
||||||
|
the internet included - could read it. A new group, `backupkey` (10997),
|
||||||
|
holds the panel alone; the guide, Help and the backup's refusal name it.
|
||||||
|
|
||||||
|
- **A domain's rate limit is taken only by its own applications** (audit
|
||||||
|
M1): the level-2 domain limit was read from `MAIL FROM`, which reaches the
|
||||||
|
milter before Postfix holds it to the login, so a client that never
|
||||||
|
signed in, or another tenant's application, could use up a domain's
|
||||||
|
limit. It is now the signed-in application's domain.
|
||||||
|
- **One accepted message is one Inbound › Log row per recipient** (audit
|
||||||
|
M3): a recipient repeated in RCPT TO, up to Postfix's thousand, was a row
|
||||||
|
each, and the journal milter's rows did not count toward the 10 000-row
|
||||||
|
cut between the six-hourly prunes. Repeats are one row, a message's rows
|
||||||
|
are written in one transaction, and both writers count toward the cut.
|
||||||
|
- **A domain's custom DMARC report address may not be on the server's own
|
||||||
|
name** (audit W1): it opened a mailbox the whole internet could file
|
||||||
|
reports into, under any domain, or another domain's hosted one. Only the
|
||||||
|
hosted address of a domain that chose it is a mailbox now, and a report
|
||||||
|
must arrive at the address tagged with its domain.
|
||||||
|
- **A domain's DMARC reports are deleted with it** (audit A5/W2): reports
|
||||||
|
belong to a domain by name, and a domain deleted and added again showed
|
||||||
|
its old reports to whoever it was assigned to then.
|
||||||
|
- **The sender map is rebuilt one change at a time** (audit W3): two
|
||||||
|
application changes at once could leave the map without the newer one
|
||||||
|
until the next change.
|
||||||
|
- **A stranger's recipient no longer writes an Inbound › Log row** (audit
|
||||||
|
M2): Postfix quotes a refused recipient in its reply with the quotes
|
||||||
|
removed, so `RCPT TO:<"; from=<x> to=<y> …"@domain>` spelled a second
|
||||||
|
sender and recipient there, and the parser took the first. It now takes
|
||||||
|
Postfix's own tail at the end of the line.
|
||||||
|
- **The sender check keeps two `From:` and two `Sender:` lines** (audit M5),
|
||||||
|
not every one an application sends: the judgement needs no more, and a
|
||||||
|
hundred long lines were megabytes held per connection.
|
||||||
|
- **A link's token stays out of the duplicate-cookie log line** (audit A4),
|
||||||
|
as it already did out of the origin check's.
|
||||||
|
- **ETRN is refused and no longer offered** (audit S6): Postfix's default
|
||||||
|
let anyone on port 25 flush the queue toward an inbound domain's upstream.
|
||||||
|
- **SECURITY.md names the 2.x line as the supported one** (audit X1); its
|
||||||
|
table still said 1.x.
|
||||||
|
- **The entrypoint follows no link at the DMARC maps** (audit I2): the
|
||||||
|
three `dmarc_*` maps root creates at the start were missing from the list
|
||||||
|
of paths where a symbolic link is removed first.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- security.md says what the code does where it said more (audit A6, A7,
|
||||||
|
W7): a wrong method answers `405` before the role is looked at; the setup
|
||||||
|
token is made anew at the first request after it expired; two GETs make
|
||||||
|
the server's own DKIM key on first view. The README's local trial says
|
||||||
|
that the compose file is the hardened deployment.
|
||||||
|
|
||||||
|
- **The upstream's "Required" TLS checks no certificate**, and the guide and
|
||||||
|
Help now say so (audit M7): it guards against a listener, not against one
|
||||||
|
who answers in the upstream's place.
|
||||||
|
|
||||||
|
|
||||||
|
## [2.14.3] - 2026-10-17
|
||||||
|
|
||||||
|
A tag beside a label. No migration; schema 13.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **A tag beside a label no longer squeezes the field under it**: the
|
||||||
|
e-mail's "not confirmed" on Account made its label taller and left less
|
||||||
|
room above the field than Username has (the owner's phone).
|
||||||
|
|
||||||
|
## [2.14.2] - 2026-10-17
|
||||||
|
|
||||||
|
The last small items of the day. No migration; schema 13.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Health dials the inbound journal's socket** with the inbound relay on:
|
||||||
|
the milter that writes Inbound › Log was the one the panel did not check.
|
||||||
|
The send log's socket is named as the mockup names it, `journal-milter`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **A DMARC report says which day it is**: its day stays the reporter's, in
|
||||||
|
UTC (owner, 2026-10-17: accepted as it is), and the report's page now says
|
||||||
|
so under its window; Settings and Help name it as the one exception to
|
||||||
|
the panel's zone. A daily report from midnight to midnight is one day, not
|
||||||
|
two.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- The guide's gaps from the docs audit: `/license` and `/server/components`,
|
||||||
|
DMARC pages' paths, `selfpost-backup -o`, `-i` and `-version`, the
|
||||||
|
Inbound log's retention field, `PANEL_COOKIE_SECURE`.
|
||||||
|
|
||||||
|
## [2.14.1] - 2026-10-17
|
||||||
|
|
||||||
|
Overview's head in the mockup's words. No migration; schema 13.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Overview's head says it as the mockup does** (overview-head): "Mail is
|
||||||
|
flowing. One thing needs attention." - or "Nothing needs attention", or
|
||||||
|
"Mail may not be flowing" when a check fails - and under it each check
|
||||||
|
that is not fine with its value ("TLS certificate: expires in 12 days.").
|
||||||
|
|
||||||
|
## [2.14.0] - 2026-10-17
|
||||||
|
|
||||||
|
Password reset by e-mail, to a confirmed address. Migration 0013; schema 13.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Password reset by e-mail** (password-reset): *Forgot your password?* on
|
||||||
|
the sign-in page sends a one-time link, for 30 minutes, to the account's
|
||||||
|
e-mail - once that address has been confirmed by a link sent to it. A new
|
||||||
|
address is sent its confirmation link when it is saved (Account, or the
|
||||||
|
user form); Account shows whether it is confirmed and can send the link
|
||||||
|
again. Notifications go to the address confirmed or not. Setting a new
|
||||||
|
password from a link signs every session of the account out. Migration
|
||||||
|
0013; schema 13.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Changing your e-mail on Account asks for your password**, as changing
|
||||||
|
the password does: the address is where a reset link goes.
|
||||||
|
|
||||||
|
## [2.13.1] - 2026-10-17
|
||||||
|
|
||||||
|
The documents and the panel's words checked against the code. No migration;
|
||||||
|
schema 12.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **A panel password's twelve characters are characters**: the minimum
|
||||||
|
counted bytes, so six letters of a non-Latin alphabet passed for twelve.
|
||||||
|
It counts characters now, as the backup password always did. Passwords
|
||||||
|
already set are not affected.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **The panel and its mockups say the same** (docs-audit): an inbound
|
||||||
|
domain's MX box says when it was checked and its side menu links the
|
||||||
|
domain's Inbound log; a deferred message's history ends with the wait in
|
||||||
|
the queue; Overview says it refreshes on its own; DMARC's hint for an
|
||||||
|
unaligned third party reads as the mockup. Help says what the code does:
|
||||||
|
an application's password is kept by the server so an export can carry
|
||||||
|
it, every password check of a backup counts, a password set for another
|
||||||
|
user signs them out everywhere, the limit mail is daily per sending
|
||||||
|
domain, and a domain administrator is no longer shown the Filter lists
|
||||||
|
topic (their page is the global role's).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- The documents checked against the code (docs-audit, 2026-10-17): the
|
||||||
|
guide, architecture, schema-migrations, security, development, README and
|
||||||
|
product say what the code does.
|
||||||
|
|
||||||
## [2.13.0] - 2026-10-17
|
## [2.13.0] - 2026-10-17
|
||||||
|
|
||||||
The panel in the container's time zone. No migration; schema 12.
|
The panel in the container's time zone. No migration; schema 12.
|
||||||
@@ -116,7 +433,7 @@ Nothing for an operator to do.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Inbound › Log shows what the upstream answered** (inbound-upstream-result,
|
- **Inbound › Log shows what the upstream answered** (inbound-upstream-result,
|
||||||
[the plan](docs/plans/inbound-upstream-result.md)): an *Upstream* column
|
the plan, since removed): an *Upstream* column
|
||||||
beside the decision - *queued*, *held*, *sent*, *deferred* (a greylisting, an
|
beside the decision - *queued*, *held*, *sent*, *deferred* (a greylisting, an
|
||||||
upstream that cannot be reached), *bounced* (refused, or expired in the
|
upstream that cannot be reached), *bounced* (refused, or expired in the
|
||||||
queue) - opening the last reply, the host that gave it and when. Read from
|
queue) - opening the last reply, the host that gave it and when. Read from
|
||||||
@@ -195,7 +512,7 @@ schema 11.
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **The panel signs outgoing mail with DKIM itself** (panel-dkim-signing,
|
- **The panel signs outgoing mail with DKIM itself** (panel-dkim-signing,
|
||||||
[the plan](docs/plans/panel-dkim-signing.md)): a milter of the panel's on
|
the plan, since removed): a milter of the panel's on
|
||||||
two sockets, one for applications' mail - signed with the key of the
|
two sockets, one for applications' mail - signed with the key of the
|
||||||
domain the login belongs to - and one for the panel's own, signed with the
|
domain the login belongs to - and one for the panel's own, signed with the
|
||||||
server's name. `relaxed/simple`, RFC 6376 § 5.4.1's headers and `From`
|
server's name. `relaxed/simple`, RFC 6376 § 5.4.1's headers and `From`
|
||||||
@@ -382,7 +699,7 @@ No migration; schema 11.
|
|||||||
The listener now sets `normalize` - the setting Postfix recommends and 3.9
|
The listener now sets `normalize` - the setting Postfix recommends and 3.9
|
||||||
made the default: DATA ends only at the standard `<CR><LF>.<CR><LF>`, a
|
made the default: DATA ends only at the standard `<CR><LF>.<CR><LF>`, a
|
||||||
bare newline elsewhere is read as a line end. Found while reading for
|
bare newline elsewhere is read as a line end. Found while reading for
|
||||||
[own-mta-builds](docs/own-mta-builds-2026-10-15.md) (§ 5); `test/e2e`
|
own-mta-builds (§ 5, since removed); `test/e2e`
|
||||||
sends the sequence and finds one message at the upstream with all of it
|
sends the sequence and finds one message at the upstream with all of it
|
||||||
inside. Only installations with `INBOUND_RELAY_ENABLE=true` or
|
inside. Only installations with `INBOUND_RELAY_ENABLE=true` or
|
||||||
`DMARC_REPORTS_ENABLE=true` have the listener.
|
`DMARC_REPORTS_ENABLE=true` have the listener.
|
||||||
@@ -1098,7 +1415,7 @@ Documentation only; no product code changes.
|
|||||||
held to - any address of the application's domain, or the addresses of its
|
held to - any address of the application's domain, or the addresses of its
|
||||||
list, with or without a `+extension` - and refuses anything else with
|
list, with or without a `+extension` - and refuses anything else with
|
||||||
`550 5.7.1`, a domain this server does not sign for included
|
`550 5.7.1`, a domain this server does not sign for included
|
||||||
([plans/from-header-binding.md](docs/plans/from-header-binding.md)).
|
(plans/from-header-binding.md, since removed).
|
||||||
- **The check is strict.** It is the first milter of the chain, on a socket
|
- **The check is strict.** It is the first milter of the chain, on a socket
|
||||||
of its own (`SENDER_CHECK_SOCKET`), attached with
|
of its own (`SENDER_CHECK_SOCKET`), attached with
|
||||||
`default_action=tempfail`: while the panel does not answer, Postfix defers
|
`default_action=tempfail`: while the panel does not answer, Postfix defers
|
||||||
@@ -1152,7 +1469,7 @@ Documentation only; no product code changes.
|
|||||||
a user's own switch, `users.notify`, on for everyone; `notify_state`, what
|
a user's own switch, `users.notify`, on for everyone; `notify_state`, what
|
||||||
the panel has already written to its users about. The instance switch is a
|
the panel has already written to its users about. The instance switch is a
|
||||||
settings key, on unless turned off
|
settings key, on unless turned off
|
||||||
([plans/panel-notifications.md](docs/plans/panel-notifications.md)).
|
(plans/panel-notifications.md, since removed).
|
||||||
- **The panel writes to its users when something needs them.** Four things,
|
- **The panel writes to its users when something needs them.** Four things,
|
||||||
each said once: a sending domain's DKIM, SPF or DMARC record stops matching
|
each said once: a sending domain's DKIM, SPF or DMARC record stops matching
|
||||||
(after two hourly checks in a row, and once more when it matches again); a
|
(after two hourly checks in a row, and once more when it matches again); a
|
||||||
@@ -1202,7 +1519,7 @@ Documentation only; no product code changes.
|
|||||||
The table that will hold what Postfix wrote about a message for as long as
|
The table that will hold what Postfix wrote about a message for as long as
|
||||||
its send-log row lives, with the store methods to write a line, to write it
|
its send-log row lives, with the store methods to write a line, to write it
|
||||||
once, to read the lines of a row and to prune
|
once, to read the lines of a row and to prune
|
||||||
([plans/delivery-log-storage.md](docs/plans/delivery-log-storage.md)).
|
(plans/delivery-log-storage.md, since removed).
|
||||||
- **The log tailer keeps what Postfix writes about queued messages.** A
|
- **The log tailer keeps what Postfix writes about queued messages.** A
|
||||||
`mail.log` line headed by a queue id is stored with the line's own time, one
|
`mail.log` line headed by a queue id is stored with the line's own time, one
|
||||||
transaction per pass over the log; after an unclean stop a line read twice
|
transaction per pass over the log; after an unclean stop a line read twice
|
||||||
@@ -1802,7 +2119,7 @@ screens. Nothing in `internal/web` changes; the design stays `candidate`
|
|||||||
scrolling at 1280px and up. Documented on the components page and in the
|
scrolling at 1280px and up. Documented on the components page and in the
|
||||||
plan (§ Type and tables).
|
plan (§ Type and tables).
|
||||||
- Panel redesign, header comparison sheet
|
- Panel redesign, header comparison sheet
|
||||||
([docs/assets/panel-redesign/header/](docs/assets/panel-redesign/header/index.html)):
|
(`docs/assets/panel-redesign/header/`, since removed):
|
||||||
at nav size the stamp dissolves into the brick bar and the perforated edge
|
at nav size the stamp dissolves into the brick bar and the perforated edge
|
||||||
was drawn inverted (round bumps instead of round holes). Three replacements
|
was drawn inverted (round bumps instead of round holes). Three replacements
|
||||||
beside the current header — **A** wordmark with the stamp's real hole-to-tooth
|
beside the current header — **A** wordmark with the stamp's real hole-to-tooth
|
||||||
@@ -1876,7 +2193,7 @@ screens. Nothing in `internal/web` changes; the design stays `candidate`
|
|||||||
grammar (`measure` / `fill` plus Web Awesome layout CSS), no JS component
|
grammar (`measure` / `fill` plus Web Awesome layout CSS), no JS component
|
||||||
library, new mockups rather than `docs/assets/panel-ui/`. Status: candidate.
|
library, new mockups rather than `docs/assets/panel-ui/`. Status: candidate.
|
||||||
- Roadmap item and plan for `/preflight` installation check page
|
- Roadmap item and plan for `/preflight` installation check page
|
||||||
([docs/plans/preflight.md](docs/plans/preflight.md)) — instance-level
|
(`docs/plans/preflight.md`, since removed) — instance-level
|
||||||
infrastructure verification (rDNS, TLS, ports, HELO banner, proxy headers,
|
infrastructure verification (rDNS, TLS, ports, HELO banner, proxy headers,
|
||||||
DKIM socket, test email). Status: candidate.
|
DKIM socket, test email). Status: candidate.
|
||||||
|
|
||||||
@@ -2050,7 +2367,7 @@ antispam and quarantine. Upgrading from 1.9.0 is a tag bump; no migration.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- [docs/plans/inbound-antispam-panel.md](docs/plans/inbound-antispam-panel.md) —
|
- `docs/plans/inbound-antispam-panel.md` (since removed) —
|
||||||
agreed plan for inbound antispam journal (inbound-journal milter + mail.log
|
agreed plan for inbound antispam journal (inbound-journal milter + mail.log
|
||||||
rejects) and instance-wide allow/deny lists synced to rspamd maps; target
|
rejects) and instance-wide allow/deny lists synced to rspamd maps; target
|
||||||
`1.10.0`; Composer → Opus → Fable workflow.
|
`1.10.0`; Composer → Opus → Fable workflow.
|
||||||
@@ -2250,7 +2567,7 @@ tag bump; no schema migration.
|
|||||||
self-contained backups and persistent queue.
|
self-contained backups and persistent queue.
|
||||||
|
|
||||||
- docs: HTML mockups for a full panel UI refresh
|
- docs: HTML mockups for a full panel UI refresh
|
||||||
([docs/assets/panel-ui/](docs/assets/panel-ui/index.html)) — current screens,
|
(`docs/assets/panel-ui/`, since removed) — current screens,
|
||||||
agreed and candidate roadmap surfaces (queue-retries, inbound-relay,
|
agreed and candidate roadmap surfaces (queue-retries, inbound-relay,
|
||||||
dmarc-reports, panel-docs), a hybrid width (ops pages use the window, forms
|
dmarc-reports, panel-docs), a hybrid width (ops pages use the window, forms
|
||||||
keep a reading measure), and a 390px emergency layout. The stamp and brick
|
keep a reading measure), and a 390px emergency layout. The stamp and brick
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ send log and DNS checks in the panel, encrypted backups.
|
|||||||
- Optional inbound relay on port 25 (backup-MX / forwarder; off by default)
|
- Optional inbound relay on port 25 (backup-MX / forwarder; off by default)
|
||||||
- Optional DMARC aggregate report ingest on port 25 (off by default)
|
- Optional DMARC aggregate report ingest on port 25 (off by default)
|
||||||
- In-panel Help: a page of topics, opened from the `?` in the head of a box
|
- In-panel Help: a page of topics, opened from the `?` in the head of a box
|
||||||
- Per-application client IP allow-list for SMTP AUTH (optional)
|
- Per-application client IP allow-list for submitting mail (optional; refused at MAIL FROM, not an authentication boundary)
|
||||||
- Web panel grouped by mail direction — Overview, Outbound (domains, log, queue, DMARC reports), Inbound, Server (health, system log, backup, users, settings)
|
- Web panel grouped by mail direction — Overview, Outbound (domains, log, queue, DMARC reports), Inbound, Server (health, system log, backup, users, settings)
|
||||||
- Multi-domain relay — each SASL application is bound to one sending domain
|
- Multi-domain relay — each SASL application is bound to one sending domain
|
||||||
- DNS status checks (PTR, SPF, DKIM, DMARC) with in-panel re-check
|
- DNS status checks (PTR, SPF, DKIM, DMARC) with in-panel re-check
|
||||||
- Two-level rate limiting — IP backstop (Postfix), per-domain ceilings, and trusted-IP app overrides
|
- Two-level rate limiting — IP backstop (Postfix), per-domain ceilings, and per-application overrides
|
||||||
- Full-server backup and single-domain export/import (optional password encryption)
|
- Full-server backup and single-domain export/import (optional password encryption)
|
||||||
- Single Docker image; production data in a `./data` bind mount (the quick start below uses a named Docker volume instead)
|
- Single Docker image; production data in a `./data` bind mount (the quick start below uses a named Docker volume instead)
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ send log and DNS checks in the panel, encrypted backups.
|
|||||||
| [Architecture](docs/architecture.md) | As-built technical design |
|
| [Architecture](docs/architecture.md) | As-built technical design |
|
||||||
| [Security design](docs/security.md) | Mandatory requirements, accepted risks, the CSRF ADR |
|
| [Security design](docs/security.md) | Mandatory requirements, accepted risks, the CSRF ADR |
|
||||||
| [Development](docs/development.md) | Building, testing, docs rules, model routing, commits |
|
| [Development](docs/development.md) | Building, testing, docs rules, model routing, commits |
|
||||||
| [Roadmap](docs/roadmap.md) | Open work (1.x+) — direction, not commitments |
|
| [Roadmap](docs/roadmap.md) | Open work — direction, not commitments |
|
||||||
| [CHANGELOG](CHANGELOG.md) | Release history |
|
| [CHANGELOG](CHANGELOG.md) | Release history |
|
||||||
|
|
||||||
Found a vulnerability? Do not open an issue — [SECURITY.md](SECURITY.md) has
|
Found a vulnerability? Do not open an issue — [SECURITY.md](SECURITY.md) has
|
||||||
@@ -62,9 +62,23 @@ missing PTR record for you. Details: [Operator guide](docs/guide.md).
|
|||||||
|
|
||||||
- Docker + Compose v2 on the host
|
- Docker + Compose v2 on the host
|
||||||
- A reverse proxy in front of the panel (SelfPost never terminates HTTPS itself)
|
- A reverse proxy in front of the panel (SelfPost never terminates HTTPS itself)
|
||||||
- Rough sizing: **1 vCPU**, **512 MB–1 GB RAM**, **8–10 GB disk** (send log and
|
- Machine, by what is switched on (measured; details and disk growth in the
|
||||||
rotated `mail.log` are the main growth drivers; both sit in the `./data`
|
guide's [Machine requirements](docs/guide.md#machine-requirements)):
|
||||||
volume, and both are capped — 90 days and 14 files by default)
|
|
||||||
|
| Switched on | Host memory, at least | Images on disk |
|
||||||
|
|---|---|---|
|
||||||
|
| Outbound only (465/587), with or without port 25 (inbound relay, DMARC reports) | 512 MB | 315 MB |
|
||||||
|
| With the spam filter (rspamd sidecar), port-25 messages kept small | 768 MB | 626 MB |
|
||||||
|
| With the spam filter and 25 MiB messages, several at once | 1 GB | 626 MB |
|
||||||
|
|
||||||
|
rspamd takes about 100 MB more for each large message it scans; a reverse
|
||||||
|
proxy in a container adds its own few megabytes.
|
||||||
|
|
||||||
|
One vCPU is enough. **Building the image needs 1.5 GB of memory and about
|
||||||
|
3 GB of free disk** (the Go compiler: a 1 GB build was killed) - on a
|
||||||
|
smaller machine, build elsewhere and `docker load` the image. The send log
|
||||||
|
is what grows: about 2-3 KB a message for the retention (90 days by
|
||||||
|
default) - 10 000 messages a day keep about 2.5 GB.
|
||||||
|
|
||||||
### Network and IP
|
### Network and IP
|
||||||
|
|
||||||
@@ -112,8 +126,8 @@ outbound mail will not reach the real internet without DNS, PTR, and port 25.
|
|||||||
> local image and pulls nothing:
|
> local image and pulls nothing:
|
||||||
>
|
>
|
||||||
> ```sh
|
> ```sh
|
||||||
> docker build -f build/Dockerfile --build-arg VERSION=2.13.0 \
|
> docker build -f build/Dockerfile --build-arg VERSION=2.16.0 \
|
||||||
> -t ghcr.io/mixeme/selfpost:2.13.0 .
|
> -t ghcr.io/mixeme/selfpost:2.16.0 .
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -122,9 +136,14 @@ docker run --rm -d --name selfpost-try \
|
|||||||
-e SELFPOST_HOSTNAME=mail.local.test \
|
-e SELFPOST_HOSTNAME=mail.local.test \
|
||||||
-e PANEL_COOKIE_SECURE=false \
|
-e PANEL_COOKIE_SECURE=false \
|
||||||
-v selfpost-try-data:/data \
|
-v selfpost-try-data:/data \
|
||||||
ghcr.io/mixeme/selfpost:2.13.0
|
ghcr.io/mixeme/selfpost:2.16.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This trial runs with Docker's default capabilities and no
|
||||||
|
`no-new-privileges`; the compose file of a real deployment
|
||||||
|
(`deploy/docker-compose.yml`) drops every capability but the few it names,
|
||||||
|
sets `no-new-privileges` and a process ceiling.
|
||||||
|
|
||||||
**Get the setup URL** (pick one):
|
**Get the setup URL** (pick one):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
+10
-3
@@ -3,15 +3,22 @@
|
|||||||
## Supported versions
|
## Supported versions
|
||||||
|
|
||||||
SelfPost follows SemVer. Fixes are issued for the **latest minor release of the
|
SelfPost follows SemVer. Fixes are issued for the **latest minor release of the
|
||||||
1.x line** only; there is no backporting to earlier minors. Upgrade before
|
2.x line** only; there is no backporting to earlier minors or to 1.x. Upgrade before
|
||||||
reporting if you are behind — the image tag is `ghcr.io/mixeme/selfpost:X.Y.Z`.
|
reporting if you are behind — the image tag is `ghcr.io/mixeme/selfpost:X.Y.Z`.
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
|---|---|
|
|---|---|
|
||||||
| latest 1.x | yes |
|
| latest 2.x | yes |
|
||||||
| earlier 1.x | no — upgrade first |
|
| earlier 2.x | no — upgrade first |
|
||||||
|
| 1.x | no — upgrade to 2.x (guide § Upgrading) |
|
||||||
| 0.x | no (pre-release) |
|
| 0.x | no (pre-release) |
|
||||||
|
|
||||||
|
A security update of a Debian package the image runs - Postfix, Cyrus SASL,
|
||||||
|
OpenSSL, Berkeley DB - arrives with the next patch release built after it:
|
||||||
|
published image tags are never rebuilt. A weekly check
|
||||||
|
(`.github/workflows/base-packages.yml`) says when one has moved, and a patch
|
||||||
|
release follows.
|
||||||
|
|
||||||
## Reporting a vulnerability
|
## Reporting a vulnerability
|
||||||
|
|
||||||
**Do not open a public issue.** Use GitHub's private vulnerability reporting:
|
**Do not open a public issue.** Use GitHub's private vulnerability reporting:
|
||||||
|
|||||||
+15
-4
@@ -57,7 +57,7 @@ ENV DEBIAN_FRONTEND=noninteractive
|
|||||||
# test say whether it does.
|
# test say whether it does.
|
||||||
# No DKIM package is installed: the panel signs itself (internal/milter,
|
# No DKIM package is installed: the panel signs itself (internal/milter,
|
||||||
# sign.go) and makes the keys in Go (internal/domain/dkim.go); OpenDKIM left
|
# sign.go) and makes the keys in Go (internal/domain/dkim.go); OpenDKIM left
|
||||||
# the image in 2.8.0 (docs/plans/panel-dkim-signing.md).
|
# the image in 2.8.0 (docs/architecture.md § DKIM signing).
|
||||||
# systemd-standalone-sysusers is named so that cron-daemon-common, which
|
# systemd-standalone-sysusers is named so that cron-daemon-common, which
|
||||||
# logrotate brings (logrotate 3.22.0-1, debian/control: "Depends: cron |
|
# logrotate brings (logrotate 3.22.0-1, debian/control: "Depends: cron |
|
||||||
# anacron | cron-daemon | systemd-sysv"), creates its group with it rather
|
# anacron | cron-daemon | systemd-sysv"), creates its group with it rather
|
||||||
@@ -109,9 +109,10 @@ RUN groupadd --system --gid 10999 panel && useradd --system --uid 10999 --gid p
|
|||||||
# (set up in entrypoint.sh) - lets Postfix read the sasldb2/sender map and
|
# (set up in entrypoint.sh) - lets Postfix read the sasldb2/sender map and
|
||||||
# connect to the panel's milters. The DKIM keys are the panel's alone. (The
|
# connect to the panel's milters. The DKIM keys are the panel's alone. (The
|
||||||
# supervisor control socket has a group of its own, `supervisor`, below.)
|
# supervisor control socket has a group of its own, `supervisor`, below.)
|
||||||
# The id is fixed, because the host sees it: an operator gives the TLS
|
# The id is fixed, because the host saw it: until 2.14.4 an operator gave the
|
||||||
# private key on the host to this group by number, so that the panel can put
|
# TLS private key on the host to this group by number - now to backupkey,
|
||||||
# it into a full backup (guide § Taking a full backup). It lies outside the
|
# below, which Postfix is not in - and a key given then still names it. It
|
||||||
|
# lies outside the
|
||||||
# range the distribution hands out to system groups, 100-999 (adduser 3.152,
|
# range the distribution hands out to system groups, 100-999 (adduser 3.152,
|
||||||
# /etc/adduser.conf: "Default: FIRST_SYSTEM_GID=100, LAST_SYSTEM_GID=999";
|
# /etc/adduser.conf: "Default: FIRST_SYSTEM_GID=100, LAST_SYSTEM_GID=999";
|
||||||
# passwd 1:4.17.4-2, /etc/login.defs: SYS_GID_MIN 101, SYS_GID_MAX 999 - both
|
# passwd 1:4.17.4-2, /etc/login.defs: SYS_GID_MIN 101, SYS_GID_MAX 999 - both
|
||||||
@@ -127,6 +128,16 @@ RUN groupadd --system --gid 10998 selfpost \
|
|||||||
&& usermod -aG selfpost panel \
|
&& usermod -aG selfpost panel \
|
||||||
&& usermod -aG selfpost postfix
|
&& usermod -aG selfpost postfix
|
||||||
|
|
||||||
|
# The host's TLS private key is given to this group, not to selfpost, so that
|
||||||
|
# the panel can put it into a full backup (guide § Taking a full backup) and
|
||||||
|
# no Postfix daemon can read it: postfix is in selfpost, and a memory-safety
|
||||||
|
# bug in an smtpd reached from the internet would otherwise yield the key of
|
||||||
|
# the mail and of the panel (security audit 2026-10-17, I1). Postfix itself
|
||||||
|
# serves a root-only copy (postfix-config.sh). The panel is the one member.
|
||||||
|
# Fixed for the same reason as selfpost's, next to it.
|
||||||
|
RUN groupadd --system --gid 10997 backupkey \
|
||||||
|
&& usermod -aG backupkey panel
|
||||||
|
|
||||||
# The panel reads the mail queue with `postqueue -p` (and the status page's
|
# The panel reads the mail queue with `postqueue -p` (and the status page's
|
||||||
# queue card). postqueue is setgid postdrop, which normally gives it the group
|
# queue card). postqueue is setgid postdrop, which normally gives it the group
|
||||||
# needed to reach Postfix's showq socket - but the documented deployment runs
|
# needed to reach Postfix's showq socket - but the documented deployment runs
|
||||||
|
|||||||
+5
-4
@@ -91,6 +91,7 @@ drop_links /data/dkim /data/dkim.new /data/opendkim /data/sasl /data/postfix /da
|
|||||||
/data/sasl/sasldb2 /data/postfix/queue \
|
/data/sasl/sasldb2 /data/postfix/queue \
|
||||||
/data/postfix/sender_login_maps /data/postfix/relay_domains \
|
/data/postfix/sender_login_maps /data/postfix/relay_domains \
|
||||||
/data/postfix/transport /data/postfix/relay_recipients /data/postfix/tls_policy \
|
/data/postfix/transport /data/postfix/relay_recipients /data/postfix/tls_policy \
|
||||||
|
/data/postfix/dmarc_recipients /data/postfix/dmarc_transport /data/postfix/dmarc_relay_domains \
|
||||||
/data/log/mail.log
|
/data/log/mail.log
|
||||||
# Restored backups or previously-created state may contain panel-owned files
|
# Restored backups or previously-created state may contain panel-owned files
|
||||||
# under /data; make sure they stay writable without disturbing anything that a
|
# under /data; make sure they stay writable without disturbing anything that a
|
||||||
@@ -110,7 +111,7 @@ find /data -mindepth 1 -maxdepth 1 ! -name log ! -name postfix ! -name sasl ! -n
|
|||||||
# serves (cmd/panel/signer.go). Until 2.8.0 the keys were under
|
# serves (cmd/panel/signer.go). Until 2.8.0 the keys were under
|
||||||
# /data/opendkim, shared with OpenDKIM; 2.8.x copied them to /data/dkim at its
|
# /data/opendkim, shared with OpenDKIM; 2.8.x copied them to /data/dkim at its
|
||||||
# first start and kept the old tree for a rollback to 2.7.x until the next
|
# first start and kept the old tree for a rollback to 2.7.x until the next
|
||||||
# release, this one (docs/plans/panel-dkim-signing.md, Q2). It is removed
|
# release, this one (CHANGELOG [2.9.0]). It is removed
|
||||||
# once /data/dkim holds a key. A /data that never ran 2.8.x - keys under the
|
# once /data/dkim holds a key. A /data that never ran 2.8.x - keys under the
|
||||||
# old tree only - stops the start instead: the panel would make the server's
|
# old tree only - stops the start instead: the panel would make the server's
|
||||||
# own name a new key, and its published record would no longer match; 2.8.x
|
# own name a new key, and its published record would no longer match; 2.8.x
|
||||||
@@ -149,11 +150,11 @@ rm -f /data/sasl/sasldb2.new
|
|||||||
# Postfix state under /data (architecture.md § Persistence). The panel writes
|
# Postfix state under /data (architecture.md § Persistence). The panel writes
|
||||||
# sender_login_maps; Postfix owns the on-disk queue tree under queue/.
|
# sender_login_maps; Postfix owns the on-disk queue tree under queue/.
|
||||||
mkdir -p /data/postfix/queue
|
mkdir -p /data/postfix/queue
|
||||||
[ -e /data/postfix/sender_login_maps ] || : > /data/postfix/sender_login_maps
|
[ -e /data/postfix/sender_login_maps ] || [ -L /data/postfix/sender_login_maps ] || : > /data/postfix/sender_login_maps
|
||||||
# Inbound relay maps (written by the panel when INBOUND_RELAY_ENABLE=true).
|
# Inbound relay maps (written by the panel when INBOUND_RELAY_ENABLE=true).
|
||||||
# Empty files keep postfix check happy if the flag is on before any domain exists.
|
# Empty files keep postfix check happy if the flag is on before any domain exists.
|
||||||
for f in relay_domains transport relay_recipients tls_policy; do
|
for f in relay_domains transport relay_recipients tls_policy; do
|
||||||
[ -e "/data/postfix/$f" ] || : > "/data/postfix/$f"
|
[ -e "/data/postfix/$f" ] || [ -L "/data/postfix/$f" ] || : > "/data/postfix/$f"
|
||||||
done
|
done
|
||||||
# Every map here is the panel's to write and Postfix's to read - the sender
|
# Every map here is the panel's to write and Postfix's to read - the sender
|
||||||
# map, the inbound and the DMARC maps alike - so each file beside queue/ is
|
# map, the inbound and the DMARC maps alike - so each file beside queue/ is
|
||||||
@@ -195,7 +196,7 @@ chmod 0755 /data/antispam
|
|||||||
# because parent directory has insecure permissions" when the directory's
|
# because parent directory has insecure permissions" when the directory's
|
||||||
# group is not root and has write permission.
|
# group is not root and has write permission.
|
||||||
mkdir -p /data/log
|
mkdir -p /data/log
|
||||||
[ -e /data/log/mail.log ] || : > /data/log/mail.log
|
[ -e /data/log/mail.log ] || [ -L /data/log/mail.log ] || : > /data/log/mail.log
|
||||||
chown -R postfix:selfpost /data/log
|
chown -R postfix:selfpost /data/log
|
||||||
chmod 2750 /data/log
|
chmod 2750 /data/log
|
||||||
find /data/log -type f -exec chmod 0640 {} +
|
find /data/log -type f -exec chmod 0640 {} +
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ reload() {
|
|||||||
# same_pair says whether a certificate file and a private key file belong
|
# same_pair says whether a certificate file and a private key file belong
|
||||||
# together, by comparing the public key each one prints. openssl-x509(1) and
|
# together, by comparing the public key each one prints. openssl-x509(1) and
|
||||||
# openssl-pkey(1) do not say the two print it byte for byte alike; measured
|
# openssl-pkey(1) do not say the two print it byte for byte alike; measured
|
||||||
# in the image, 2026-10-15, OpenSSL 3.0.22: identical for RSA-2048, P-256 and
|
# in the image, 2026-10-15, OpenSSL 3.0.x of bookworm, before trixie's 3.5
|
||||||
|
# (re-read on 3.5.7 is due with the next look at this script): identical for RSA-2048, P-256 and
|
||||||
# P-384 keys, and for a P-256 key written in SEC1 form ("EC PRIVATE KEY") -
|
# P-384 keys, and for a P-256 key written in SEC1 form ("EC PRIVATE KEY") -
|
||||||
# certbot 2.0 and later issue ECDSA keys by default (assumptions C-X1).
|
# certbot 2.0 and later issue ECDSA keys by default (assumptions C-X1).
|
||||||
same_pair() {
|
same_pair() {
|
||||||
|
|||||||
+30
-6
@@ -94,7 +94,7 @@ HOSTNAME_VALUE="${SELFPOST_HOSTNAME:-$(hostname -f 2>/dev/null || hostname)}"
|
|||||||
# other user; Postfix does not enforce it (smtpd loads the key as root in
|
# other user; Postfix does not enforce it (smtpd loads the key as root in
|
||||||
# pre_jail_init(), Postfix 3.10.13 src/smtpd/smtpd.c), but the mount cannot
|
# pre_jail_init(), Postfix 3.10.13 src/smtpd/smtpd.c), but the mount cannot
|
||||||
# give that mode - it is :ro and owned by the host's user, often readable by
|
# give that mode - it is :ro and owned by the host's user, often readable by
|
||||||
# group 10998 for the panel's backup. So the pair is copied into a directory of
|
# group 10997 (the panel's alone) for the panel's backup. So the pair is copied into a directory of
|
||||||
# Postfix's own, the key root:root 0600, before postconf and `postfix check`
|
# Postfix's own, the key root:root 0600, before postconf and `postfix check`
|
||||||
# (assumptions D-arch:154).
|
# (assumptions D-arch:154).
|
||||||
TLS_CERT_SRC="${TLS_CERT_FILE:-/etc/postfix/tls/fullchain.pem}"
|
TLS_CERT_SRC="${TLS_CERT_FILE:-/etc/postfix/tls/fullchain.pem}"
|
||||||
@@ -297,9 +297,19 @@ postconf -e \
|
|||||||
"local_transport=error:local mail delivery is disabled"
|
"local_transport=error:local mail delivery is disabled"
|
||||||
|
|
||||||
# Outbound delivery: straight to the recipient MX, opportunistic TLS.
|
# Outbound delivery: straight to the recipient MX, opportunistic TLS.
|
||||||
|
# The client trusts the authorities Debian's ca-certificates ships, for an
|
||||||
|
# inbound domain's upstream with Verified TLS (the "secure" level in the TLS
|
||||||
|
# policy map, upstream-tls-verify): Postfix trusts none by default - the
|
||||||
|
# *_tls_CAfile defaults are empty and tls_append_default_CA is "no" (Postfix
|
||||||
|
# 3.10.13, src/global/mail_params.h, DEF_SMTP_TLS_CA_FILE,
|
||||||
|
# DEF_TLS_APPEND_DEF_CA). The levels may and encrypt check nothing and only
|
||||||
|
# log the verdict; mail to the recipients' MX is unchanged. The file is the
|
||||||
|
# bundle update-ca-certificates writes (ca-certificates, installed in the
|
||||||
|
# image), read by smtp(8) unchrooted (chroot is off below).
|
||||||
postconf -e \
|
postconf -e \
|
||||||
"smtp_tls_security_level=may" \
|
"smtp_tls_security_level=may" \
|
||||||
"smtp_tls_note_starttls_offer=yes"
|
"smtp_tls_note_starttls_offer=yes" \
|
||||||
|
"smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt"
|
||||||
|
|
||||||
# TLS server material shared by every inbound service. auth_only guarantees
|
# TLS server material shared by every inbound service. auth_only guarantees
|
||||||
# credentials are never accepted before TLS is up on any port. Postfix 3.10.13,
|
# credentials are never accepted before TLS is up on any port. Postfix 3.10.13,
|
||||||
@@ -323,11 +333,21 @@ postconf -e \
|
|||||||
# and the banner names no distribution (Debian's adds "(Debian)": postfix
|
# and the banner names no distribution (Debian's adds "(Debian)": postfix
|
||||||
# 3.10.13-0+deb13u1, debian/main.cf.in:15 "(@DEB_VENDOR@)", filled in by
|
# 3.10.13-0+deb13u1, debian/main.cf.in:15 "(@DEB_VENDOR@)", filled in by
|
||||||
# debian/rules:118; the upstream default is "$myhostname ESMTP $mail_name").
|
# debian/rules:118; the upstream default is "$myhostname ESMTP $mail_name").
|
||||||
|
# ETRN is refused to everyone and not offered: Postfix's default lets any
|
||||||
|
# client flush the queue toward an inbound domain's upstream
|
||||||
|
# (fast_flush_domains = $relay_domains), and nothing here needs it (security
|
||||||
|
# audit 2026-10-17, S6; Postfix 3.10.13, src/smtpd/smtpd.c, etrn_cmd() asks
|
||||||
|
# smtpd_check_etrn(), whose "reject" answers reject_code, 554). The discard is
|
||||||
|
# silent: without the pseudo keyword smtpd logs "discarding EHLO keywords:
|
||||||
|
# ETRN" for every connection (src/smtpd/smtpd.c:2016, EHLO_MASK_SILENT;
|
||||||
|
# src/global/ehlo_mask.c:88; seen on the test server after 2.14.4).
|
||||||
postconf -e \
|
postconf -e \
|
||||||
"smtpd_tls_protocols=>=TLSv1.2" \
|
"smtpd_tls_protocols=>=TLSv1.2" \
|
||||||
"smtpd_tls_mandatory_protocols=>=TLSv1.2" \
|
"smtpd_tls_mandatory_protocols=>=TLSv1.2" \
|
||||||
"smtp_tls_mandatory_protocols=>=TLSv1.2" \
|
"smtp_tls_mandatory_protocols=>=TLSv1.2" \
|
||||||
"disable_vrfy_command=yes" \
|
"disable_vrfy_command=yes" \
|
||||||
|
"smtpd_etrn_restrictions=reject" \
|
||||||
|
"smtpd_discard_ehlo_keywords=silent-discard,etrn" \
|
||||||
'smtpd_banner=$myhostname ESMTP'
|
'smtpd_banner=$myhostname ESMTP'
|
||||||
|
|
||||||
# SASL: Cyrus with the local sasldb2 the panel maintains. The realm is left
|
# SASL: Cyrus with the local sasldb2 the panel maintains. The realm is left
|
||||||
@@ -406,7 +426,7 @@ postconf -e \
|
|||||||
# deferred (docs/security.md).
|
# deferred (docs/security.md).
|
||||||
#
|
#
|
||||||
# The panel's DKIM signer follows, strict too: unreachable, it defers rather
|
# The panel's DKIM signer follows, strict too: unreachable, it defers rather
|
||||||
# than send unsigned (docs/plans/panel-dkim-signing.md). It signs with the
|
# than send unsigned (docs/architecture.md § DKIM signing). It signs with the
|
||||||
# key of the login's domain. The journal-milter is monitoring only and is
|
# key of the login's domain. The journal-milter is monitoring only and is
|
||||||
# fail-open (default_action=accept): its failure must never block the relay.
|
# fail-open (default_action=accept): its failure must never block the relay.
|
||||||
# Per-milter settings use Postfix 3.0+ brace syntax.
|
# Per-milter settings use Postfix 3.0+ brace syntax.
|
||||||
@@ -534,8 +554,12 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
|||||||
TRANSPORT_SETTING=""
|
TRANSPORT_SETTING=""
|
||||||
RECIPIENT_RESTRICTIONS="reject"
|
RECIPIENT_RESTRICTIONS="reject"
|
||||||
if [ "${INBOUND_RELAY_ENABLE}" = "true" ]; then
|
if [ "${INBOUND_RELAY_ENABLE}" = "true" ]; then
|
||||||
|
# [ -e ] is false for a dangling link and the redirect would follow
|
||||||
|
# it: the entrypoint's drop_links removes links at these paths, and
|
||||||
|
# this refuses to create through one all the same (audit 2026-10-17,
|
||||||
|
# I2).
|
||||||
for f in "$RELAY_DOMAINS_MAP" "$TRANSPORT_MAP" "$RELAY_RECIPIENTS_MAP" "$TLS_POLICY_MAP"; do
|
for f in "$RELAY_DOMAINS_MAP" "$TRANSPORT_MAP" "$RELAY_RECIPIENTS_MAP" "$TLS_POLICY_MAP"; do
|
||||||
[ -e "$f" ] || : > "$f"
|
[ -e "$f" ] || [ -L "$f" ] || : > "$f"
|
||||||
done
|
done
|
||||||
RELAY_DOMAINS_SETTING="texthash:${RELAY_DOMAINS_MAP}"
|
RELAY_DOMAINS_SETTING="texthash:${RELAY_DOMAINS_MAP}"
|
||||||
TRANSPORT_SETTING="texthash:${TRANSPORT_MAP}"
|
TRANSPORT_SETTING="texthash:${TRANSPORT_MAP}"
|
||||||
@@ -543,7 +567,7 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
|||||||
fi
|
fi
|
||||||
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
if [ "${DMARC_REPORTS_ENABLE}" = "true" ]; then
|
||||||
for f in "$DMARC_RECIPIENTS_MAP" "$DMARC_TRANSPORT_MAP" "$DMARC_RELAY_DOMAINS_MAP"; do
|
for f in "$DMARC_RECIPIENTS_MAP" "$DMARC_TRANSPORT_MAP" "$DMARC_RELAY_DOMAINS_MAP"; do
|
||||||
[ -e "$f" ] || : > "$f"
|
[ -e "$f" ] || [ -L "$f" ] || : > "$f"
|
||||||
done
|
done
|
||||||
ln -sf /usr/local/bin/panel /usr/local/bin/dmarc-ingest
|
ln -sf /usr/local/bin/panel /usr/local/bin/dmarc-ingest
|
||||||
# flags=, user= and argv= are arguments of pipe(8), not -o parameters:
|
# flags=, user= and argv= are arguments of pipe(8), not -o parameters:
|
||||||
@@ -732,7 +756,7 @@ if [ "${INBOUND_RELAY_ENABLE}" = "true" ] || [ "${DMARC_REPORTS_ENABLE}" = "true
|
|||||||
# only here (above), so no client is spared. The submission ports take
|
# only here (above), so no client is spared. The submission ports take
|
||||||
# the default, normalize as well since this Postfix: an application whose
|
# the default, normalize as well since this Postfix: an application whose
|
||||||
# SMTP library ends DATA with a bare <LF>.<LF> is no longer read to its
|
# SMTP library ends DATA with a bare <LF>.<LF> is no longer read to its
|
||||||
# end there (own-mta-builds, § 5: their smuggled transaction would still
|
# end there (docs/security.md § Mail path: their smuggled transaction would still
|
||||||
# be the authenticated session's, so they were left to the default).
|
# be the authenticated session's, so they were left to the default).
|
||||||
postconf -P \
|
postconf -P \
|
||||||
"smtp/inet/smtpd_forbid_bare_newline=normalize" \
|
"smtp/inet/smtpd_forbid_bare_newline=normalize" \
|
||||||
|
|||||||
@@ -14,13 +14,15 @@ import (
|
|||||||
// are compared as they are, not as lists kept by hand beside them (audit
|
// are compared as they are, not as lists kept by hand beside them (audit
|
||||||
// 2026-10-12, stage 5 - the three lists this file held were compared with each
|
// 2026-10-12, stage 5 - the three lists this file held were compared with each
|
||||||
// other and with nothing else). Read: what the panel's code asks of the
|
// other and with nothing else). Read: what the panel's code asks of the
|
||||||
// environment (cmd/panel and internal, tests aside) and what the start-up
|
// environment (cmd/panel, cmd/selfpost-backup and internal, tests aside) and what the start-up
|
||||||
// scripts take with a default (build/*.sh, "${VAR:-…}"). Documented: the
|
// scripts take with a default (build/*.sh, "${VAR:-…}"). Documented: the
|
||||||
// operator's table in guide.md § Environment variables, with the paragraph
|
// operator's table in guide.md § Environment variables, with the paragraph
|
||||||
// under it, and the internal list in architecture.md § Configuration.
|
// under it, and the internal list in architecture.md § Configuration.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
goEnvRead = regexp.MustCompile(`(?:envDefault|envInt|envBool|envSeconds|envDuration|os\.Getenv|os\.LookupEnv)\("([A-Z][A-Z0-9_]+)"`)
|
goEnvRead = regexp.MustCompile(`(?:envDefault|envInt|envBool|envSeconds|envDuration|os\.Getenv|os\.LookupEnv)\("([A-Z][A-Z0-9_]+)"`)
|
||||||
|
// A name kept in a constant, as the backup CLI keeps its password's.
|
||||||
|
goEnvConst = regexp.MustCompile(`\w+Env\s*=\s*"([A-Z][A-Z0-9_]+)"`)
|
||||||
scriptEnvRead = regexp.MustCompile(`\$\{([A-Z][A-Z0-9_]+):[-?=]`)
|
scriptEnvRead = regexp.MustCompile(`\$\{([A-Z][A-Z0-9_]+):[-?=]`)
|
||||||
docEnvName = regexp.MustCompile("`([A-Z][A-Z0-9_]+)`")
|
docEnvName = regexp.MustCompile("`([A-Z][A-Z0-9_]+)`")
|
||||||
)
|
)
|
||||||
@@ -38,12 +40,16 @@ func readFile(t *testing.T, path string) string {
|
|||||||
func envRead(t *testing.T) map[string]bool {
|
func envRead(t *testing.T) map[string]bool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
keys := map[string]bool{}
|
keys := map[string]bool{}
|
||||||
for _, root := range []string{".", filepath.Join("..", "..", "internal")} {
|
for _, root := range []string{".", filepath.Join("..", "selfpost-backup"), filepath.Join("..", "..", "internal")} {
|
||||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, m := range goEnvRead.FindAllStringSubmatch(readFile(t, path), -1) {
|
text := readFile(t, path)
|
||||||
|
for _, m := range goEnvRead.FindAllStringSubmatch(text, -1) {
|
||||||
|
keys[m[1]] = true
|
||||||
|
}
|
||||||
|
for _, m := range goEnvConst.FindAllStringSubmatch(text, -1) {
|
||||||
keys[m[1]] = true
|
keys[m[1]] = true
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ func newPanel(cfg config, st *store.Store) (*web.Server, error) {
|
|||||||
SignSocket: cfg.signSocket,
|
SignSocket: cfg.signSocket,
|
||||||
SignLocalSocket: cfg.signLocalSocket,
|
SignLocalSocket: cfg.signLocalSocket,
|
||||||
JournalSocket: cfg.journalSocket,
|
JournalSocket: cfg.journalSocket,
|
||||||
|
InboundJournalSocket: cfg.inboundJournalSocket,
|
||||||
SenderSocket: cfg.senderSocket,
|
SenderSocket: cfg.senderSocket,
|
||||||
SessionIdleDays: cfg.sessionIdleDays,
|
SessionIdleDays: cfg.sessionIdleDays,
|
||||||
DNSResolvers: cfg.dnsResolvers,
|
DNSResolvers: cfg.dnsResolvers,
|
||||||
@@ -169,7 +170,9 @@ func serveHTTP(ctx context.Context, cfg config, st *store.Store) error {
|
|||||||
srv := newHTTPServer(cfg.httpAddr, srvApp.Handler())
|
srv := newHTTPServer(cfg.httpAddr, srvApp.Handler())
|
||||||
|
|
||||||
// Shut the server down cleanly when the process is asked to stop.
|
// Shut the server down cleanly when the process is asked to stop.
|
||||||
|
shutDown := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
defer close(shutDown)
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -180,5 +183,13 @@ func serveHTTP(ctx context.Context, cfg config, st *store.Store) error {
|
|||||||
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// ListenAndServe returns at the stop, before its requests have
|
||||||
|
// (net/http, Server.Shutdown); Shutdown returns once they have. The links
|
||||||
|
// they mail after their answers go on after that, and end before the
|
||||||
|
// database closes.
|
||||||
|
<-shutDown
|
||||||
|
drainCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
srvApp.Drain(drainCtx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -318,6 +318,13 @@ func openData(cfg config, resync func(*store.Store) error) (st *store.Store, res
|
|||||||
}
|
}
|
||||||
if restored {
|
if restored {
|
||||||
log.Printf("restore manifest accepted; regenerating mail-path maps from SQLite")
|
log.Printf("restore manifest accepted; regenerating mail-path maps from SQLite")
|
||||||
|
// A link taken after the backup was made would work again from it
|
||||||
|
// (security review, 2026-10-17); a link sent before a restore is
|
||||||
|
// asked for again.
|
||||||
|
if err := st.DeleteAllUserLinks(); err != nil {
|
||||||
|
_ = st.Close()
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
if err := resync(st); err != nil {
|
if err := resync(st); err != nil {
|
||||||
_ = st.Close()
|
_ = st.Close()
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
@@ -330,8 +337,8 @@ func openData(cfg config, resync func(*store.Store) error) (st *store.Store, res
|
|||||||
return st, restored, nil
|
return st, restored, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// run starts the panel's three roles and blocks until a shutdown signal or the
|
// run starts the panel's roles (the list below) and blocks until a shutdown
|
||||||
// first fatal error from any role. A signal triggers a clean stop of all
|
// signal or the first fatal error from any role. A signal triggers a clean stop of all
|
||||||
// roles; a role error cancels the others and is returned so the process exits
|
// roles; a role error cancels the others and is returned so the process exits
|
||||||
// non-zero (letting supervisord/Docker see the failure — architecture.md §
|
// non-zero (letting supervisord/Docker see the failure — architecture.md §
|
||||||
// Image and processes).
|
// Image and processes).
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const aliveEvery = 2 * time.Second
|
|||||||
// try again later"), so while this role is not serving, mail is deferred:
|
// try again later"), so while this role is not serving, mail is deferred:
|
||||||
// what that costs is counted here and
|
// what that costs is counted here and
|
||||||
// by the log tailer, and shown on Server > Health
|
// by the log tailer, and shown on Server > Health
|
||||||
// (docs/plans/from-header-binding.md § Statistics).
|
// (docs/architecture.md § Sender check).
|
||||||
func serveSenderCheck(ctx context.Context, cfg config, st *store.Store) error {
|
func serveSenderCheck(ctx context.Context, cfg config, st *store.Store) error {
|
||||||
ln, err := listenMilter(cfg.senderSocket)
|
ln, err := listenMilter(cfg.senderSocket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,10 +9,11 @@
|
|||||||
# Requires INBOUND_RELAY_ENABLE=true in .env. The milter sees the real client
|
# Requires INBOUND_RELAY_ENABLE=true in .env. The milter sees the real client
|
||||||
# IP, HELO and PTR — unlike the upstream, which only sees SelfPost's address.
|
# IP, HELO and PTR — unlike the upstream, which only sees SelfPost's address.
|
||||||
#
|
#
|
||||||
# The rspamd image is pinned to one build, 3.11.1-3 - "3.11" and "3.11.1"
|
# The rspamd image is pinned to one build, 4.2.2-0 - "4.2", "4" and "latest"
|
||||||
# move with each rebuild (Docker Hub rspamd/rspamd tags 3.11.1-0 ... -3) -
|
# are rebuilt every night - and it is the build the comments in
|
||||||
# and it is the build the comments in antispam/rspamd/ were read against;
|
# antispam/rspamd/ were read against (rspamd 4.2.2, rspamd-docker v4.2.2+0;
|
||||||
# bump it deliberately, and read them again. Default milter action is fail-open (accept) so a down sidecar
|
# 3.11.1-3 until 2.14.4, security audit 2026-10-17, S4); bump it
|
||||||
|
# deliberately, and read them again. Default milter action is fail-open (accept) so a down sidecar
|
||||||
# does not block backup-MX. Set INBOUND_ANTISPAM_MILTER_ACTION=tempfail to
|
# does not block backup-MX. Set INBOUND_ANTISPAM_MILTER_ACTION=tempfail to
|
||||||
# defer instead.
|
# defer instead.
|
||||||
|
|
||||||
@@ -26,12 +27,23 @@ services:
|
|||||||
- antispam
|
- antispam
|
||||||
|
|
||||||
antispam:
|
antispam:
|
||||||
# The milter: rspamd 3.11.1, conf/worker-proxy.inc, "milter = yes" for the
|
# The milter: rspamd 4.2.2, conf/worker-proxy.inc, "milter = yes" for the
|
||||||
# rspamd_proxy worker, which the image binds to *:11332 (rspamd-docker
|
# rspamd_proxy worker, bound to *:11332 by antispam/rspamd/worker-proxy.inc
|
||||||
# v3.11.1+3, patches/rspamd-docker-config.diff), and runs as uid 11333
|
# while every other worker stays on loopback (RSPAMD_LOCAL_ADDR below);
|
||||||
# (its Dockerfile, USER 11333:11333).
|
# it runs as uid 11333 (rspamd-docker v4.2.2+0, Dockerfile, USER
|
||||||
image: rspamd/rspamd:3.11.1-3
|
# 11333:11333) and needs no capability: it starts unprivileged, binds
|
||||||
|
# ports above 1024 and runs nothing setuid (src/rspamd.c, detect_priv()).
|
||||||
|
image: rspamd/rspamd:4.2.2-0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Workers on loopback; the proxy alone is put back on every address.
|
||||||
|
RSPAMD_LOCAL_ADDR: localhost
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
# A ceiling on processes and threads (security audit 2026-10-17, I3).
|
||||||
|
pids_limit: 256
|
||||||
# No host ports: only SelfPost talks to the milter on the compose network.
|
# No host ports: only SelfPost talks to the milter on the compose network.
|
||||||
volumes:
|
volumes:
|
||||||
# The verdict headers SelfPost's inbound journal reads.
|
# The verdict headers SelfPost's inbound journal reads.
|
||||||
@@ -39,4 +51,6 @@ services:
|
|||||||
# The allow and deny lists kept in the panel: the rules, and the files
|
# The allow and deny lists kept in the panel: the rules, and the files
|
||||||
# SelfPost writes them to.
|
# SelfPost writes them to.
|
||||||
- ./antispam/rspamd/multimap.conf:/etc/rspamd/local.d/multimap.conf:ro
|
- ./antispam/rspamd/multimap.conf:/etc/rspamd/local.d/multimap.conf:ro
|
||||||
|
# The proxy (the milter) on the compose network, nothing else.
|
||||||
|
- ./antispam/rspamd/worker-proxy.inc:/etc/rspamd/local.d/worker-proxy.inc:ro
|
||||||
- ./data/antispam:/etc/rspamd/selfpost:ro
|
- ./data/antispam:/etc/rspamd/selfpost:ro
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
# rspamd removes headers of these names that a sender wrote himself before it
|
# rspamd removes headers of these names that a sender wrote himself before it
|
||||||
# adds its own - for every client, because of the two lines below. Without
|
# adds its own - for every client, because of the two lines below. Without
|
||||||
# them it would do neither for a client it counts as local or one that
|
# them it would do neither for a client it counts as local or one that
|
||||||
# authenticated: rspamd 3.11.1, src/plugins/lua/milter_headers.lua,
|
# authenticated: rspamd 4.2.2 (as 3.11.1), src/plugins/lua/milter_headers.lua,
|
||||||
# skip_wanted() - skip_local and skip_authenticated are on by default, and
|
# skip_wanted() - skip_local and skip_authenticated are on by default, and
|
||||||
# "local" is loopback, a unix socket, or an address in local_addrs, the
|
# "local" is loopback, a unix socket, or an address in local_addrs, the
|
||||||
# private ranges (src/lua/lua_ip.c, lua_ip_is_local(); conf/options.inc). The
|
# private ranges (src/lua/lua_ip.c, lua_ip_is_local(); conf/options.inc). The
|
||||||
@@ -20,7 +20,9 @@
|
|||||||
# message carrying its own X-Spamd-Result and X-Rspamd-Action): without the
|
# message carrying its own X-Spamd-Result and X-Rspamd-Action): without the
|
||||||
# two lines, from 10.0.0.5 or as an authenticated user, the milter reply
|
# two lines, from 10.0.0.5 or as an authenticated user, the milter reply
|
||||||
# removed neither header, added neither, and added the signature; with
|
# removed neither header, added neither, and added the signature; with
|
||||||
# them, both removed and rspamd's own added, as from a public address.
|
# them, both removed and rspamd's own added, as from a public address. The
|
||||||
|
# same again on 2026-10-17 in rspamd/rspamd:4.2.2-0 (from 10.0.0.5), and the
|
||||||
|
# signature equal to HMAC-SHA256 of the queue id computed with openssl.
|
||||||
#
|
#
|
||||||
# Without this file the journal still lists accepted mail, with no verdict
|
# Without this file the journal still lists accepted mail, with no verdict
|
||||||
# next to it.
|
# next to it.
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
# signature travels on with the mail and is of no use to whoever sees it: it
|
# signature travels on with the mail and is of no use to whoever sees it: it
|
||||||
# fits that one queue id.
|
# fits that one queue id.
|
||||||
#
|
#
|
||||||
# rspamd 3.11.1, src/plugins/lua/milter_headers.lua: a custom routine is called
|
# rspamd 4.2.2 (as 3.11.1), src/plugins/lua/milter_headers.lua: a custom routine is called
|
||||||
# as function(task, common_meta) and returns error, headers to add, headers to
|
# as function(task, common_meta) and returns error, headers to add, headers to
|
||||||
# remove, metadata; a remove value of 0 removes every header of that name
|
# remove, metadata; a remove value of 0 removes every header of that name
|
||||||
# (docs.rspamd.com/modules/milter_headers, "Custom routines").
|
# (docs.rspamd.com/modules/milter_headers, "Custom routines").
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# The allow and deny lists kept in the panel (Inbound > Filter lists). SelfPost
|
# The allow and deny lists kept in the panel (Inbound > Filter lists). SelfPost
|
||||||
# writes them as six files under data/antispam/, which the fragment mounts at
|
# writes them as six files under data/antispam/, which the fragment mounts at
|
||||||
# /etc/rspamd/selfpost; rspamd rereads a file when it changes, no restart.
|
# /etc/rspamd/selfpost; rspamd rereads a file when it changes, no restart.
|
||||||
# (rspamd 3.11.1, src/libserver/maps/map.c: a file map is watched with libev's
|
# (rspamd 4.2.2 (as 3.11.1), src/libserver/maps/map.c: a file map is watched with libev's
|
||||||
# ev_stat - inotify where the kernel has it, else a stat poll every
|
# ev_stat - inotify where the kernel has it, else a stat poll every
|
||||||
# map_watch_interval x map_file_watch_multiplier, 5min x 0.1, conf/options.inc;
|
# map_watch_interval x map_file_watch_multiplier, 5min x 0.1, conf/options.inc;
|
||||||
# docs.rspamd.com/configuration/maps asks for an atomic rename, which
|
# docs.rspamd.com/configuration/maps asks for an atomic rename, which
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# allowed one is not accepted outright - rspamd would then let whichever of
|
# allowed one is not accepted outright - rspamd would then let whichever of
|
||||||
# the two rules ran first decide - but given a weight no spam score
|
# the two rules ran first decide - but given a weight no spam score
|
||||||
# outweighs, which a refusal by the deny list still overrides. (rspamd
|
# outweighs, which a refusal by the deny list still overrides. (rspamd
|
||||||
# 3.11.1, src/plugins/lua/multimap.lua: every rule is registered as a
|
# 4.2.2 (as 3.11.1), src/plugins/lua/multimap.lua: every rule is registered as a
|
||||||
# "normal" symbol, one with an action with the "passthrough" augmentation;
|
# "normal" symbol, one with an action with the "passthrough" augmentation;
|
||||||
# no "prefilter" setting is read. Measured with this file in a throwaway
|
# no "prefilter" setting is read. Measured with this file in a throwaway
|
||||||
# rspamd/rspamd:3.11.1-3, 2026-10-15: every mix of an allow and a deny entry
|
# rspamd/rspamd:3.11.1-3, 2026-10-15: every mix of an allow and a deny entry
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
# bounce whose From: domain is on the deny list was refused by default and
|
# bounce whose From: domain is on the deny list was refused by default and
|
||||||
# is not with this setting; a sender on the list is refused either way.
|
# is not with this setting; a sender on the list is refused either way.
|
||||||
#
|
#
|
||||||
# A domain entry is that domain, not its subdomains. (rspamd 3.11.1,
|
# A domain entry is that domain, not its subdomains. (rspamd 4.2.2 (as 3.11.1),
|
||||||
# src/libserver/maps/map_helpers.c: a hash map is looked up by the whole key,
|
# src/libserver/maps/map_helpers.c: a hash map is looked up by the whole key,
|
||||||
# case-insensitively - rspamd_icase_hash, rspamd_lc_cmp; "email:domain" gives
|
# case-insensitively - rspamd_icase_hash, rspamd_lc_cmp; "email:domain" gives
|
||||||
# the address's domain part, src/plugins/lua/multimap.lua, apply_addr_filter().)
|
# the address's domain part, src/plugins/lua/multimap.lua, apply_addr_filter().)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Mounted into the sidecar as /etc/rspamd/local.d/worker-proxy.inc.
|
||||||
|
#
|
||||||
|
# The milter - the proxy worker - is the one listener SelfPost needs on the
|
||||||
|
# compose network. The fragment sets RSPAMD_LOCAL_ADDR=localhost, which keeps
|
||||||
|
# every worker on the container's loopback: the normal worker (11333), whose
|
||||||
|
# scan accepts a client-named file or shared-memory input, and the controller
|
||||||
|
# (11334), whose default password is the well-known "q1" (rspamd 4.2.2,
|
||||||
|
# conf/rspamd.conf: each worker's bind_socket is the env variable LOCAL_ADDR,
|
||||||
|
# localhost by default, and its port; conf/worker-controller.inc: password =
|
||||||
|
# "q1"; the rspamd-docker v4.2.2+0 Dockerfile sets RSPAMD_LOCAL_ADDR=* for all
|
||||||
|
# three). This line puts the proxy alone back on every address: a local.d
|
||||||
|
# scalar at priority 1 replaces the default one (contrib/libucl/ucl_parser.c,
|
||||||
|
# UCL_DUPLICATE_MERGE). The proxy still reaches the normal worker on
|
||||||
|
# localhost, as before (security audit 2026-10-17, S4).
|
||||||
|
#
|
||||||
|
# rspamd expands its template syntax in these files, comments included:
|
||||||
|
# quote none of it here.
|
||||||
|
bind_socket = "*:11332";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Example host Apache vhost for the SelfPost panel (default scenario). Install
|
# Example host Apache vhost for the SelfPost panel (default scenario). Install
|
||||||
# on the host running Apache — NOT inside the SelfPost container. Requires
|
# on the host running Apache — NOT inside the SelfPost container. Requires
|
||||||
# mod_ssl and mod_proxy/mod_proxy_http enabled (`a2enmod ssl proxy proxy_http`).
|
# mod_ssl, mod_proxy/mod_proxy_http and mod_rewrite enabled (`a2enmod ssl proxy proxy_http rewrite`).
|
||||||
#
|
#
|
||||||
# Certificates: obtain with the Apache certbot plugin against THIS vhost
|
# Certificates: obtain with the Apache certbot plugin against THIS vhost
|
||||||
# (`certbot --apache -d mail.example.com`). Certbot edits this file in place to
|
# (`certbot --apache -d mail.example.com`). Certbot edits this file in place to
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
# the CA URL's host and path, "acme-v02.api.letsencrypt.org-directory" for Let's
|
# the CA URL's host and path, "acme-v02.api.letsencrypt.org-directory" for Let's
|
||||||
# Encrypt). caddy/modules/caddytls/automation.go, DefaultIssuers(): ZeroSSL is a
|
# Encrypt). caddy/modules/caddytls/automation.go, DefaultIssuers(): ZeroSSL is a
|
||||||
# second issuer only when an email is configured, and the Caddyfile below sets
|
# second issuer only when an email is configured, and the Caddyfile below sets
|
||||||
# none. Read at the projects' master of 2026-10-14: `caddy:2` is a moving tag.
|
# none. Read at the projects' master of 2026-10-14, when the image was the
|
||||||
|
# moving `caddy:2`; it is pinned below now, and a bump re-reads these.
|
||||||
#
|
#
|
||||||
# Merge with the base file — run this from the deploy/ directory (Compose
|
# Merge with the base file — run this from the deploy/ directory (Compose
|
||||||
# resolves every relative path in both files against the directory of the
|
# resolves every relative path in both files against the directory of the
|
||||||
@@ -46,8 +47,12 @@ services:
|
|||||||
- ./caddy/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/<hostname>:/etc/postfix/tls:ro
|
- ./caddy/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/<hostname>:/etc/postfix/tls:ro
|
||||||
|
|
||||||
caddy:
|
caddy:
|
||||||
image: caddy:2
|
# Pinned to a release: "2" moves under a running server (security audit
|
||||||
|
# 2026-10-17, I4). Bump it deliberately.
|
||||||
|
image: caddy:2.11.7
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# A ceiling on processes and threads (security audit 2026-10-17, I3).
|
||||||
|
pids_limit: 256
|
||||||
depends_on:
|
depends_on:
|
||||||
- selfpost
|
- selfpost
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
selfpost:
|
selfpost:
|
||||||
image: ghcr.io/mixeme/selfpost:2.13.0
|
image: ghcr.io/mixeme/selfpost:2.16.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
SELFPOST_HOSTNAME: "${SELFPOST_HOSTNAME:?set the mail/panel hostname, e.g. mail.example.com}"
|
||||||
@@ -99,10 +99,12 @@ services:
|
|||||||
# when the variable is true.
|
# when the variable is true.
|
||||||
- "25:25"
|
- "25:25"
|
||||||
- "127.0.0.1:8080:8080"
|
- "127.0.0.1:8080:8080"
|
||||||
# Hardening. SelfPost's entrypoint still needs to run as
|
# Hardening. SelfPost's entrypoint runs as root to fix /data ownership
|
||||||
# root very briefly to fix /data ownership and normalise permissions
|
# and normalise permissions under the shared `selfpost` group (see
|
||||||
# under the shared `selfpost` group (see build/entrypoint.sh) before
|
# build/entrypoint.sh) before supervisord drops the panel to an
|
||||||
# supervisord drops the panel to an unprivileged user — so this cannot be
|
# unprivileged user; supervisord, Postfix's master, cert-reload and
|
||||||
|
# logrotate stay root for the container's life, which is inherent to
|
||||||
|
# them (security audit 2026-10-17, I3) — so this cannot be
|
||||||
# `user: panel` or a fully read-only rootfs without breaking that startup
|
# `user: panel` or a fully read-only rootfs without breaking that startup
|
||||||
# self-healing. What IS applied: no privilege escalation past what the
|
# self-healing. What IS applied: no privilege escalation past what the
|
||||||
# image already grants, and every Linux capability dropped except the
|
# image already grants, and every Linux capability dropped except the
|
||||||
@@ -151,3 +153,9 @@ services:
|
|||||||
- SETGID
|
- SETGID
|
||||||
- DAC_OVERRIDE
|
- DAC_OVERRIDE
|
||||||
- KILL
|
- KILL
|
||||||
|
# A ceiling on processes and threads, so that a runaway or compromised
|
||||||
|
# program cannot use up the host's (security audit 2026-10-17, I3).
|
||||||
|
# Postfix starts up to 100 processes per service by default
|
||||||
|
# (default_process_limit), and a busy server runs three smtpd services,
|
||||||
|
# cleanup and smtp at once; 1024 leaves them room.
|
||||||
|
pids_limit: 1024
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ services:
|
|||||||
nginx:
|
nginx:
|
||||||
image: nginx:1.27
|
image: nginx:1.27
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# A ceiling on processes and threads (security audit 2026-10-17, I3).
|
||||||
|
pids_limit: 256
|
||||||
depends_on:
|
depends_on:
|
||||||
- selfpost
|
- selfpost
|
||||||
ports:
|
ports:
|
||||||
@@ -58,7 +60,10 @@ services:
|
|||||||
- ./nginx/certbot-www:/var/www/certbot:ro
|
- ./nginx/certbot-www:/var/www/certbot:ro
|
||||||
|
|
||||||
certbot:
|
certbot:
|
||||||
image: certbot/certbot:latest
|
# Pinned like every image here: "latest" moves under a running server
|
||||||
|
# (security audit 2026-10-17, I4). Bump it deliberately.
|
||||||
|
image: certbot/certbot:v5.8.0
|
||||||
|
pids_limit: 64
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/certbot-etc:/etc/letsencrypt
|
- ./nginx/certbot-etc:/etc/letsencrypt
|
||||||
- ./nginx/certbot-www:/var/www/certbot
|
- ./nginx/certbot-www:/var/www/certbot
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ services:
|
|||||||
traefik:
|
traefik:
|
||||||
image: traefik:v3.1
|
image: traefik:v3.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
# A ceiling on processes and threads (security audit 2026-10-17, I3).
|
||||||
|
pids_limit: 256
|
||||||
depends_on:
|
depends_on:
|
||||||
- selfpost
|
- selfpost
|
||||||
command:
|
command:
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ grep -q -- '-----BEGIN CERTIFICATE-----' "$tmp_cert" || die "the certificate of
|
|||||||
grep -q -- '-----BEGIN .*PRIVATE KEY-----' "$tmp_key" || die "the key of $DOMAIN is not PEM"
|
grep -q -- '-----BEGIN .*PRIVATE KEY-----' "$tmp_key" || die "the key of $DOMAIN is not PEM"
|
||||||
|
|
||||||
chmod 0640 "$tmp_cert" "$tmp_key"
|
chmod 0640 "$tmp_cert" "$tmp_key"
|
||||||
# The group the files had stays theirs: the guide gives the key to group 10998
|
# The group the files had stays theirs: the guide gives the key to group 10997
|
||||||
# so the panel can read it for a full backup, and a run from root's cron would
|
# so the panel can read it for a full backup, and a run from root's cron would
|
||||||
# otherwise hand it to root's group (review of audit stage 5).
|
# otherwise hand it to root's group (review of audit stage 5).
|
||||||
for f in fullchain privkey; do
|
for f in fullchain privkey; do
|
||||||
|
|||||||
+88
-52
@@ -63,7 +63,7 @@ The panel runs `postfix-reload` through supervisord's XML-RPC interface and
|
|||||||
waits for its exit status (`internal/supervisor`, `RunOnce`); the program
|
waits for its exit status (`internal/supervisor`, `RunOnce`); the program
|
||||||
rests EXITED after a run.
|
rests EXITED after a run.
|
||||||
|
|
||||||
**Liveness:** `GET /healthz` (unauthenticated) returns 200 when panel - whose
|
**Liveness:** `GET /healthz` (unauthenticated; answered only to a request with no `X-Forwarded-For` from loopback or a private address, else 404) returns 200 when panel - whose
|
||||||
roles include the DKIM signer - and postfix are RUNNING
|
roles include the DKIM signer - and postfix are RUNNING
|
||||||
(`internal/health`, `Liveness`); Docker `HEALTHCHECK` uses the same probe (its
|
(`internal/health`, `Liveness`); Docker `HEALTHCHECK` uses the same probe (its
|
||||||
90-second start period covers postfix's STARTING).
|
90-second start period covers postfix's STARTING).
|
||||||
@@ -103,7 +103,10 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
`smtpd_milters` is always overridden on this service, because main.cf's
|
`smtpd_milters` is always overridden on this service, because main.cf's
|
||||||
submission chain would otherwise let a signing outage defer relayed mail and
|
submission chain would otherwise let a signing outage defer relayed mail and
|
||||||
would file inbound mail into the outbound send log under the sender's
|
would file inbound mail into the outbound send log under the sender's
|
||||||
domain. With the inbound relay on the chain is the optional antispam milter,
|
domain. `ETRN` is refused on every listener and not offered in the EHLO
|
||||||
|
reply (`smtpd_etrn_restrictions=reject`, `smtpd_discard_ehlo_keywords=silent-discard,etrn`):
|
||||||
|
Postfix's default lets any client flush the queue toward an inbound
|
||||||
|
domain's upstream. With the inbound relay on the chain is the optional antispam milter,
|
||||||
then the panel's **inbound journal milter** (unix socket
|
then the panel's **inbound journal milter** (unix socket
|
||||||
`INBOUND_JOURNAL_MILTER_SOCKET`, default
|
`INBOUND_JOURNAL_MILTER_SOCKET`, default
|
||||||
`/run/selfpost/inbound-journal.sock`, `default_action=accept`); with DMARC
|
`/run/selfpost/inbound-journal.sock`, `default_action=accept`); with DMARC
|
||||||
@@ -127,7 +130,7 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
rspamd removes a sender's own copies of the three headers and writes and
|
rspamd removes a sender's own copies of the three headers and writes and
|
||||||
signs its own for every message it lets through: `skip_local` and
|
signs its own for every message it lets through: `skip_local` and
|
||||||
`skip_authenticated` are off in that file, so this holds whatever the
|
`skip_authenticated` are off in that file, so this holds whatever the
|
||||||
sender's address (rspamd 3.11.1, `src/plugins/lua/milter_headers.lua`,
|
sender's address (rspamd 4.2.2 (as 3.11.1), `src/plugins/lua/milter_headers.lua`,
|
||||||
`skip_wanted()`).
|
`skip_wanted()`).
|
||||||
With the filter down - it is attached fail-open - a sender's own verdict
|
With the filter down - it is attached fail-open - a sender's own verdict
|
||||||
headers carry no signature and the row is that of mail nobody judged.
|
headers carry no signature and the row is that of mail nobody judged.
|
||||||
@@ -135,9 +138,10 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
Refusals come from `mail.log`: this listener logs as `postfix/inbound`
|
Refusals come from `mail.log`: this listener logs as `postfix/inbound`
|
||||||
(`syslog_name`), and the log tailer turns a `reject:` line of
|
(`syslog_name`), and the log tailer turns a `reject:` line of
|
||||||
`postfix/inbound/smtpd` (Postfix's own policy) and a `milter-reject` /
|
`postfix/inbound/smtpd` (Postfix's own policy) and a `milter-reject` /
|
||||||
`milter-hold` / `milter-discard` line of `cleanup` (the filter's verdict,
|
`milter-discard` line of `cleanup` (the filter's verdict, counted only for
|
||||||
counted only for a queue id that listener opened) into a row, dated by the
|
a queue id that listener opened) into a row, dated by the line, when the
|
||||||
line, when the recipient is at an inbound domain
|
recipient is at an inbound domain; a message the filter holds goes on to
|
||||||
|
the inbound journal milter, which writes its row as `held`
|
||||||
([internal/logtail/inbound.go](../internal/logtail/inbound.go)). A line
|
([internal/logtail/inbound.go](../internal/logtail/inbound.go)). A line
|
||||||
names one recipient (the last), so a message refused at end-of-message is
|
names one recipient (the last), so a message refused at end-of-message is
|
||||||
one row whatever its recipient count. The tailer saves its place every five seconds,
|
one row whatever its recipient count. The tailer saves its place every five seconds,
|
||||||
@@ -145,7 +149,8 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
start are written only if the same row is not there already. The
|
start are written only if the same row is not there already. The
|
||||||
journal is pruned every six hours: rows
|
journal is pruned every six hours: rows
|
||||||
older than the `inbound_spam_log_retention_days` setting, then down to
|
older than the `inbound_spam_log_retention_days` setting, then down to
|
||||||
10 000.
|
10 000 - and down to 10 000 every 500 rows either writer adds in between
|
||||||
|
(the store counts them, `countInboundWrites`).
|
||||||
**Filter lists** (`inbound_spam_list`): allow and deny entries for the
|
**Filter lists** (`inbound_spam_list`): allow and deny entries for the
|
||||||
external filter - sender address, sender domain, client address or network -
|
external filter - sender address, sender domain, client address or network -
|
||||||
validated and normalised by `internal/inbound` (`lists.go`) and written as
|
validated and normalised by `internal/inbound` (`lists.go`) and written as
|
||||||
@@ -166,15 +171,21 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
allowed IP with a denied address were all `reject` (assumptions
|
allowed IP with a denied address were all `reject` (assumptions
|
||||||
D-arch:121). A sender entry is matched against
|
D-arch:121). A sender entry is matched against
|
||||||
the envelope sender only (`extract_from = "smtp"`; by default rspamd takes
|
the envelope sender only (`extract_from = "smtp"`; by default rspamd takes
|
||||||
a bounce's `From:` header instead - rspamd 3.11.1,
|
a bounce's `From:` header instead - rspamd 4.2.2 (as 3.11.1),
|
||||||
`src/plugins/lua/multimap.lua`, the `from` callback).
|
`src/plugins/lua/multimap.lua`, the `from` callback).
|
||||||
How often an entry decided a message is not stored: `Lists.Usage` reads it
|
How often an entry decided a message is not stored: `Lists.Usage` reads it
|
||||||
off the journal on each view of Filter lists - the rows a list decided (the
|
off the journal on each view of Filter lists - the rows a list decided (the
|
||||||
deny rule's reply, the allow rule's symbol, which the journal milter keeps
|
deny rule's reply, the allow rule's symbol, which the journal milter keeps
|
||||||
ahead of its six-symbol cap) matched against the entries as rspamd matches
|
ahead of its six-symbol cap) matched against the entries as rspamd matches
|
||||||
them. When that stops being good enough is written in
|
them. So a count reaches back as far as the journal does, a row counts for
|
||||||
[plans/inbound-antispam-panel.md](plans/inbound-antispam-panel.md) § Filter
|
every fitting entry of the deciding list, and a count is of messages,
|
||||||
list usage.
|
roughly. A counter kept with each entry (hits and last hit on
|
||||||
|
`inbound_spam_list`, a migration) is worth it when any one of these holds
|
||||||
|
(owner, 2026-10-12): the journal is cut by its 10 000-row cap rather than by
|
||||||
|
age (the tailer's prune keeps removing rows while the oldest row of
|
||||||
|
Inbound › Log is younger than the retention); the retention is set short,
|
||||||
|
towards its 7 days; counts are wanted that outlive the journal; or the page
|
||||||
|
is slow - past a few hundred entries, measure it.
|
||||||
**Inbound relay** (`INBOUND_RELAY_ENABLE=true`): accepts only
|
**Inbound relay** (`INBOUND_RELAY_ENABLE=true`): accepts only
|
||||||
`relay_domains` + `relay_recipient_maps` (`reject_unauth_destination`,
|
`relay_domains` + `relay_recipient_maps` (`reject_unauth_destination`,
|
||||||
`reject_unlisted_recipient`). Maps under `/data/postfix/` (`relay_domains`,
|
`reject_unlisted_recipient`). Maps under `/data/postfix/` (`relay_domains`,
|
||||||
@@ -183,8 +194,9 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
an empty upstream host are omitted from the maps. Optional
|
an empty upstream host are omitted from the maps. Optional
|
||||||
`INBOUND_ANTISPAM_MILTER` only when inbound relay is enabled (not on
|
`INBOUND_ANTISPAM_MILTER` only when inbound relay is enabled (not on
|
||||||
DMARC-only port 25); it is attached with its own `default_action`,
|
DMARC-only port 25); it is attached with its own `default_action`,
|
||||||
`INBOUND_ANTISPAM_MILTER_ACTION`, which is `accept` (fail-open) unless set
|
`INBOUND_ANTISPAM_MILTER_ACTION`, `accept` (fail-open, the default) or
|
||||||
to `tempfail` (main.cf's `milter_default_action` is `tempfail`).
|
`tempfail`, any other value stopping the start (main.cf's
|
||||||
|
`milter_default_action` is `tempfail`).
|
||||||
**DMARC ingest** (`DMARC_REPORTS_ENABLE=true`): allow-listed
|
**DMARC ingest** (`DMARC_REPORTS_ENABLE=true`): allow-listed
|
||||||
report addresses via `check_recipient_access` on `postfix/dmarc_recipients`;
|
report addresses via `check_recipient_access` on `postfix/dmarc_recipients`;
|
||||||
messages pipe to `dmarc-ingest` (symlink to `panel`). When both flags are
|
messages pipe to `dmarc-ingest` (symlink to `panel`). When both flags are
|
||||||
@@ -217,8 +229,8 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
and to smtpd's `client=` line in mail.log.
|
and to smtpd's `client=` line in mail.log.
|
||||||
- **Chroot disabled** for all services (DNS/TLS inside container).
|
- **Chroot disabled** for all services (DNS/TLS inside container).
|
||||||
- **TLS certs** — read-only mount at `TLS_CERT_FILE` / `TLS_KEY_FILE`, copied
|
- **TLS certs** — read-only mount at `TLS_CERT_FILE` / `TLS_KEY_FILE`, copied
|
||||||
at start into `/etc/postfix/tls-internal`, owned by root and `0600` as
|
at start into `/etc/postfix/tls-internal`, owned by root, the key `0600`
|
||||||
Postfix's TLS_README advises (Postfix itself reads the key as root and does
|
(the certificate `0644`) as Postfix's TLS_README advises (Postfix itself reads the key as root and does
|
||||||
not check its mode), which the mount cannot give; Postfix reads the copy.
|
not check its mode), which the mount cannot give; Postfix reads the copy.
|
||||||
`cert-reload` compares the mount with the copy daily; when they differ and
|
`cert-reload` compares the mount with the copy daily; when they differ and
|
||||||
the certificate and key belong together it renews the copy (each file
|
the certificate and key belong together it renews the copy (each file
|
||||||
@@ -229,15 +241,16 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
|||||||
The panel signs outgoing mail itself, in a role of its own, `dkim-signer`
|
The panel signs outgoing mail itself, in a role of its own, `dkim-signer`
|
||||||
([cmd/panel/signer.go](../cmd/panel/signer.go),
|
([cmd/panel/signer.go](../cmd/panel/signer.go),
|
||||||
[internal/milter/sign.go](../internal/milter/sign.go)), since `2.8.0`; until
|
[internal/milter/sign.go](../internal/milter/sign.go)), since `2.8.0`; until
|
||||||
then OpenDKIM did, in a program of its own
|
then OpenDKIM did, in a program of its own (CHANGELOG `[2.8.0]`).
|
||||||
([plans/panel-dkim-signing.md](plans/panel-dkim-signing.md)).
|
|
||||||
|
|
||||||
- **Keys** — `/data/dkim/keys/<name>/<selector>.private`, PKCS#1 PEM, one
|
- **Keys** — `/data/dkim/keys/<name>/<selector>.private`, PKCS#1 PEM, one
|
||||||
for each sending domain and one for the server's own name
|
for each sending domain and one for the server's own name
|
||||||
(`internal/domain`, `Keys`; `DKIM_DIR`, default `/data/dkim`). A key is
|
(`internal/domain`, `Keys`; `DKIM_DIR`, default `/data/dkim`). A key is
|
||||||
made when a domain is added and an existing one is reused, never
|
made when a domain is added and an existing one is reused, never
|
||||||
overwritten: that would invalidate the record already published
|
overwritten: that would invalidate the record already published
|
||||||
(`EnsureKey`). A domain export carries the key and an import writes it. A
|
(`EnsureKey`). A domain export carries the key and an import writes it,
|
||||||
|
over whatever key the name had (`ImportKey`: the import is the domain
|
||||||
|
with exactly that key). A
|
||||||
deleted domain's key directory is removed once the selector table no
|
deleted domain's key directory is removed once the selector table no
|
||||||
longer names it; one a deletion had to leave, Reload configuration removes
|
longer names it; one a deletion had to leave, Reload configuration removes
|
||||||
(`Service.Resync`).
|
(`Service.Resync`).
|
||||||
@@ -288,8 +301,10 @@ then OpenDKIM did, in a program of its own
|
|||||||
- **What is signed** — `relaxed/simple`, `rsa-sha256`, with
|
- **What is signed** — `relaxed/simple`, `rsa-sha256`, with
|
||||||
`github.com/emersion/go-msgauth` v0.7.0 (`dkim`). `h=`: every header of
|
`github.com/emersion/go-msgauth` v0.7.0 (`dkim`). `h=`: every header of
|
||||||
the message whose name is in RFC 6376 § 5.4.1's list as OpenDKIM's
|
the message whose name is in RFC 6376 § 5.4.1's list as OpenDKIM's
|
||||||
`dkim_should_signhdrs[]` held it, and `Sender` and `List-Unsubscribe-Post`
|
`dkim_should_signhdrs[]` held it, and `Sender`, `List-Unsubscribe-Post`,
|
||||||
besides (`signHeaders`), each occurrence, in message order; then, once
|
`Content-Type`, `MIME-Version` and `Message-ID` besides (`signHeaders`;
|
||||||
|
`Message-ID` is not over-signed: cleanup adds one a message lacks after the
|
||||||
|
milters), each occurrence, in message order; then, once
|
||||||
more, each of `Subject`, `Date`, `To`, `Cc`, `Reply-To` and `Sender` the
|
more, each of `Subject`, `Date`, `To`, `Cc`, `Reply-To` and `Sender` the
|
||||||
message has (`overSign` - only those present: Postfix's cleanup adds a
|
message has (`overSign` - only those present: Postfix's cleanup adds a
|
||||||
missing `Date:` after the milters, out of their sight, and a name in `h=`
|
missing `Date:` after the milters, out of their sight, and a name in `h=`
|
||||||
@@ -310,8 +325,8 @@ then OpenDKIM did, in a program of its own
|
|||||||
message has an hour from the end of its headers to its end - go-milter
|
message has an hour from the end of its headers to its end - go-milter
|
||||||
v0.4.1 tells a session nothing of a dropped connection - after which it is
|
v0.4.1 tells a session nothing of a dropped connection - after which it is
|
||||||
deferred, never signed over part of its body.
|
deferred, never signed over part of its body.
|
||||||
- **Logs** go to the panel's stdout, the container's log, as `dkim-signer:`
|
- **Logs** go to the panel's stderr, the container's log, as
|
||||||
lines; nothing is written to `mail.log` (nor did OpenDKIM write there).
|
`panel: … dkim-signer:` lines; nothing is written to `mail.log` (nor did OpenDKIM write there).
|
||||||
- **The old tree,** `/data/opendkim` - the keys, `KeyTable` and
|
- **The old tree,** `/data/opendkim` - the keys, `KeyTable` and
|
||||||
`SigningTable` until `2.8.0`, shared with OpenDKIM through group
|
`SigningTable` until `2.8.0`, shared with OpenDKIM through group
|
||||||
`selfpost`. `2.8.x` copied its keys to `/data/dkim/keys` at its first
|
`selfpost`. `2.8.x` copied its keys to `/data/dkim/keys` at its first
|
||||||
@@ -343,7 +358,7 @@ milter, § Postfix above, port 25):
|
|||||||
(`300s` / `4000s` / `5d` / `0`) with a muted note on the box.
|
(`300s` / `4000s` / `5d` / `0`) with a muted note on the box.
|
||||||
2. **sender check** — unix socket `SENDER_CHECK_SOCKET`, first in the chain
|
2. **sender check** — unix socket `SENDER_CHECK_SOCKET`, first in the chain
|
||||||
and **strict** (`default_action=tempfail`): § Sender check below.
|
and **strict** (`default_action=tempfail`): § Sender check below.
|
||||||
3. **DKIM signer** (`2.8.0`, [plans/panel-dkim-signing.md](plans/panel-dkim-signing.md))
|
3. **DKIM signer** (`2.8.0`, § DKIM signing)
|
||||||
— unix sockets `DKIM_SIGN_SOCKET` (applications' mail on 465/587, signed
|
— unix sockets `DKIM_SIGN_SOCKET` (applications' mail on 465/587, signed
|
||||||
with the key of the domain the SASL login belongs to) and
|
with the key of the domain the SASL login belongs to) and
|
||||||
`DKIM_SIGN_LOCAL_SOCKET` (the panel's own mail, `non_smtpd_milters`,
|
`DKIM_SIGN_LOCAL_SOCKET` (the panel's own mail, `non_smtpd_milters`,
|
||||||
@@ -387,8 +402,8 @@ generates pass no milter.
|
|||||||
### Sender check
|
### Sender check
|
||||||
|
|
||||||
[internal/milter/sender.go](../internal/milter/sender.go) holds a message's
|
[internal/milter/sender.go](../internal/milter/sender.go) holds a message's
|
||||||
`From:` header against the application that sends it
|
`From:` header against the application that sends it (since `2.4.0`;
|
||||||
([plans/from-header-binding.md](plans/from-header-binding.md)). Postfix holds
|
`Sender:` too since `2.8.0`). Postfix holds
|
||||||
the envelope sender against the SASL login, and the signer signs with the key
|
the envelope sender against the SASL login, and the signer signs with the key
|
||||||
of the login's domain (§ DKIM signing): the signature's `d=` is that domain,
|
of the login's domain (§ DKIM signing): the signature's `d=` is that domain,
|
||||||
and a receiver holds it to the domain of `From:` (DMARC, RFC 7489 § 3.1.1).
|
and a receiver holds it to the domain of `From:` (DMARC, RFC 7489 § 3.1.1).
|
||||||
@@ -494,10 +509,15 @@ the hole it closed was the same.
|
|||||||
[internal/notify](../internal/notify/notify.go) reports four things to the
|
[internal/notify](../internal/notify/notify.go) reports four things to the
|
||||||
users of the panel: a sending domain's DNS record that stopped matching, a
|
users of the panel: a sending domain's DNS record that stopped matching, a
|
||||||
level-2 rate limit that refused mail, the TLS certificate about to expire, a
|
level-2 rate limit that refused mail, the TLS certificate about to expire, a
|
||||||
message more than three days in the queue. The rules of when a message goes
|
message more than three days in the queue. The operator's view is guide
|
||||||
out are the owner's and are written in
|
§ Notifications. When a message goes out is the owner's rule (2026-10-12):
|
||||||
[plans/panel-notifications.md](plans/panel-notifications.md) § When a message
|
|
||||||
is sent; the operator's view is guide § Notifications.
|
| Subject | Sent | "Cleared" message |
|
||||||
|
|---|---|---|
|
||||||
|
| DNS mismatch | when it has held for an hour (two checks, below); a resolver that did not answer is not a mismatch | the same way back |
|
||||||
|
| Certificate | 14 days before expiry, and 3 days before if it is still the same certificate | none |
|
||||||
|
| Level-2 limit refused mail | at the first refusal for a sending domain, then at most once a day for it | none |
|
||||||
|
| Queue older than three days | when such a message appears | one, when none is left |
|
||||||
|
|
||||||
- **Readings.** Once an hour, starting three minutes after the process does:
|
- **Readings.** Once an hour, starting three minutes after the process does:
|
||||||
every sending domain's records through a DNS checker of the notifier's own,
|
every sending domain's records through a DNS checker of the notifier's own,
|
||||||
@@ -574,9 +594,12 @@ and `MAIL_LOG` in [cmd/panel/main.go](../cmd/panel/main.go). Backups exclude
|
|||||||
Rotation uses rename + `postfix reload`
|
Rotation uses rename + `postfix reload`
|
||||||
([build/logrotate-mail.conf](../build/logrotate-mail.conf)), not `copytruncate` —
|
([build/logrotate-mail.conf](../build/logrotate-mail.conf)), not `copytruncate` —
|
||||||
the latter can drop `status=sent` lines and leave send-log rows stuck at
|
the latter can drop `status=sent` lines and leave send-log rows stuck at
|
||||||
`queued`. After rename, logrotate runs `create 0640 postfix selfpost` (postlogd
|
`queued`. After rename, logrotate runs `create 0640 postfix selfpost`, so the
|
||||||
recreates the file on its first line after the reload, as root and mode
|
file is there, readable by the panel through group `selfpost`, before postlogd
|
||||||
`0600`, which the unprivileged panel user cannot read). `follow()` drains the
|
writes its first line after the reload; a file postlogd makes itself (one
|
||||||
|
removed while the container runs) is `0640` too, through
|
||||||
|
`maillog_file_permissions=0640` - Postfix's default is `0600`, which the
|
||||||
|
unprivileged panel user could not read. `follow()` drains the
|
||||||
old inode once more before switching descriptors, and keeps it open and reads
|
old inode once more before switching descriptors, and keeps it open and reads
|
||||||
it for a minute after: postlogd keeps writing to the renamed file until the
|
it for a minute after: postlogd keeps writing to the renamed file until the
|
||||||
reload makes it exit, seconds later (Postfix 3.10.13, src/postlogd/postlogd.c;
|
reload makes it exit, seconds later (Postfix 3.10.13, src/postlogd/postlogd.c;
|
||||||
@@ -625,8 +648,9 @@ is mail of its own queue id and matches no row.
|
|||||||
|
|
||||||
**Read offset is persisted** (`logtail_state` table, in the baseline schema): the
|
**Read offset is persisted** (`logtail_state` table, in the baseline schema): the
|
||||||
tailer stores its position plus a fingerprint of the log - the hash of its first
|
tailer stores its position plus a fingerprint of the log - the hash of its first
|
||||||
512 bytes and the file's device and inode, so two logs that begin alike are two
|
512 bytes and the file's inode (not the device, which can change across a
|
||||||
files and a renamed file keeps its fingerprint - and on start resumes from it,
|
host's reboot), so two logs that begin alike are two files and a renamed file
|
||||||
|
keeps its fingerprint - and on start resumes from it,
|
||||||
parsing the tail written while the panel was down. If the fingerprint no longer
|
parsing the tail written while the panel was down. If the fingerprint no longer
|
||||||
matches, the log was rotated in the meantime: the tailer first opens
|
matches, the log was rotated in the meantime: the tailer first opens
|
||||||
`mail.log.1` (left plain by `delaycompress`), and if that is the file the saved
|
`mail.log.1` (left plain by `delaycompress`), and if that is the file the saved
|
||||||
@@ -635,7 +659,7 @@ start - what Postfix wrote between the stop and the rotation is not lost. A
|
|||||||
`mail.log.1` that is another file (rotated twice) is logged with the time from
|
`mail.log.1` that is another file (rotated twice) is logged with the time from
|
||||||
which nothing was read. Re-parsing lines is harmless because `UpdateStatus`
|
which nothing was read. Re-parsing lines is harmless because `UpdateStatus`
|
||||||
writes the same status onto the same row. Only a first-ever start, with nothing
|
writes the same status onto the same row. Only a first-ever start, with nothing
|
||||||
stored, begins at end-of-file, so installing the panel does not replay a
|
stored (or a stored place that cannot be read), begins at end-of-file, so installing the panel does not replay a
|
||||||
pre-existing log.
|
pre-existing log.
|
||||||
|
|
||||||
**A refusal's line, and a refusal the panel did not write.** A message refused
|
**A refusal's line, and a refusal the panel did not write.** A message refused
|
||||||
@@ -815,7 +839,9 @@ building them only takes method values, so the route guard test can run it on
|
|||||||
a bare `Server` and ask which pattern answers a path:
|
a bare `Server` and ask which pattern answers a path:
|
||||||
|
|
||||||
- **public** — `/healthz` (liveness), `/license` (the embedded `LICENSE`),
|
- **public** — `/healthz` (liveness), `/license` (the embedded `LICENSE`),
|
||||||
`/static/`, `/setup/`, `/login`, `/logout`;
|
`/static/`, `/setup/`, `/login`, `/logout`, and the links of
|
||||||
|
password-reset, opened from a mail signed in or not: `/login/reset`,
|
||||||
|
`/login/reset/<token>`, `/confirm/<token>` (`internal/web/auth/links.go`);
|
||||||
- **authenticated** — everything else, behind `RequireAuth`: a request without
|
- **authenticated** — everything else, behind `RequireAuth`: a request without
|
||||||
a live session is redirected to `/login`.
|
a live session is redirected to `/login`.
|
||||||
|
|
||||||
@@ -837,9 +863,9 @@ only reads (the delete confirmation at `GET …/delete` renders a form).
|
|||||||
| Outbound › Log, Queue | `GET /outbound/log` · `/outbound/log/fragment` · `/outbound/log/{id}` · `GET /outbound/queue` · `/outbound/queue/fragment` |
|
| Outbound › Log, Queue | `GET /outbound/log` · `/outbound/log/fragment` · `/outbound/log/{id}` · `GET /outbound/queue` · `/outbound/queue/fragment` |
|
||||||
| Outbound › DMARC reports | `GET /outbound/dmarc` · `…/dmarc/domains/{id}` · `…/dmarc/reports/{id}` — registered only with `DMARC_REPORTS_ENABLE=true` |
|
| Outbound › DMARC reports | `GET /outbound/dmarc` · `…/dmarc/domains/{id}` · `…/dmarc/reports/{id}` — registered only with `DMARC_REPORTS_ENABLE=true` |
|
||||||
| Inbound › Domains | `GET, POST /inbound/domains` · `/inbound/domains/{id}` · `POST …/dns-recheck`, `…/upstream`, `…/recipients` · `GET, POST …/delete` — registered only with `INBOUND_RELAY_ENABLE=true` |
|
| Inbound › Domains | `GET, POST /inbound/domains` · `/inbound/domains/{id}` · `POST …/dns-recheck`, `…/upstream`, `…/recipients` · `GET, POST …/delete` — registered only with `INBOUND_RELAY_ENABLE=true` |
|
||||||
| Inbound › Log, Filter lists | `GET /inbound/log` · `GET, POST /inbound/filter-lists` · `POST /inbound/filter-lists/{id}/delete` — registered only with `INBOUND_RELAY_ENABLE=true` |
|
| Inbound › Log, Queue, Filter lists | `GET /inbound/log` · `GET /inbound/queue` · `/inbound/queue/fragment` · `GET, POST /inbound/filter-lists` · `POST /inbound/filter-lists/{id}/delete` — registered only with `INBOUND_RELAY_ENABLE=true` |
|
||||||
| Server | `GET /server/health` (+ `/fragment`) · `POST /server/health/recheck`, `/server/health/reload` · `GET /server/log` (+ `/fragment`) · `GET, POST /server/backup` · `POST /server/backup/import` · `/server/users`, `…/new`, `…/{uid}`, `…/{uid}/delete` · `GET, POST /server/settings` · `POST /server/settings/dns-recheck` · `GET /server/components` |
|
| Server | `GET /server/health` (+ `/fragment`) · `POST /server/health/recheck`, `/server/health/reload`, `/server/health/test-message` · `GET /server/log` (+ `/fragment`) · `GET, POST /server/backup` · `POST /server/backup/import` · `/server/users`, `…/new`, `…/{uid}`, `…/{uid}/delete` · `GET, POST /server/settings` · `POST /server/settings/dns-recheck` · `GET /server/components` |
|
||||||
| Account | `GET /account` · `POST /account/profile`, `/account/password` |
|
| Account | `GET /account` · `POST /account/profile`, `/account/password`, `/account/email/confirm` |
|
||||||
| User menu | `GET /help` · `POST /logout` (public mux) |
|
| User menu | `GET /help` · `POST /logout` (public mux) |
|
||||||
|
|
||||||
An application is addressed under its domain, and an application id that does
|
An application is addressed under its domain, and an application id that does
|
||||||
@@ -940,9 +966,8 @@ is one layout and one way to render.
|
|||||||
package (`NewOutDomain`, `NewHelp`, …) that the handler fills. The shell is
|
package (`NewOutDomain`, `NewHelp`, …) that the handler fills. The shell is
|
||||||
derived from `Meta` and the feature flags (`SetInboundEnabled`,
|
derived from `Meta` and the feature flags (`SetInboundEnabled`,
|
||||||
`SetDMARCEnabled`) in one place, `Engine.shell`, so which entries the menu
|
`SetDMARCEnabled`) in one place, `Engine.shell`, so which entries the menu
|
||||||
has is decided once. No template reads a `map[string]any`; the one map left
|
has is decided once. No page's data is a map: the health sampler too
|
||||||
in the handlers is the health sampler's, which is turned into a typed
|
returns a typed `health.Machine`.
|
||||||
`Health` before rendering.
|
|
||||||
- **The component kit.** [components.html](../internal/web/view/templates/components.html)
|
- **The component kit.** [components.html](../internal/web/view/templates/components.html)
|
||||||
holds one `{{define}}` per partial (page head, postmark, box and its head and
|
holds one `{{define}}` per partial (page head, postmark, box and its head and
|
||||||
foot, flash, health card, DNS record, facts, side menu, timeline, log pane,
|
foot, flash, health card, DNS record, facts, side menu, timeline, log pane,
|
||||||
@@ -982,8 +1007,8 @@ has the component skeleton of its mockup), `TestRedesignedPagesLoadOnlyTheKit`,
|
|||||||
ratchet list `internal/web/view/legacy_pages.txt` is empty and can only shrink;
|
ratchet list `internal/web/view/legacy_pages.txt` is empty and can only shrink;
|
||||||
the CI step
|
the CI step
|
||||||
[design-first](../.github/scripts/design-first.sh) rejects a commit that
|
[design-first](../.github/scripts/design-first.sh) rejects a commit that
|
||||||
changes a guard together with the code it judges, and a template that is a
|
changes a guard together with the code it judges, and a template that is
|
||||||
byte-for-byte return of one from history. The design contract and the process
|
the same as one from history, whitespace aside. The design contract and the process
|
||||||
are in [plans/panel-redesign.md](plans/panel-redesign.md) § Enforcement.
|
are in [plans/panel-redesign.md](plans/panel-redesign.md) § Enforcement.
|
||||||
|
|
||||||
### Security headers, CSP and scripts
|
### Security headers, CSP and scripts
|
||||||
@@ -1018,7 +1043,8 @@ A validation refusal is shown as the text the validator wrote (on the form,
|
|||||||
next to the field, or in a notice above the first box). Anything else — a
|
next to the field, or in a notice above the first box). Anything else — a
|
||||||
database error, `saslpasswd2`, a Postfix reload — is logged
|
database error, `saslpasswd2`, a Postfix reload — is logged
|
||||||
and the page gets a fixed sentence saying so; the error text never reaches the
|
and the page gets a fixed sentence saying so; the error text never reaches the
|
||||||
browser. A template error renders a bare `500` rather than a half-written page
|
browser - save on the global-only Backup page, where an import's refusal
|
||||||
|
names what is wrong with the file (security.md § Output and process). A template error renders a bare `500` rather than a half-written page
|
||||||
(`Engine.Render` buffers).
|
(`Engine.Render` buffers).
|
||||||
|
|
||||||
### Sessions
|
### Sessions
|
||||||
@@ -1151,8 +1177,9 @@ single-connection trade-off that follows from it.
|
|||||||
| `postfix/dmarc_recipients` | Allow-listed DMARC aggregate report addresses |
|
| `postfix/dmarc_recipients` | Allow-listed DMARC aggregate report addresses |
|
||||||
| `postfix/dmarc_transport` | Pipe transport for report ingest |
|
| `postfix/dmarc_transport` | Pipe transport for report ingest |
|
||||||
| `postfix/dmarc_relay_domains` | Domains accepted for DMARC report delivery |
|
| `postfix/dmarc_relay_domains` | Domains accepted for DMARC report delivery |
|
||||||
| `postfix/queue/` | Postfix transit mail (deferred/active); survives container recreate. Handed to `postfix` / `postdrop` by name at every start, before `postfix check` (`own_queue` in `build/entrypoint.sh`): the package accounts' numbers moved in `2.8.0` (postfix 101 → 100, postdrop 103 → 102) |
|
| `postfix/queue/` | Postfix transit mail (deferred/active); survives container recreate; excluded from backups. Handed to `postfix` / `postdrop` by name at every start, before `postfix check` (`own_queue` in `build/entrypoint.sh`): the package accounts' numbers moved in `2.8.0` (postfix 101 → 100, postdrop 103 → 102) |
|
||||||
| `log/mail.log` | Postfix delivery log + rotated copies (excluded from backups) |
|
| `log/mail.log` | Postfix delivery log + rotated copies (excluded from backups) |
|
||||||
|
| `antispam/` | The filter lists (six `.map` files) and the filter's key `filter.key`, which the spam filter's container mounts (`ANTISPAM_DIR`) |
|
||||||
| `manifest.json` | Backup version stamp; removed by the first start after a restore once its Resync is done |
|
| `manifest.json` | Backup version stamp; removed by the first start after a restore once its Resync is done |
|
||||||
| `contents.json` | A backup's last entry: the size and SHA-256 of each file under `data/`, compared by the first start after a restore before it opens the database (renamed `contents.checked.json` once compared, removed with the manifest) |
|
| `contents.json` | A backup's last entry: the size and SHA-256 of each file under `data/`, compared by the first start after a restore before it opens the database (renamed `contents.checked.json` once compared, removed with the manifest) |
|
||||||
|
|
||||||
@@ -1162,7 +1189,7 @@ full backups also archive the operator's `./certs` PEM files when present.
|
|||||||
### Database
|
### Database
|
||||||
|
|
||||||
The schema is an embedded baseline, `internal/store/migrations/0001_init.sql`,
|
The schema is an embedded baseline, `internal/store/migrations/0001_init.sql`,
|
||||||
and the migrations after it (`0002` to `0012` today; the next is `0013`),
|
and the migrations after it (`0002` to `0014` today; the next is `0015`),
|
||||||
applied by `store.Open` in file order; `PRAGMA user_version` counts the files
|
applied by `store.Open` in file order; `PRAGMA user_version` counts the files
|
||||||
applied and
|
applied and
|
||||||
`PRAGMA application_id` is `0x53503230` ("SP20"). There is no 1.x migration
|
`PRAGMA application_id` is `0x53503230` ("SP20"). There is no 1.x migration
|
||||||
@@ -1181,7 +1208,11 @@ The tables that carry the panel's model:
|
|||||||
the user's own address, edited on Account and set by the global role on the
|
the user's own address, edited on Account and set by the global role on the
|
||||||
user form; `all_domains` / `all_inbound_domains` widen a *domain* user to
|
user form; `all_domains` / `all_inbound_domains` widen a *domain* user to
|
||||||
every domain of that direction, including ones added later (with a flag set
|
every domain of that direction, including ones added later (with a flag set
|
||||||
the assignment rows are ignored).
|
the assignment rows are ignored); `email_confirmed_at` is when `email` was
|
||||||
|
confirmed, `''` while it is not (v13).
|
||||||
|
- **`user_links`** — the one-time links that confirm an address and reset a
|
||||||
|
password (v13): the token's SHA-256, `purpose`, the address it went to, its
|
||||||
|
expiry; one live link per user and purpose.
|
||||||
- **`user_domains`** and **`user_inbound_domains`** — the outbound and inbound
|
- **`user_domains`** and **`user_inbound_domains`** — the outbound and inbound
|
||||||
domains assigned to a *domain* user. Separate tables: the same name on both
|
domains assigned to a *domain* user. Separate tables: the same name on both
|
||||||
is two assignments, and a sending domain gives no inbound access or the other
|
is two assignments, and a sending domain gives no inbound access or the other
|
||||||
@@ -1221,8 +1252,9 @@ the stored log lines follow;
|
|||||||
`postfix reload` in `postrotate` — see § Log tailer above).
|
`postfix reload` in `postrotate` — see § Log tailer above).
|
||||||
|
|
||||||
**Restore:** panel button or `selfpost-backup` CLI — self-contained archive:
|
**Restore:** panel button or `selfpost-backup` CLI — self-contained archive:
|
||||||
`data/` (SQLite snapshot + tree minus `log/`, the setup token and any
|
`data/` (SQLite snapshot + tree minus `log/`, the Postfix queue
|
||||||
`tls/` under `/data`), `docker-compose.yml`, `.env`,
|
`postfix/queue`, the copy `sasl/sasldb2.new` a password change is made on, the
|
||||||
|
setup token and any `tls/` under `/data`), `docker-compose.yml`, `.env`,
|
||||||
and `certs/` when present;
|
and `certs/` when present;
|
||||||
version check on restore. Requires the project directory mounted read-only at
|
version check on restore. Requires the project directory mounted read-only at
|
||||||
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` in the default compose file). On the
|
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` in the default compose file). On the
|
||||||
@@ -1299,11 +1331,15 @@ unsupported rather than as a missing doc:
|
|||||||
`POSTFIX_DMARC_RECIPIENTS` (`/data/postfix/dmarc_recipients`),
|
`POSTFIX_DMARC_RECIPIENTS` (`/data/postfix/dmarc_recipients`),
|
||||||
`POSTFIX_DMARC_TRANSPORT` (`/data/postfix/dmarc_transport`),
|
`POSTFIX_DMARC_TRANSPORT` (`/data/postfix/dmarc_transport`),
|
||||||
`POSTFIX_DMARC_RELAY_DOMAINS` (`/data/postfix/dmarc_relay_domains`),
|
`POSTFIX_DMARC_RELAY_DOMAINS` (`/data/postfix/dmarc_relay_domains`),
|
||||||
`POSTFIX_QUEUE_DIR` (`/data/postfix/queue` — set in `build/postfix-config.sh`),
|
`POSTFIX_QUEUE_DIR` (`/data/postfix/queue` — read in `build/postfix-config.sh`
|
||||||
|
for `queue_directory`; `build/entrypoint.sh` hands `/data/postfix/queue` to
|
||||||
|
Postfix by that path, so overriding it desyncs the queue's ownership),
|
||||||
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` — operator project directory for
|
`SELFPOST_DEPLOY_ROOT` (`/selfpost-deploy` — operator project directory for
|
||||||
full backups; mount `.:/selfpost-deploy:ro` in compose), `ANTISPAM_DIR`
|
full backups; mount `.:/selfpost-deploy:ro` in compose), `ANTISPAM_DIR`
|
||||||
(`/data/antispam` — the filter lists and the filter's key, which the spam
|
(`/data/antispam` — the filter lists and the filter's key, which the spam
|
||||||
filter's container mounts).
|
filter's container mounts). `SELFPOST_BACKUP_PASSWORD` is read by the
|
||||||
|
`selfpost-backup` CLI, not the panel: the encryption password, the other
|
||||||
|
way being `-password-file` (guide § Encrypting a backup).
|
||||||
- **Milter and Postfix startup:** `MILTER_CONNECT_TIMEOUT` (`15s`),
|
- **Milter and Postfix startup:** `MILTER_CONNECT_TIMEOUT` (`15s`),
|
||||||
`MILTER_COMMAND_TIMEOUT` (`15s`), `MILTER_CONTENT_TIMEOUT` (`30s`),
|
`MILTER_COMMAND_TIMEOUT` (`15s`), `MILTER_CONTENT_TIMEOUT` (`30s`),
|
||||||
`MILTER_WAIT_TIMEOUT` (`30` seconds).
|
`MILTER_WAIT_TIMEOUT` (`30` seconds).
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<a href="panel/in-queue.html">Queue</a> (empty state)<br>
|
<a href="panel/in-queue.html">Queue</a> (empty state)<br>
|
||||||
<a href="panel/in-quarantine.html">Quarantine</a> · <a href="panel/in-quarantine-message.html">held message</a> ·
|
<a href="panel/in-quarantine.html">Quarantine</a> · <a href="panel/in-quarantine-message.html">held message</a> ·
|
||||||
<a href="panel/in-filter-lists.html">Filter lists</a> — roadmap items, drawn as their target look</td></tr>
|
<a href="panel/in-filter-lists.html">Filter lists</a> — roadmap items, drawn as their target look</td></tr>
|
||||||
<tr><th>Server</th><td><a href="panel/health.html">Health</a> · <a href="panel/preflight.html">Preflight</a> (roadmap) · <a href="panel/system-log.html">System log</a> ·
|
<tr><th>Server</th><td><a href="panel/health.html">Health</a> · <a href="panel/system-log.html">System log</a> ·
|
||||||
<a href="panel/backup.html">Backup</a> · <a href="panel/users.html">Users</a> · <a href="panel/user.html">user form</a> ·
|
<a href="panel/backup.html">Backup</a> · <a href="panel/users.html">Users</a> · <a href="panel/user.html">user form</a> ·
|
||||||
<a href="panel/user-delete.html">delete user</a> · <a href="panel/settings.html">Settings</a></td></tr>
|
<a href="panel/user-delete.html">delete user</a> · <a href="panel/settings.html">Settings</a></td></tr>
|
||||||
<tr><th>User menu</th><td><a href="panel/account.html">Account</a> · <a href="panel/help.html">Help</a></td></tr>
|
<tr><th>User menu</th><td><a href="panel/account.html">Account</a> · <a href="panel/help.html">Help</a></td></tr>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group" open><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group" open><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -80,12 +79,18 @@
|
|||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<form class="sp-form" method="post" action="#">
|
<form class="sp-form" method="post" action="#">
|
||||||
<div class="field"><label class="label" for="username">Username</label><div class="control has-icons-left"><input class="input" id="username" name="username" value="admin" autocomplete="username" spellcheck="false" required><span class="icon is-left"><i class="ti ti-user"></i></span></div></div>
|
<div class="field"><label class="label" for="username">Username</label><div class="control has-icons-left"><input class="input" id="username" name="username" value="admin" autocomplete="username" spellcheck="false" required><span class="icon is-left"><i class="ti ti-user"></i></span></div></div>
|
||||||
<div class="field"><label class="label" for="email">E-mail</label><div class="control has-icons-left"><input class="input" id="email" name="email" type="email" value="mix@example.org" autocomplete="email"><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
<div class="field"><label class="label" for="email">E-mail <span class="tag is-warning is-light">not confirmed</span></label><div class="control has-icons-left"><input class="input" id="email" name="email" type="email" value="mix@example.org" autocomplete="email"><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
||||||
<p class="help">Your own address. A domain's report address can be filled with it from Domain settings.</p></div>
|
<p class="help">Your own address. Saving a new one sends it a link to confirm it; once confirmed it is where a link to reset your password can go. A domain's report address can be filled with it from Domain settings.</p></div>
|
||||||
|
<div class="field"><label class="label" for="pcur">Current password</label><div class="control"><input class="input" id="pcur" name="current_password" type="password" autocomplete="current-password"></div>
|
||||||
|
<p class="help">Needed only to change the e-mail.</p></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="notify" checked> Send me notifications</label>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="notify" checked> Send me notifications</label>
|
||||||
<p class="help">To this address, when something needs you: a DNS record of a domain stops matching, a rate limit refuses mail, the TLS certificate is about to expire, a message has waited in the queue for three days. <a href="help.html#notifications">What is sent and when</a></p></div>
|
<p class="help">To this address, when something needs you: a DNS record of a domain stops matching, a rate limit refuses mail, the TLS certificate is about to expire, a message has waited in the queue for three days. <a href="help.html#notifications">What is sent and when</a></p></div>
|
||||||
<button type="submit" class="button is-primary">Save profile</button>
|
<button type="submit" class="button is-primary">Save profile</button>
|
||||||
</form>
|
</form>
|
||||||
|
<form class="sp-form mt-4" method="post" action="#">
|
||||||
|
<p class="help mb-3">A link was sent to <span class="sp-mono">mix@example.org</span> at 14:02; it works once, for 24 hours.</p>
|
||||||
|
<button type="submit" class="button">Send the link again</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +113,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html" aria-current="page"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html" aria-current="page"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item is-active" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item is-active" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html" aria-current="page"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html" aria-current="page"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
@@ -111,7 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ NAV = [
|
|||||||
]),
|
]),
|
||||||
("server", "Server", "health.html", "ti-server-2", [
|
("server", "Server", "health.html", "ti-server-2", [
|
||||||
("health", "Health", "health.html", "ti-heartbeat"),
|
("health", "Health", "health.html", "ti-heartbeat"),
|
||||||
("preflight", "Preflight", "preflight.html", "ti-checklist"),
|
|
||||||
("system-log", "System log", "system-log.html", "ti-file-text"),
|
("system-log", "System log", "system-log.html", "ti-file-text"),
|
||||||
("backup", "Backup", "backup.html", "ti-archive"),
|
("backup", "Backup", "backup.html", "ti-archive"),
|
||||||
("users", "Users", "users.html", "ti-users"),
|
("users", "Users", "users.html", "ti-users"),
|
||||||
@@ -52,7 +51,7 @@ HEAD = """<!DOCTYPE html>
|
|||||||
</head>
|
</head>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FOOT = ('<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a>'
|
FOOT = ('<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a>'
|
||||||
' · <a href="#">Source</a> · No warranty</footer>')
|
' · <a href="#">Source</a> · No warranty</footer>')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -292,7 +291,7 @@
|
|||||||
<div class="sp-box-foot">Bulma's <code>is-small</code> and <code>is-size-7</code> are not used: a control is never small, and <code>sp-small</code> replaces the helper. The postmark's fine print is an imprint, not text.</div>
|
<div class="sp-box-foot">Bulma's <code>is-small</code> and <code>is-size-7</code> are not used: a control is never small, and <code>sp-small</code> replaces the helper. The postmark's fine print is an imprint, not text.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Confirm e-mail · SelfPost</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="../shared/brand.css">
|
||||||
|
<link rel="stylesheet" href="theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<div class="sp-form">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Confirm e-mail</h1>
|
||||||
|
<div class="notification is-danger is-light"><i class="ti ti-alert-circle"></i>Sign in to the account in this browser, then open the link again.</div>
|
||||||
|
<p><a href="login.html">Sign in</a></p>
|
||||||
|
</div>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Confirm e-mail · SelfPost</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="../shared/brand.css">
|
||||||
|
<link rel="stylesheet" href="theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="account.html">
|
||||||
|
<p class="sp-kicker">mail.example.org · admin</p>
|
||||||
|
<h1 class="title is-3">Confirm e-mail</h1>
|
||||||
|
<p class="sp-muted mb-4"><span class="sp-mono">mix@example.org</span> becomes the confirmed address of this account: where a link to reset its password can be sent.</p>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Confirm</button></div>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -103,7 +102,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="dmarc.html">DMARC reports</a> / <a href="dmarc-domain.html">shop.example.org</a> /</p>
|
<p class="sp-kicker"><a href="dmarc.html">DMARC reports</a> / <a href="dmarc-domain.html">shop.example.org</a> /</p>
|
||||||
<h1 class="title is-4">Outlook.com · 20 Sep 2026</h1>
|
<h1 class="title is-4">Outlook.com · 20 Sep</h1>
|
||||||
<p class="sp-route"><span>shop.example.org</span><span class="tag is-success is-light">1 903 pass</span><span class="tag is-warning is-light">7 fail</span></p>
|
<p class="sp-route"><span>shop.example.org</span><span class="tag is-success is-light">1 903 pass</span><span class="tag is-warning is-light">7 fail</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +80,7 @@
|
|||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Reporter</dt><dd>Outlook.com</dd></div>
|
<div><dt>Reporter</dt><dd>Outlook.com</dd></div>
|
||||||
<div><dt>Report id</dt><dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd></div>
|
<div><dt>Report id</dt><dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd></div>
|
||||||
<div><dt>Window</dt><dd>20 Sep 00:00 – 21 Sep 00:00</dd></div>
|
<div><dt>Window</dt><dd>20 Sep 03:00 – 21 Sep 03:00</dd><dd class="sp-muted">The reporter's day, 20 Sep: reports count their days in UTC, and the title and the lists name that day. The times are this panel's zone.</dd></div>
|
||||||
<div><dt>Received</dt><dd>2026-09-21 04:40</dd></div>
|
<div><dt>Received</dt><dd>2026-09-21 04:40</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +111,7 @@
|
|||||||
</table></div>
|
</table></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html" aria-current="page"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -98,7 +97,7 @@
|
|||||||
</table></div>
|
</table></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html" aria-current="page"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html" aria-current="page"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item is-active" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item is-active" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,14 +61,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html" aria-current="page"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html" aria-current="page"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Server</p>
|
<p class="sp-kicker">Server</p>
|
||||||
<h1 class="title is-3">Health</h1>
|
<h1 class="title is-3">Health</h1>
|
||||||
<p class="sp-lead">Everything behind the six cards on Overview. Refreshes every 10 s.</p>
|
<p class="sp-lead">Everything behind the six cards on Overview. Refreshes automatically.</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,6 +133,7 @@
|
|||||||
<tr><td>dkim-signer <span class="sp-muted">· notifications</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sign-local.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>dkim-signer <span class="sp-muted">· notifications</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sign-local.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>journal-milter<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>journal-milter<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>sender-check<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sender.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>sender-check<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sender.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
|
<tr><td>inbound-journal-milter <span class="sp-muted">· Inbound log</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/inbound-journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>rspamd <span class="sp-muted">· inbound spam filter</span><br><span class="sp-muted sp-mono sp-small">inet:antispam:11332</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>rspamd <span class="sp-muted">· inbound spam filter</span><br><span class="sp-muted sp-mono sp-small">inet:antispam:11332</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
@@ -157,6 +157,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>DKIM<br><span class="sp-muted sp-mono sp-small">sp2026._domainkey.mail.example.org</span></td><td><span class="tag is-success is-light">published</span></td></tr>
|
<tr><td>DKIM<br><span class="sp-muted sp-mono sp-small">sp2026._domainkey.mail.example.org</span></td><td><span class="tag is-success is-light">published</span></td></tr>
|
||||||
<tr><td>SPF<br><span class="sp-muted sp-mono sp-small">mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
<tr><td>SPF<br><span class="sp-muted sp-mono sp-small">mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
||||||
|
<tr><td>DMARC<br><span class="sp-muted sp-mono sp-small">_dmarc.mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
@@ -189,7 +190,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group" open><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group" open><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -100,6 +99,7 @@
|
|||||||
<li><a href="#recipients"><i class="ti ti-address-book"></i>Valid recipients</a></li>
|
<li><a href="#recipients"><i class="ti ti-address-book"></i>Valid recipients</a></li>
|
||||||
<li><a href="#filter"><i class="ti ti-shield-check"></i>Spam filter</a></li>
|
<li><a href="#filter"><i class="ti ti-shield-check"></i>Spam filter</a></li>
|
||||||
<li><a href="#inbound-log"><i class="ti ti-list-details"></i>Inbound log</a></li>
|
<li><a href="#inbound-log"><i class="ti ti-list-details"></i>Inbound log</a></li>
|
||||||
|
<li><a href="#inbound-queue"><i class="ti ti-stack-2"></i>Inbound queue</a></li>
|
||||||
<li><a href="#filter-lists"><i class="ti ti-filter"></i>Filter lists</a></li>
|
<li><a href="#filter-lists"><i class="ti ti-filter"></i>Filter lists</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="menu-label">Server</p>
|
<p class="menu-label">Server</p>
|
||||||
@@ -126,37 +126,38 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>Outbound</h2></div>
|
<div class="sp-box-head"><span class="sp-no">02</span><h2>Outbound</h2></div>
|
||||||
<div class="sp-help-topic" id="dns"><h3>DNS records</h3><p>DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow <code>include:</code> or <code>redirect=</code>, so a record that relies on them is reported as a warning, not a failure. <code>p=none</code> does not affect delivery; tighten to <code>quarantine</code>, then <code>reject</code>, once the reports look clean. A domain's report address is set in its Domain settings.</p></div>
|
<div class="sp-help-topic" id="dns"><h3>DNS records</h3><p>DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow <code>include:</code> or <code>redirect=</code>, so a record that relies on them is reported as a warning, not a failure. <code>p=none</code> does not affect delivery; tighten to <code>quarantine</code>, then <code>reject</code>, once the reports look clean. A domain's report address is set in its Domain settings.</p></div>
|
||||||
<div class="sp-help-topic" id="apps"><h3>Applications</h3><p>An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once at creation or regeneration and is not stored in readable form.</p></div>
|
<div class="sp-help-topic" id="apps"><h3>Applications</h3><p>An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once, at creation or regeneration, and the panel does not show it again; the server keeps it, so a domain export can carry it.</p></div>
|
||||||
<div class="sp-help-topic" id="sender-check"><h3>Sender check</h3><p>An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The <code>From:</code> line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other <code>From:</code> - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with <code>550 5.7.1</code>. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A <code>Sender:</code> line, which a mail program shows beside <code>From:</code> as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in <code>From:</code> with the person's name, and the person's address in <code>Reply-To:</code>: their domain in <code>From:</code> would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.<br><br>The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.</p></div>
|
<div class="sp-help-topic" id="sender-check"><h3>Sender check</h3><p>An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The <code>From:</code> line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other <code>From:</code> - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with <code>550 5.7.1</code>. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A <code>Sender:</code> line, which a mail program shows beside <code>From:</code> as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in <code>From:</code> with the person's name, and the person's address in <code>Reply-To:</code>: their domain in <code>From:</code> would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.<br><br>The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.</p></div>
|
||||||
<div class="sp-help-topic" id="limits"><h3>Rate limits</h3><p>Level 1 is a per-IP ceiling set in <code>.env</code>. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.</p></div>
|
<div class="sp-help-topic" id="limits"><h3>Rate limits</h3><p>Level 1 is a per-IP ceiling set in <code>.env</code>. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.</p></div>
|
||||||
<div class="sp-help-topic" id="log"><h3>Outbound log</h3><p>One row per message and recipient, with the status Postfix last reported: queued (accepted, nothing reported yet), sent (the receiving server took it; what the mailbox does next is not reported back), deferred (not taken yet, Postfix tries again), bounced (failed for good, or the queue lifetime ran out; or closed by the panel when Postfix no longer held the message and <code>mail.log</code> never said what became of it) and rejected (refused before queueing, so it never reached the queue: under a level-2 rate limit, from a client address the application does not allow, or for a <code>From:</code> or <code>Sender:</code> line that is not the application's - the message's page says which). A message's page lists these steps and the lines Postfix wrote about it in <code>mail.log</code>, found by queue id - and for a message refused before it had one, Postfix's one line about the refusal. The lines are kept with the row: they stay on the page after that file has rotated, for as long as the row does. A message accepted before version 2.2.0 shows them only while they are still in the current file. Rows are deleted after the retention set in Server › Settings, 90 days by default, and their lines with them. A domain administrator sees only the rows of their own domains.</p></div>
|
<div class="sp-help-topic" id="log"><h3>Outbound log</h3><p>One row per message and recipient (a message refused for its From: or Sender: line is one row, however many recipients it had), with the status Postfix last reported: queued (accepted, nothing reported yet), sent (the receiving server took it; what the mailbox does next is not reported back), deferred (not taken yet, Postfix tries again), bounced (failed for good, or the queue lifetime ran out; or closed by the panel when Postfix no longer held the message and <code>mail.log</code> never said what became of it) and rejected (refused before queueing, so it never reached the queue: under a level-2 rate limit, from a client address the application does not allow, or for a <code>From:</code> or <code>Sender:</code> line that is not the application's - the message's page says which). A message's page lists these steps and the lines Postfix wrote about it in <code>mail.log</code>, found by queue id - and for a message refused before it had one, Postfix's one line about the refusal. The lines are kept with the row: they stay on the page after that file has rotated, for as long as the row does. A message accepted before version 2.2.0 shows them only while they are still in the current file. Rows are deleted after the retention set in Server › Settings, 90 days by default, and their lines with them. A domain administrator sees only the rows of their own domains.</p></div>
|
||||||
<div class="sp-help-topic" id="queue"><h3>Queue and retries</h3><p>The page lists, as <code>postqueue -p</code> prints it, the messages Postfix has accepted and not yet delivered: mail being sent right now and mail deferred for a later attempt. Only the global role can open it, and it only shows; nothing here deletes or forces a message. A deferred message is tried again after the first retry delay, then at gaps that double up to a cap, until the receiving server takes it or the queue lifetime runs out and it bounces; there is no attempt counter, and the box on the page shows this server's values. A queue that keeps growing means a receiver is refusing or not answering: open the message from the Outbound log to read the reason Postfix logged.</p></div>
|
<div class="sp-help-topic" id="queue"><h3>Queue and retries</h3><p>The page lists the messages Postfix has accepted and not yet delivered, one row per recipient: mail being sent right now and mail deferred for a later attempt, with the last reply of the receiving server, or why no connection was made. Mail for an inbound domain waits under Inbound › Queue instead. Only the global role can open it, and it only shows; nothing here deletes or forces a message, and Details opens the message in the Outbound log. A deferred message is tried again after the first retry delay, then at gaps that double up to a cap, until the receiving server takes it or the queue lifetime runs out and it bounces; there is no attempt counter, and the box on the page shows this server's values. A queue that keeps growing means a receiver is refusing or not answering; the last reply says which.</p></div>
|
||||||
<div class="sp-help-topic" id="dmarc"><h3>DMARC reports</h3><p>An aggregate report is the summary a receiving provider sends, usually daily, to the <code>rua=</code> address of your DMARC record: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as <em>this relay</em>. A fail from any other source is someone else sending as your domain — a forwarder, an application you forgot, or abuse — so do not tighten <code>p=</code> until that sender is fixed or removed. A domain administrator reads the reports of their own domains.</p></div>
|
<div class="sp-help-topic" id="dmarc"><h3>DMARC reports</h3><p>An aggregate report is the summary a receiving provider sends, usually daily, to the <code>rua=</code> address of your DMARC record - its day is the reporter's, counted in UTC, and that is the day the lists and a report's title name, while the report's page gives its window in this panel's zone: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as <em>this relay</em>. A fail from any other source is someone else sending as your domain — a forwarder, an application you forgot, or abuse — so do not tighten <code>p=</code> until that sender is fixed or removed. A domain administrator reads the reports of their own domains.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">03</span><h2>Inbound</h2></div>
|
<div class="sp-box-head"><span class="sp-no">03</span><h2>Inbound</h2></div>
|
||||||
<div class="sp-help-topic" id="inbound"><h3>Backup-MX and forwarding</h3><p>An optional backup-MX or forwarder, switched on in the server's <code>.env</code>; the Inbound menu exists only then. Postfix accepts mail on port 25 for the listed domains only and hands each message on to that domain's upstream; there are no mailboxes. For each domain publish the MX record its page shows, and keep a primary MX the domain already has if this is a backup. The MX tag is green when at least one MX of the domain points at this server, and a domain accepts no mail until an upstream is saved on its page. Adding and deleting domains is for the global role.</p></div>
|
<div class="sp-help-topic" id="inbound"><h3>Backup-MX and forwarding</h3><p>An optional backup-MX or forwarder, switched on in the server's <code>.env</code>; the Inbound menu exists only then. Postfix accepts mail on port 25 for the listed domains only and hands each message on to that domain's upstream; there are no mailboxes. For each domain publish the MX record its page shows, and keep a primary MX the domain already has if this is a backup. The MX tag is green when at least one MX of the domain points at this server, and a domain accepts no mail until an upstream is saved on its page. Adding and deleting domains is for the global role.</p></div>
|
||||||
<div class="sp-help-topic" id="upstream"><h3>Upstream</h3><p>The upstream is the server that actually holds the mailboxes. Give its host and port — used exactly as written, with no MX lookup — and choose the TLS for the hop to it: Opportunistic uses TLS when the upstream offers it and otherwise sends in the clear; Required sends only over TLS, and the mail waits in the queue while the upstream does not offer it or the TLS handshake fails - an upstream that answers STARTTLS with a permanent error bounces it; Off never uses TLS. While the upstream does not answer, Postfix keeps the mail and retries as for any other mail.</p></div>
|
<div class="sp-help-topic" id="upstream"><h3>Upstream</h3><p>The upstream is the server that actually holds the mailboxes. Give its host and port — used exactly as written, with no MX lookup — and choose the TLS for the hop to it: Opportunistic uses TLS when the upstream offers it and otherwise sends in the clear; Required sends only over TLS, and the mail waits in the queue while the upstream does not offer it or the TLS handshake fails - an upstream that answers STARTTLS with a permanent error bounces it. Neither checks the upstream's certificate: TLS here keeps the mail from whoever only listens on the way, not from one who answers in the upstream's place. Verified is Required with that check too: the upstream's certificate has to be issued by a trusted authority for the host given here, so the host is given by name, not by address, and the mail waits in the queue while the check fails - a certificate for another name, or one the upstream signed itself, included. Off never uses TLS. While the upstream does not answer, Postfix keeps the mail and retries as for any other mail.</p></div>
|
||||||
<div class="sp-help-topic" id="recipients"><h3>Valid recipients</h3><p>Choose whom this domain accepts. Listed addresses only: the relay accepts exactly the addresses you list, one per line or comma-separated, and refuses any other while the sender is still connected, so it never has to send a bounce for them. Any recipient: everything at the domain is accepted and passed on, and an address the upstream then rejects comes back as a bounce from this server to a sender who is often forged — backscatter. Prefer a list, and keep it in step with the mailboxes upstream.</p></div>
|
<div class="sp-help-topic" id="recipients"><h3>Valid recipients</h3><p>Choose whom this domain accepts. Listed addresses only: the relay accepts exactly the addresses you list, one per line or comma-separated, and refuses any other while the sender is still connected, so it never has to send a bounce for them. Any recipient: everything at the domain is accepted and passed on, and an address the upstream then rejects comes back as a bounce from this server to a sender who is often forged — backscatter. Prefer a list, and keep it in step with the mailboxes upstream.</p></div>
|
||||||
<div class="sp-help-topic" id="filter"><h3>Spam filter</h3><p>The spam filter belongs to the server, not to a domain. The server's administrator attaches it in the server's <code>.env</code>, and from then on every inbound domain is screened by it before mail is handed on; the box shows on or off and has nothing to switch. Off means mail goes to the upstream unfiltered. While the filter is down, mail is either accepted unfiltered (the default) or refused with a temporary error, so the sending server tries again later, as the server's setting says; the box states which.</p></div>
|
<div class="sp-help-topic" id="filter"><h3>Spam filter</h3><p>The spam filter belongs to the server, not to a domain. The server's administrator attaches it in the server's <code>.env</code>, and from then on every inbound domain is screened by it before mail is handed on; the box shows on or off and has nothing to switch. Off means mail goes to the upstream unfiltered. While the filter is down, mail is either accepted unfiltered (the default) or refused with a temporary error, so the sending server tries again later, as the server's setting says; the box states which.</p></div>
|
||||||
<div class="sp-help-topic" id="inbound-log"><h3>Inbound log</h3><p>One row per recipient of a message offered on port 25 for an inbound domain, with what was decided: <span class="tag is-success is-light">accept</span> — taken for the upstream; <span class="tag is-danger is-light">reject</span> — refused for good; <span class="tag is-warning is-light">tempfail</span> — the sender was asked to come back later; <span class="tag is-primary is-light">quarantine</span> — held by the filter. <em>Why</em> names who decided. <code>rspamd</code> is the spam filter, with its action, the score against the reject threshold and the heaviest symbols; <code>postfix</code> is the relay's own rule — an unknown recipient, a sender domain that does not exist — with its reply; <code>selfpost</code> means nobody judged the message: it was relayed with no filter attached. <em>Upstream</em> says what became of an accepted message: <span class="tag is-light">queued</span> — not tried yet; <span class="tag is-primary is-light">held</span> — held in the queue by the filter, not tried until it is released; <span class="tag is-success is-light">sent</span> — the upstream took it; <span class="tag is-warning is-light">deferred</span> — the upstream put it off or could not be reached, and Postfix tries again; <span class="tag is-danger is-light">bounced</span> — the upstream refused it, or it waited in the queue too long, and the sender was sent a bounce, if it had a return address. It opens the last reply and the host that gave it. A refused message has no upstream result, and neither has a row logged before 2.10.0.<br><br>Accepted mail is recorded as it passes; refused mail is read from <code>mail.log</code>, so its row has no subject, and a message refused after its text was sent is one row however many recipients it had. A refusal to relay for a domain that is not an inbound one is every scanner on the internet and is not recorded. Rows are kept for the number of days set in Settings, and never more than 10 000.</p></div>
|
<div class="sp-help-topic" id="inbound-log"><h3>Inbound log</h3><p>One row per recipient of a message offered on port 25 for an inbound domain, with what was decided: <span class="tag is-success is-light">accept</span> — taken for the upstream; <span class="tag is-danger is-light">reject</span> — refused for good; <span class="tag is-warning is-light">tempfail</span> — the sender was asked to come back later; <span class="tag is-primary is-light">quarantine</span> — held by the filter. <em>Why</em> names who decided. <code>rspamd</code> is the spam filter, with its action, the score against the reject threshold and the heaviest symbols; <code>postfix</code> is the relay's own rule — an unknown recipient, a sender domain that does not exist — with its reply; <code>selfpost</code> means nobody judged the message: it was relayed with no filter attached. <em>Upstream</em> says what became of an accepted message: <span class="tag is-light">queued</span> — not tried yet; <span class="tag is-primary is-light">held</span> — held in the queue by the filter, not tried until it is released; <span class="tag is-success is-light">sent</span> — the upstream took it; <span class="tag is-warning is-light">deferred</span> — the upstream put it off or could not be reached, and Postfix tries again; <span class="tag is-danger is-light">bounced</span> — the upstream refused it, or it waited in the queue too long, and the sender was sent a bounce, if it had a return address. It opens the last reply and the host that gave it. A refused message has no upstream result, and neither has a row logged before 2.10.0.<br><br>Accepted mail is recorded as it passes; refused mail is read from <code>mail.log</code>, so its row has no subject, and a message refused after its text was sent is one row however many recipients it had. A refusal to relay for a domain that is not an inbound one is every scanner on the internet and is not recorded. Rows are kept for the number of days set in Settings, and never more than 10 000.</p></div>
|
||||||
|
<div class="sp-help-topic" id="inbound-queue"><h3>Inbound queue</h3><p>Mail accepted on port 25 and not yet taken by its upstream, one row per recipient, with the upstream's last reply or why it could not be reached. It is the same Postfix queue as Outbound › Queue, split by the recipient's domain, and retried the same way; an upstream that refuses or does not answer is what makes it grow. Only the global role can open it, and it only shows.</p></div>
|
||||||
<div class="sp-help-topic" id="filter-lists"><h3>Filter lists</h3><p>Two lists for the whole server that correct the spam filter by hand. An entry is a sender address, a sender domain — that domain, not its subdomains — or a client address or network in CIDR form. A sender on the Deny list is refused outright. A sender on the Allow list is given a weight no spam score outweighs, so the filter lets it through. The sender's address is whatever the sending server says it is - anyone can claim to be a sender you allow - so allow a client address or network where you can, and a sender address only for mail you would rather receive forged than lose. When entries of both lists match one message, <strong>Deny wins</strong>.<br><br>SelfPost does not apply the lists itself: it writes them as files under <code>/data/antispam/</code>, and rspamd rereads a file within seconds of a change. With the spam filter off the lists are kept and can be edited, but nothing reads them. The sender matched is the envelope sender — the one in the log — not the From line a mail program shows. Matched counts the messages in the Inbound log that the entry decided, so it reaches back as far as the log does; never means not once in that time, and such an entry is one to consider removing.</p></div>
|
<div class="sp-help-topic" id="filter-lists"><h3>Filter lists</h3><p>Two lists for the whole server that correct the spam filter by hand. An entry is a sender address, a sender domain — that domain, not its subdomains — or a client address or network in CIDR form. A sender on the Deny list is refused outright. A sender on the Allow list is given a weight no spam score outweighs, so the filter lets it through. The sender's address is whatever the sending server says it is - anyone can claim to be a sender you allow - so allow a client address or network where you can, and a sender address only for mail you would rather receive forged than lose. When entries of both lists match one message, <strong>Deny wins</strong>.<br><br>SelfPost does not apply the lists itself: it writes them as files under <code>/data/antispam/</code>, and rspamd rereads a file within seconds of a change. With the spam filter off the lists are kept and can be edited, but nothing reads them. The sender matched is the envelope sender — the one in the log — not the From line a mail program shows. Matched counts the messages in the Inbound log that the entry decided, so it reaches back as far as the log does; never means not once in that time, and such an entry is one to consider removing.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">04</span><h2>Server</h2></div>
|
<div class="sp-box-head"><span class="sp-no">04</span><h2>Server</h2></div>
|
||||||
<div class="sp-help-topic" id="backup"><h3>Backup and moving a domain</h3><p>Full backup downloads one archive of the whole instance: <code>data/</code> (the database, DKIM keys and application credentials), <code>docker-compose.yml</code>, <code>.env</code> and <code>certs/</code>. Both it and a domain's export ask for your account password - the one you sign in with - at every download, so a session left open is not enough to take them; wrong guesses count against the same limit as a password change. The Postfix queue is not in it. The backup is taken while the server is running, and Postfix keeps moving messages through the queue, so a copy could hold a message twice or miss it; mail still waiting for delivery is therefore not carried over, and a restored server starts with an empty queue. To keep the queue, stop the container and archive the project directory instead. The private key in <code>certs/</code> has to be readable by the panel: give it to group 10998 on the host (<code>chgrp 10998</code>, <code>chmod 640</code>). Until then the panel refuses to make a backup and names the file, rather than hand over an archive without the key. It holds secrets, so leave Encrypt with a password ticked: the file is sealed as <code>.spbk</code>, the password is not stored, and without it nothing can be recovered. To restore, unpack it into an empty project directory (decrypt a <code>.spbk</code> first with <code>selfpost-backup -decrypt</code>) and start the same SelfPost version that made it; the panel refuses data from another version. The reverse-proxy configuration and <code>mail.log</code> are not included. One domain moves on its own: Export domain on its settings page writes a <code>.json</code> (or encrypted <code>.spde</code>) with its DKIM key and its applications' passwords, and Import a domain here reads it into another instance, so the published DNS needs no change.</p></div>
|
<div class="sp-help-topic" id="backup"><h3>Backup and moving a domain</h3><p>Full backup downloads one archive of the whole instance: <code>data/</code> (the database, DKIM keys and application credentials), <code>docker-compose.yml</code>, <code>.env</code> and <code>certs/</code>. Both it and a domain's export ask for your account password - the one you sign in with - at every download, so a session left open is not enough to take them; a wrong one counts against the limit of a password change, ten in 15 minutes. The Postfix queue is not in it. The backup is taken while the server is running, and Postfix keeps moving messages through the queue, so a copy could hold a message twice or miss it; mail still waiting for delivery is therefore not carried over, and a restored server starts with an empty queue. To keep the queue, stop the container and archive the project directory instead. The private key in <code>certs/</code> has to be readable by the panel: give it to group 10997 on the host (<code>chgrp 10997</code>, <code>chmod 640</code>), a group only the panel is in, so Postfix's daemons cannot read it. Until then the panel refuses to make a backup and names the file, rather than hand over an archive without the key. It holds secrets, so leave Encrypt with a password ticked: the file is sealed as <code>.spbk</code>, the password is not stored, and without it nothing can be recovered. To restore, unpack it into an empty project directory (decrypt a <code>.spbk</code> first with <code>selfpost-backup -decrypt</code>) and start the same SelfPost version that made it; the panel refuses data from another version. The reverse-proxy configuration and <code>mail.log</code> are not included. One domain moves on its own: Export domain on its settings page writes a <code>.json</code> (or encrypted <code>.spde</code>) with its DKIM key and its applications' passwords, and Import a domain here reads it into another instance, so the published DNS needs no change.</p></div>
|
||||||
<div class="sp-help-topic" id="users"><h3>Users and roles</h3><p>The two roles are a reach, not a rank: every user is an administrator and signs in with a username and password. Global reaches the whole server — Overview, Server, the queue and every domain — and is the only role that adds or deletes domains and users. Domain reaches only the domains assigned to it, outbound and inbound separately (the same name on both lists is two assignments); <em>All</em> includes domains added later, and a domain user needs at least one domain or <em>All</em> on either list. The only global user cannot be demoted or deleted, and you cannot delete the account you are signed in with. A new password set here signs that user out of their other sessions.</p></div>
|
<div class="sp-help-topic" id="users"><h3>Users and roles</h3><p>The two roles are a reach, not a rank: every user is an administrator and signs in with a username and password. Global reaches the whole server — Overview, Server, the queue and every domain — and is the only role that adds or deletes domains and users. Domain reaches only the domains assigned to it, outbound and inbound separately (the same name on both lists is two assignments); <em>All</em> includes domains added later, and a domain user needs at least one domain or <em>All</em> on either list. The only global user cannot be demoted or deleted, and you cannot delete the account you are signed in with. A new password set here signs that user out everywhere; set for yourself, it keeps the session you set it from. Adding a user, changing a user's password, e-mail or role, and deleting a user ask for your own account password, as a backup does, with the same limit on wrong guesses.</p></div>
|
||||||
<div class="sp-help-topic" id="settings"><h3>Settings</h3><p>Settings holds what is true of the whole instance. The time zone is the container's, <code>TZ</code> in <code>.env</code> (UTC when it is not set): every time the panel shows and every day it counts is that zone's, as are the stamps Postfix writes in <code>mail.log</code>, so the two always agree; changing it is a restart of the container. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast <code>/data</code> grows. With the inbound relay on there is a second number, for the Inbound log, with the same range. It does not change the rotation of <code>mail.log</code>, which is daily with about two weeks kept. The sending rate limit shown under it is level 1, the per-client-IP ceiling set in <code>.env</code>: changing it needs a restart, and no level-2 limit can exceed it. Notifications are turned on or off here for everyone.</p></div>
|
<div class="sp-help-topic" id="settings"><h3>Settings</h3><p>Settings holds what is true of the whole instance. The time zone is the container's, <code>TZ</code> in <code>.env</code> (UTC when it is not set): every time the panel shows and every day it counts is that zone's, as are the stamps Postfix writes in <code>mail.log</code>, so the two always agree; changing it is a restart of the container. The one exception is the day a DMARC report covers, which is the reporter's, counted in UTC. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast <code>/data</code> grows. With the inbound relay on there is a second number, for the Inbound log, with the same range. It does not change the rotation of <code>mail.log</code>, which is daily with about two weeks kept. The sending rate limit shown under it is level 1, the per-client-IP ceiling set in <code>.env</code>: changing it needs a restart, and no level-2 limit can exceed it. Notifications are turned on or off here for everyone.</p></div>
|
||||||
<div class="sp-help-topic" id="notifications"><h3>Notifications</h3><p>The panel writes to its users when something needs them, from <code>postmaster@</code> the server's hostname. Four things are reported. A sending domain's SPF, DKIM or DMARC record stops matching: sent once two hourly checks in a row have found it so, and once more when the record matches again. A level-2 rate limit refuses mail: sent at the first refusal, then at most once a day for the same limit. The TLS certificate is 14 days from expiry, and again at 3 days if it is still the same certificate. A message has waited in the queue for more than three days, with one more message when none is left. A global user is told all of it; a domain administrator is told about the DNS records and limits of their own domains.<br><br>Each user turns notifications on or off under Account and needs an e-mail there; Server › Settings turns them off for everyone. They are the panel's own mail: not rows of the Outbound log, and not counted against any limit.<br><br>For that mail to arrive, the server's own name needs two DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, and an SPF record, <code>v=spf1 a -all</code>. Server › Settings gives both with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains does not depend on either.</p></div>
|
<div class="sp-help-topic" id="notifications"><h3>Notifications</h3><p>The panel writes to its users when something needs them, from <code>postmaster@</code> the server's hostname. Four things are reported. A sending domain's SPF, DKIM or DMARC record stops matching: sent once two hourly checks in a row have found it so, and once more when the record matches again. A level-2 rate limit refuses mail: sent at the first refusal, then at most once a day for the same sending domain. The TLS certificate is 14 days from expiry, and again at 3 days if it is still the same certificate. A message has waited in the queue for more than three days, with one more message when none is left. A global user is told all of it; a domain administrator is told about the DNS records and limits of their own domains.<br><br>Each user turns notifications on or off under Account and needs an e-mail there; Server › Settings turns them off for everyone. They are the panel's own mail: not rows of the Outbound log, and not counted against any limit.<br><br>For that mail to arrive, the server's own name needs DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, an SPF record, <code>v=spf1 a -all</code>, and a DMARC record, <code>v=DMARC1; p=reject</code>, which has receivers refuse mail forged in the server's name - a false password-reset message among it; only the panel sends as this name, signed, so reject is safe. Server › Settings gives all three with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains depends on none of them.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">05</span><h2>Account</h2></div>
|
<div class="sp-box-head"><span class="sp-no">05</span><h2>Account</h2></div>
|
||||||
<div class="sp-help-topic" id="account"><h3>Your account</h3><p>Account is your own, whatever your role: your username and e-mail, whether the panel sends you notifications, and your password. Changing the password signs out every other session of this account and keeps the one you are using; applications have their own logins and are not affected.</p></div>
|
<div class="sp-help-topic" id="account"><h3>Your account</h3><p>Account is your own, whatever your role: your username and e-mail, whether the panel sends you notifications, and your password. Changing the password signs out every other session of this account and keeps the one you are using; applications have their own logins and are not affected. A new e-mail is sent a link that confirms it, which works once, for 24 hours; changing the e-mail asks for your password. Notifications go to the address whether it is confirmed or not, but only a confirmed one can get a link to reset a forgotten password: <em>Forgot your password?</em> on the sign-in page sends it, for 30 minutes and once, and setting the new password signs every session of the account out. Without a confirmed e-mail the global administrator sets a new password under Users, and the only global administrator recovers from the container.</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -94,7 +93,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -70,7 +69,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="in-domains.html">Inbound domains</a> /</p>
|
<p class="sp-kicker"><a href="in-domains.html">Inbound domains</a> /</p>
|
||||||
<h1 class="title is-3">lists.example.org</h1>
|
<h1 class="title is-3">lists.example.org</h1>
|
||||||
<p class="sp-lead">Accepted on port 25, handed to <span class="sp-mono">mx.internal:25</span> over required TLS · 4 recipients</p>
|
<p class="sp-lead">Accepted on port 25, handed to <span class="sp-mono">mx.internal:25</span> over verified TLS · 4 recipients</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
||||||
@@ -124,7 +123,8 @@
|
|||||||
<div class="field"><label class="label" for="port">Port</label><div class="control"><input class="input sp-mono" id="port" name="port" inputmode="numeric" value="25" required></div></div>
|
<div class="field"><label class="label" for="port">Port</label><div class="control"><input class="input sp-mono" id="port" name="port" inputmode="numeric" value="25" required></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field"><label class="label" for="tls">TLS to upstream</label>
|
<div class="field"><label class="label" for="tls">TLS to upstream</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="tls" name="tls_mode"><option>Opportunistic</option><option selected>Required</option><option>Off</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="tls" name="tls_mode"><option>Opportunistic</option><option>Required</option><option selected>Verified</option><option>Off</option></select></div></div>
|
||||||
|
<p class="help">Required encrypts the hop without checking who answers. Verified also checks the upstream's certificate: issued by a trusted authority for the host above, which is then given by name.</p></div>
|
||||||
<button type="submit" class="button is-primary">Save upstream</button>
|
<button type="submit" class="button is-primary">Save upstream</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
dev@lists.example.org
|
dev@lists.example.org
|
||||||
owner@lists.example.org
|
owner@lists.example.org
|
||||||
security@lists.example.org</textarea></div>
|
security@lists.example.org</textarea></div>
|
||||||
<p class="help">Unknown recipients are rejected at RCPT, so this relay generates no backscatter. Prefer a list unless the upstream itself rejects unknowns.</p></div>
|
<p class="help">One per line or comma-separated. Unknown recipients are rejected at RCPT, so this relay generates no backscatter. Prefer a list unless the upstream itself rejects unknowns.</p></div>
|
||||||
<button type="submit" class="button is-primary">Save recipients</button>
|
<button type="submit" class="button is-primary">Save recipients</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,17 +154,16 @@ security@lists.example.org</textarea></div>
|
|||||||
<div class="sp-box-head"><span class="sp-no">04</span><h2>Spam filter</h2><div class="sp-end"><span class="tag is-success is-light">on</span></div><a class="sp-help" href="help.html#filter" title="A server setting"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">04</span><h2>Spam filter</h2><div class="sp-end"><span class="tag is-success is-light">on</span></div><a class="sp-help" href="help.html#filter" title="A server setting"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Engine</dt><dd>rspamd, for every inbound domain of this server</dd></div>
|
<div><dt>Applies to</dt><dd>Every inbound domain of this server</dd></div>
|
||||||
<div><dt>Suspicious mail</dt><dd>Held — <a href="in-quarantine.html">1 message in quarantine</a> for this domain</dd></div>
|
<div><dt>Filter</dt><dd class="sp-mono">inet:antispam:11332</dd></div>
|
||||||
<div><dt>Spam</dt><dd>Refused at the door; see <em>reject</em> in the <a href="in-log.html">log</a></dd></div>
|
<div><dt>If the filter is down</dt><dd>Mail is accepted unfiltered</dd></div>
|
||||||
<div><dt>Allow and deny lists</dt><dd class="sp-muted">Kept by the global administrator, one pair for the whole server</dd></div>
|
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html" aria-current="page"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -91,14 +90,14 @@
|
|||||||
<div class="table-container"><table class="table is-fullwidth is-hoverable">
|
<div class="table-container"><table class="table is-fullwidth is-hoverable">
|
||||||
<thead><tr><th>Domain</th><th>DNS</th><th>Upstream</th><th>Recipients</th><th>TLS to upstream</th><th></th></tr></thead>
|
<thead><tr><th>Domain</th><th>DNS</th><th>Upstream</th><th>Recipients</th><th>TLS to upstream</th><th></th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><a href="in-domain.html"><strong>lists.example.org</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">mx.internal:25</td><td>4 addresses</td><td><span class="tag is-success is-light">required</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
<tr><td><a href="in-domain.html"><strong>lists.example.org</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">mx.internal:25</td><td>4 addresses</td><td><span class="tag is-success is-light">verified</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
||||||
<tr><td><a href="in-domain.html"><strong>acme.io</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">10.0.4.12:2525</td><td>any address</td><td><span class="tag is-light">opportunistic</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
<tr><td><a href="in-domain.html"><strong>acme.io</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">10.0.4.12:2525</td><td>any address</td><td><span class="tag is-light">opportunistic</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
<div class="sp-box-foot">The MX tag is green when at least one MX of the domain points at this server.</div>
|
<div class="sp-box-foot">The MX tag is green when at least one MX of the domain points at this server.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html" aria-current="page"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html" aria-current="page"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -138,7 +137,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -100,7 +99,7 @@
|
|||||||
<div class="sp-box-foot"><span>Page 1 of 38</span><a href="#">Older →</a><span class="sp-end">A decision opens its reason, an upstream result its last reply · Allow / Deny sender opens <a href="in-filter-lists.html">Filter lists</a> with the entry filled in</span></div>
|
<div class="sp-box-foot"><span>Page 1 of 38</span><a href="#">Older →</a><span class="sp-end">A decision opens its reason, an upstream result its last reply · Allow / Deny sender opens <a href="in-filter-lists.html">Filter lists</a> with the entry filled in</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html" aria-current="page"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html" aria-current="page"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -143,7 +142,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html" aria-current="page"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html" aria-current="page"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -92,7 +91,7 @@
|
|||||||
<div class="sp-box-foot"><span>3 messages · 3.0 MB of 500 MB</span><span class="sp-end">A domain administrator sees only their assigned inbound domains</span></div>
|
<div class="sp-box-foot"><span>3 messages · 3.0 MB of 500 MB</span><span class="sp-end">A domain administrator sees only their assigned inbound domains</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html" aria-current="page"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group" open><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html" aria-current="page"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -74,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Waiting</h2><div class="sp-end">Refreshes every 10 s</div></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Waiting</h2><div class="sp-end">Refreshes on its own</div><a class="sp-help" href="help.html#inbound-queue" title="What waits here and why"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-empty"><i class="ti ti-stack-2"></i>Nothing is waiting. Every accepted message has reached its upstream.</div>
|
<div class="sp-empty"><i class="ti ti-stack-2"></i>Nothing is waiting. Every accepted message has reached its upstream.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<h1 class="title is-3">No administrator yet</h1>
|
<h1 class="title is-3">No administrator yet</h1>
|
||||||
<p class="sp-muted">Open the one-time setup link printed in the container log to create one.</p>
|
<p class="sp-muted">Open the one-time setup link printed in the container log to create one.</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Sign in · SelfPost</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="../shared/brand.css">
|
||||||
|
<link rel="stylesheet" href="theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="overview.html">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Sign in</h1>
|
||||||
|
<div class="notification is-success is-light"><i class="ti ti-circle-check"></i>If that account has a confirmed e-mail, the link is on its way. It works once, for 30 minutes.</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="username">Username</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="username" name="username" autocomplete="username" required><span class="icon is-left"><i class="ti ti-user"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="password">Password</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
||||||
|
<p class="mt-4"><a href="login-reset.html">Forgot your password?</a></p>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>New password · SelfPost</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="../shared/brand.css">
|
||||||
|
<link rel="stylesheet" href="theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="login.html">
|
||||||
|
<p class="sp-kicker">mail.example.org · admin</p>
|
||||||
|
<h1 class="title is-3">New password</h1>
|
||||||
|
<p class="sp-muted mb-4">Setting it signs every session of this account out; sign in with it afterwards.</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="new_password">New password</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="new_password" name="new_password" type="password" autocomplete="new-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="new_password_confirm">Repeat</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="new_password_confirm" name="new_password_confirm" type="password" autocomplete="new-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Set password</button></div>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Reset password · SelfPost</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="../shared/brand.css">
|
||||||
|
<link rel="stylesheet" href="theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="login-reset-sent.html">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Reset password</h1>
|
||||||
|
<p class="sp-muted mb-4">A link to set a new password goes to the e-mail of the account, once that address is confirmed under Account. It works once, for 30 minutes.</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="username">Username</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="username" name="username" autocomplete="username" required><span class="icon is-left"><i class="ti ti-user"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Send the link</button></div>
|
||||||
|
<p class="mt-4"><a href="login.html">Back to sign in</a></p>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -30,8 +30,9 @@
|
|||||||
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
||||||
|
<p class="mt-4"><a href="login-reset.html">Forgot your password?</a></p>
|
||||||
</form>
|
</form>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -100,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -69,7 +68,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="out-domains.html">Outbound domains</a> / <a href="out-domain.html">example.org</a> / Applications /</p>
|
<p class="sp-kicker"><a href="out-domains.html">Outbound domains</a> / <a href="out-domain.html">example.org</a> / Applications /</p>
|
||||||
<h1 class="title is-3 sp-mono">prod-server</h1>
|
<h1 class="title is-3 sp-mono">prod-server</h1>
|
||||||
<p class="sp-lead">1 102 messages in 30 days · peak 96 msg/h · average 1.5 msg/h. The same form, empty, adds a new application.</p>
|
<p class="sp-lead">1 102 messages in 30 days · peak 96 msg/h · average 1.5 msg/h.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button" href="out-app-created.html"><i class="ti ti-key"></i>New password</a>
|
<a class="button" href="out-app-created.html"><i class="ti ti-key"></i>New password</a>
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<div class="field"><label class="label" for="login">Login</label>
|
<div class="field"><label class="label" for="login">Login</label>
|
||||||
<div class="control"><input class="input sp-mono" id="login" name="login" value="prod-server" autocomplete="off" spellcheck="false" required></div>
|
<div class="control"><input class="input sp-mono" id="login" name="login" value="prod-server" autocomplete="off" spellcheck="false" required></div>
|
||||||
<p class="help">Unique across domains; letters, digits, <code>.</code> <code>-</code> <code>_</code>.</p></div>
|
<p class="help">Fixed: add a new application to use another login.</p></div>
|
||||||
<div class="field"><label class="label" for="amode">May send as</label>
|
<div class="field"><label class="label" for="amode">May send as</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="amode" name="mode"><option selected>Any address of the domain</option><option>Specific addresses (list)</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="amode" name="mode"><option selected>Any address of the domain</option><option>Specific addresses (list)</option></select></div></div></div>
|
||||||
<div class="field"><label class="label" for="addresses">Addresses</label>
|
<div class="field"><label class="label" for="addresses">Addresses</label>
|
||||||
@@ -129,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -81,6 +80,7 @@
|
|||||||
<ul class="sp-list-check">
|
<ul class="sp-list-check">
|
||||||
<li><i class="ti ti-x"></i>permanently delete its DKIM signing key;</li>
|
<li><i class="ti ti-x"></i>permanently delete its DKIM signing key;</li>
|
||||||
<li><i class="ti ti-x"></i>delete <strong>both applications</strong> (<span class="sp-mono">prod-server</span>, <span class="sp-mono">alerts</span>) with their credentials and sender bindings;</li>
|
<li><i class="ti ti-x"></i>delete <strong>both applications</strong> (<span class="sp-mono">prod-server</span>, <span class="sp-mono">alerts</span>) with their credentials and sender bindings;</li>
|
||||||
|
<li><i class="ti ti-x"></i>delete the <strong>12 DMARC reports</strong> received for it;</li>
|
||||||
<li><i class="ti ti-x"></i>stop signing mail as the domain.</li>
|
<li><i class="ti ti-x"></i>stop signing mail as the domain.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="sp-muted mb-4">The published DKIM record becomes invalid — remove it from DNS unless you plan to re-add the domain. Inbound domains are not touched.</p>
|
<p class="sp-muted mb-4">The published DKIM record becomes invalid — remove it from DNS unless you plan to re-add the domain. Inbound domains are not touched.</p>
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -119,7 +118,7 @@
|
|||||||
<form class="sp-form" method="post" action="#">
|
<form class="sp-form" method="post" action="#">
|
||||||
<div class="field"><label class="label" for="mode">Limit mode</label>
|
<div class="field"><label class="label" for="mode">Limit mode</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="mode" name="mode"><option>Manual</option><option selected>Auto (from statistics)</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="mode" name="mode"><option>Manual</option><option selected>Auto (from statistics)</option></select></div></div></div>
|
||||||
<div class="field"><label class="label" for="mult">Multiplier (1.5–10, default 3)</label>
|
<div class="field"><label class="label" for="mult">Multiplier (1.5–5, default 2.5)</label>
|
||||||
<div class="control"><input class="input" id="mult" name="auto_multiplier" type="number" min="1.5" max="10" step="0.1" value="3"></div>
|
<div class="control"><input class="input" id="mult" name="auto_multiplier" type="number" min="1.5" max="10" step="0.1" value="3"></div>
|
||||||
<p class="help">Computed limit: <strong>288</strong> messages / 3600 s. Peak was 96 msg/h. Last recalculated yesterday 03:00.</p></div>
|
<p class="help">Computed limit: <strong>288</strong> messages / 3600 s. Peak was 96 msg/h. Last recalculated yesterday 03:00.</p></div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
@@ -142,7 +141,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -172,7 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html" aria-current="page"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -100,7 +99,7 @@
|
|||||||
<div class="sp-box-foot">Each tag is one check. Green — published and correct; amber — published but weak; red — missing or wrong.</div>
|
<div class="sp-box-foot">Each tag is one check. Green — published and correct; amber — published but weak; red — missing or wrong.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -101,7 +100,7 @@
|
|||||||
<div class="sp-box-foot"><span>Page 1 of 412</span><a href="#">Older →</a><span class="sp-end">New rows appear on their own</span></div>
|
<div class="sp-box-foot"><span>Page 1 of 412</span><a href="#">Older →</a><span class="sp-end">New rows appear on their own</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html" aria-current="page"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -94,9 +93,9 @@
|
|||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>History</h2><a class="sp-help" href="help.html#log" title="What the statuses mean"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">02</span><h2>History</h2><a class="sp-help" href="help.html#log" title="What the statuses mean"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<ol class="sp-timeline">
|
<ol class="sp-timeline">
|
||||||
<li><time>13:58:07</time><strong>Accepted</strong> from <span class="sp-mono">alerts</span> at 198.51.100.7</li>
|
<li><time>2026-09-21 13:58:07</time><strong>Accepted and queued</strong> — Postfix accepted the message over an authenticated submission and the journal-milter recorded it.</li>
|
||||||
<li class="sp-warn"><time>13:58:09</time><strong>Deferred</strong> — the receiving server asked to retry later</li>
|
<li class="sp-warn"><time>2026-09-21 13:58:09</time><strong>Deferred, will be retried</strong> — the receiving server could not take the message yet. Postfix retries: first after 5 minutes, then with increasing gaps up to 1 hour 7 minutes, for up to 5 days.</li>
|
||||||
<li class="sp-pending"><time>next attempt ≈ 14:08</time>Still in the <a href="out-queue.html">queue</a>; kept for 5 days, then bounced</li>
|
<li class="sp-pending"><time>not yet</time>Still in the queue; the next attempt is Postfix's own, and after 5 days it is bounced.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,7 +111,7 @@
|
|||||||
</span></pre>
|
</span></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html" aria-current="page"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group" open><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html" aria-current="page"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -69,7 +68,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Outbound</p>
|
<p class="sp-kicker">Outbound</p>
|
||||||
<h1 class="title is-3">Outbound queue</h1>
|
<h1 class="title is-3">Outbound queue</h1>
|
||||||
<p class="sp-lead">Messages accepted from applications and still waiting for the receiving server. Refreshes every 10 s.</p>
|
<p class="sp-lead">Messages accepted from applications and still waiting for the receiving server. Refreshes on its own.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@
|
|||||||
"is-9": [
|
"is-9": [
|
||||||
"box[Overview]:help×4",
|
"box[Overview]:help×4",
|
||||||
"box[Outbound]:help×7",
|
"box[Outbound]:help×7",
|
||||||
"box[Inbound]:help×6",
|
"box[Inbound]:help×7",
|
||||||
"box[Server]:help×4",
|
"box[Server]:help×4",
|
||||||
"box[Account]:help"
|
"box[Account]:help"
|
||||||
]
|
]
|
||||||
@@ -380,11 +380,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"preflight": [
|
|
||||||
"head+postmark+actions",
|
|
||||||
"box[Checks]:table",
|
|
||||||
"box[Send a test e-mail]:body"
|
|
||||||
],
|
|
||||||
"settings": [
|
"settings": [
|
||||||
"head+actions",
|
"head+actions",
|
||||||
{
|
{
|
||||||
@@ -402,7 +397,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"is-6": [
|
"is-6": [
|
||||||
"box[Notification mail]:record×2",
|
"box[Notification mail]:record×3",
|
||||||
"box[Sending rate limits]:body",
|
"box[Sending rate limits]:body",
|
||||||
"box[Time zone]:body"
|
"box[Time zone]:body"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html" aria-current="page">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html" aria-current="page">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group"><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item sp-current" href="overview.html">Overview</a>
|
<a class="navbar-item sp-current" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -70,9 +69,9 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">mail.example.org</p>
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
<h1 class="title is-3">Mail is flowing. One thing needs attention.</h1>
|
<h1 class="title is-3">Mail is flowing. One thing needs attention.</h1>
|
||||||
<p class="sp-lead">The TLS certificate expires in 12 days and has not renewed yet.</p>
|
<p class="sp-lead"><a href="health.html">TLS certificate</a>: expires in 12 days.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sp-muted sp-small">Updated 14:02:31<br>refreshes every 10 s</div>
|
<div class="sp-muted sp-small">Updated 14:02:31<br>refreshes on its own</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-server-cog"></i></span><span><span class="sp-h-name">Processes</span><span class="sp-h-value">5 of 5 running</span><span class="sp-h-sub">up 18 days</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-server-cog"></i></span><span><span class="sp-h-name">Processes</span><span class="sp-h-value">5 of 5 running</span><span class="sp-h-sub">up 18 days</span></span></a>
|
||||||
<a href="health.html" class="sp-warn"><span class="sp-h-icon"><i class="ti ti-certificate"></i></span><span><span class="sp-h-name">TLS certificate</span><span class="sp-h-value">Expires in 12 days</span><span class="sp-h-sub">renewal has not run</span></span></a>
|
<a href="health.html" class="sp-warn"><span class="sp-h-icon"><i class="ti ti-certificate"></i></span><span><span class="sp-h-name">TLS certificate</span><span class="sp-h-value">Expires in 12 days</span><span class="sp-h-sub">renewal has not run</span></span></a>
|
||||||
<a href="out-queue.html"><span class="sp-h-icon"><i class="ti ti-stack-2"></i></span><span><span class="sp-h-name">Queues</span><span class="sp-h-value">Both empty</span><span class="sp-h-sub">outbound and inbound</span></span></a>
|
<a href="out-queue.html"><span class="sp-h-icon"><i class="ti ti-stack-2"></i></span><span><span class="sp-h-name">Queues</span><span class="sp-h-value">Both empty</span><span class="sp-h-sub">outbound and inbound</span></span></a>
|
||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-plug-connected"></i></span><span><span class="sp-h-name">Milter sockets</span><span class="sp-h-value">5 of 5 answering</span><span class="sp-h-sub">incl. the spam filter</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-plug-connected"></i></span><span><span class="sp-h-name">Milter sockets</span><span class="sp-h-value">6 of 6 answering</span><span class="sp-h-sub">incl. the spam filter</span></span></a>
|
||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-arrows-exchange"></i></span><span><span class="sp-h-name">Reverse DNS</span><span class="sp-h-value">Forward = reverse</span><span class="sp-h-sub">203.0.113.25</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-arrows-exchange"></i></span><span><span class="sp-h-name">Reverse DNS</span><span class="sp-h-value">Forward = reverse</span><span class="sp-h-sub">203.0.113.25</span></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-theme="light">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Preflight · SelfPost</title>
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css">
|
|
||||||
<link rel="stylesheet" href="../shared/brand.css">
|
|
||||||
<link rel="stylesheet" href="theme.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
|
||||||
<div class="container">
|
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html" aria-current="page"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
|
||||||
<div class="navbar-menu">
|
|
||||||
<div class="navbar-start">
|
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link" href="out-domains.html">Outbound</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item" href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a>
|
|
||||||
<a class="navbar-item" href="out-log.html"><i class="ti ti-list-details"></i>Log</a>
|
|
||||||
<a class="navbar-item" href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a>
|
|
||||||
<a class="navbar-item" href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link" href="in-domains.html">Inbound</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item" href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a>
|
|
||||||
<a class="navbar-item" href="in-log.html"><i class="ti ti-list-details"></i>Log</a>
|
|
||||||
<a class="navbar-item" href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a>
|
|
||||||
<a class="navbar-item" href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a>
|
|
||||||
<a class="navbar-item" href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable sp-current">
|
|
||||||
<a class="navbar-link" href="health.html">Server</a>
|
|
||||||
<div class="navbar-dropdown">
|
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
|
||||||
<a class="navbar-item is-active" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
|
||||||
<a class="navbar-item" href="settings.html"><i class="ti ti-settings"></i>Settings</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end">
|
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
|
||||||
<a class="navbar-link" href="account.html"><i class="ti ti-user-circle"></i>admin</a>
|
|
||||||
<div class="navbar-dropdown is-right">
|
|
||||||
<a class="navbar-item" href="account.html"><i class="ti ti-key"></i>Account</a>
|
|
||||||
<a class="navbar-item" href="help.html"><i class="ti ti-help"></i>Help</a>
|
|
||||||
<hr class="navbar-divider">
|
|
||||||
<form method="post" action="login.html"><button type="submit" class="navbar-item"><i class="ti ti-logout"></i>Sign out</button></form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html" aria-current="page"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
|
||||||
<main>
|
|
||||||
<div class="container sp-page">
|
|
||||||
<div class="sp-head">
|
|
||||||
<div class="sp-postmark sp-warn"><span>PREFLIGHT</span><b>WARN</b><span>1 OF 7 CHECKS</span></div>
|
|
||||||
<div class="sp-grow">
|
|
||||||
<p class="sp-kicker">Server</p>
|
|
||||||
<h1 class="title is-3">Preflight</h1>
|
|
||||||
<p class="sp-lead">Is this installation ready to deliver mail? Run it after the first start, after moving the server, or when receivers start refusing you. Health watches what runs; Preflight checks what the outside world sees.</p>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Run again</button></form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Checks</h2><div class="sp-end">Ran 14:02 · 3.1 s</div></div>
|
|
||||||
<div class="table-container"><table class="table is-fullwidth">
|
|
||||||
<thead><tr><th>Check</th><th>Result</th><th>Found</th><th>What to do</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Reverse DNS</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">203.0.113.25 ⇄ mail.example.org</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>TLS certificate</strong></td><td><span class="tag is-warning is-light">warn</span></td><td class="sp-small">Valid for <span class="sp-mono">mail.example.org</span>, expires in 12 days</td><td>Renewal has not run. Check that port 80 reaches the ACME client and look for <code>acme</code> in the <a href="system-log.html">system log</a>.</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Port 25</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">220 mail.example.org ESMTP</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Ports 465, 587</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">TLS handshake, 220 banner on both</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>HELO name</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">mail.example.org</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Proxy headers</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-small">Peer <span class="sp-mono">172.18.0.2</span> is trusted; <span class="sp-mono">X-Forwarded-Proto: https</span></td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>DKIM signing</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">inet:127.0.0.1:8891 answers</td><td class="sp-muted">—</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table></div>
|
|
||||||
<div class="sp-box-foot"><span>Ports are dialled from inside the container at the public address; a firewall that treats inside and outside differently can make this pass while the world is still blocked. SPF, DKIM and DMARC are per domain — each <a href=\"out-domains.html\">outbound domain</a> shows its own.</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>Send a test e-mail</h2><div class="sp-end">Confirms the relay accepted it, not that it arrived</div></div>
|
|
||||||
<div class="sp-box-body">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-6">
|
|
||||||
<form class="sp-form" method="post" action="#">
|
|
||||||
<div class="field"><label class="label" for="rcpt">Recipient</label>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded has-icons-left"><input class="input" id="rcpt" name="recipient" type="email" placeholder="you@elsewhere.example" required><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
|
||||||
<div class="control"><button type="submit" class="button is-primary"><i class="ti ti-send"></i>Send</button></div>
|
|
||||||
</div>
|
|
||||||
<p class="help">Sent from <span class="sp-mono">postmaster@mail.example.org</span> through the local Postfix.</p></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="column is-6">
|
|
||||||
<dl class="sp-facts">
|
|
||||||
<div><dt>Last test</dt><dd>14:03 to <span class="sp-mono">mix@elsewhere.example</span></dd></div>
|
|
||||||
<div><dt>Result</dt><dd><span class="tag is-success is-light">queued</span> <span class="sp-mono sp-small">4XcC1d8Pq2z1</span></dd></div>
|
|
||||||
<div><dt>Then</dt><dd><a href="out-message.html">Follow it in the outbound log</a></dd></div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html" aria-current="page"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html" aria-current="page"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html" aria-current="page"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html" aria-current="page"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
@@ -128,6 +127,18 @@
|
|||||||
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=spf1 a -all"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=spf1 a -all"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
<p class="help">Notifications are sent without it, and a receiver may file them as spam.</p>
|
<p class="help">Notifications are sent without it, and a receiver may file them as spam.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sp-record">
|
||||||
|
<div class="sp-record-head"><h3>DMARC</h3><span class="tag is-warning is-light">missing</span><small>For the server's own name, not for your domains</small></div>
|
||||||
|
<div class="sp-row">
|
||||||
|
<div class="field"><label class="label">Host / name</label>
|
||||||
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="_dmarc.mail.example.org"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
|
<div class="field"><label class="label">Type</label><div class="control"><input class="input" readonly value="TXT"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="field"><label class="label">Value to publish</label>
|
||||||
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=DMARC1; p=reject"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
|
<p class="help">Without it a receiver does not refuse mail forged in the server's name - a false password-reset message included. Only the panel sends as this name, signed, so reject is safe.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box" id="rate-limits">
|
<div class="box" id="rate-limits">
|
||||||
@@ -146,13 +157,13 @@
|
|||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Zone</dt><dd class="sp-mono">Europe/Moscow</dd><dd class="sp-muted">UTC+03:00. Set in <code>.env</code> (<code>TZ</code>); restart to change.</dd></div>
|
<div><dt>Zone</dt><dd class="sp-mono">Europe/Moscow</dd><dd class="sp-muted">UTC+03:00. Set in <code>.env</code> (<code>TZ</code>); restart to change.</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>.</p>
|
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>. One exception: the day a DMARC report covers is the reporter's, in UTC.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Create administrator</button></div>
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Create administrator</button></div>
|
||||||
</form>
|
</form>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,18 @@
|
|||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<form class="sp-form" method="post" action="#">
|
<form class="sp-form" method="post" action="#">
|
||||||
<div class="field"><label class="label" for="username">Username</label><div class="control has-icons-left"><input class="input" id="username" name="username" value="admin" autocomplete="username" spellcheck="false" required><span class="icon is-left"><i class="ti ti-user"></i></span></div></div>
|
<div class="field"><label class="label" for="username">Username</label><div class="control has-icons-left"><input class="input" id="username" name="username" value="admin" autocomplete="username" spellcheck="false" required><span class="icon is-left"><i class="ti ti-user"></i></span></div></div>
|
||||||
<div class="field"><label class="label" for="email">E-mail</label><div class="control has-icons-left"><input class="input" id="email" name="email" type="email" value="mix@example.org" autocomplete="email"><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
<div class="field"><label class="label" for="email">E-mail <span class="tag is-warning is-light">not confirmed</span></label><div class="control has-icons-left"><input class="input" id="email" name="email" type="email" value="mix@example.org" autocomplete="email"><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
||||||
<p class="help">Your own address. A domain's report address can be filled with it from Domain settings.</p></div>
|
<p class="help">Your own address. Saving a new one sends it a link to confirm it; once confirmed it is where a link to reset your password can go. A domain's report address can be filled with it from Domain settings.</p></div>
|
||||||
|
<div class="field"><label class="label" for="pcur">Current password</label><div class="control"><input class="input" id="pcur" name="current_password" type="password" autocomplete="current-password"></div>
|
||||||
|
<p class="help">Needed only to change the e-mail.</p></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="notify" checked> Send me notifications</label>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="notify" checked> Send me notifications</label>
|
||||||
<p class="help">To this address, when something needs you: a DNS record of a domain stops matching, a rate limit refuses mail, the TLS certificate is about to expire, a message has waited in the queue for three days. <a href="help.html#notifications">What is sent and when</a></p></div>
|
<p class="help">To this address, when something needs you: a DNS record of a domain stops matching, a rate limit refuses mail, the TLS certificate is about to expire, a message has waited in the queue for three days. <a href="help.html#notifications">What is sent and when</a></p></div>
|
||||||
<button type="submit" class="button is-primary">Save profile</button>
|
<button type="submit" class="button is-primary">Save profile</button>
|
||||||
</form>
|
</form>
|
||||||
|
<form class="sp-form mt-4" method="post" action="#">
|
||||||
|
<p class="help mb-3">A link was sent to <span class="sp-mono">mix@example.org</span> at 14:02; it works once, for 24 hours.</p>
|
||||||
|
<button type="submit" class="button">Send the link again</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!-- title: Confirm e-mail | shell: bare -->
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<div class="sp-form">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Confirm e-mail</h1>
|
||||||
|
<div class="notification is-danger is-light"><i class="ti ti-alert-circle"></i>Sign in to the account in this browser, then open the link again.</div>
|
||||||
|
<p><a href="login.html">Sign in</a></p>
|
||||||
|
</div>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!-- title: Confirm e-mail | shell: bare -->
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="account.html">
|
||||||
|
<p class="sp-kicker">mail.example.org · admin</p>
|
||||||
|
<h1 class="title is-3">Confirm e-mail</h1>
|
||||||
|
<p class="sp-muted mb-4"><span class="sp-mono">mix@example.org</span> becomes the confirmed address of this account: where a link to reset its password can be sent.</p>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Confirm</button></div>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="dmarc.html">DMARC reports</a> / <a href="dmarc-domain.html">shop.example.org</a> /</p>
|
<p class="sp-kicker"><a href="dmarc.html">DMARC reports</a> / <a href="dmarc-domain.html">shop.example.org</a> /</p>
|
||||||
<h1 class="title is-4">Outlook.com · 20 Sep 2026</h1>
|
<h1 class="title is-4">Outlook.com · 20 Sep</h1>
|
||||||
<p class="sp-route"><span>shop.example.org</span><span class="tag is-success is-light">1 903 pass</span><span class="tag is-warning is-light">7 fail</span></p>
|
<p class="sp-route"><span>shop.example.org</span><span class="tag is-success is-light">1 903 pass</span><span class="tag is-warning is-light">7 fail</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Reporter</dt><dd>Outlook.com</dd></div>
|
<div><dt>Reporter</dt><dd>Outlook.com</dd></div>
|
||||||
<div><dt>Report id</dt><dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd></div>
|
<div><dt>Report id</dt><dd class="sp-mono sp-small">5f1c9a0e7b2d4e61a3</dd></div>
|
||||||
<div><dt>Window</dt><dd>20 Sep 00:00 – 21 Sep 00:00</dd></div>
|
<div><dt>Window</dt><dd>20 Sep 03:00 – 21 Sep 03:00</dd><dd class="sp-muted">The reporter's day, 20 Sep: reports count their days in UTC, and the title and the lists name that day. The times are this panel's zone.</dd></div>
|
||||||
<div><dt>Received</dt><dd>2026-09-21 04:40</dd></div>
|
<div><dt>Received</dt><dd>2026-09-21 04:40</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Server</p>
|
<p class="sp-kicker">Server</p>
|
||||||
<h1 class="title is-3">Health</h1>
|
<h1 class="title is-3">Health</h1>
|
||||||
<p class="sp-lead">Everything behind the six cards on Overview. Refreshes every 10 s.</p>
|
<p class="sp-lead">Everything behind the six cards on Overview. Refreshes automatically.</p>
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
<tr><td>dkim-signer <span class="sp-muted">· notifications</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sign-local.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>dkim-signer <span class="sp-muted">· notifications</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sign-local.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>journal-milter<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>journal-milter<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>sender-check<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sender.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>sender-check<br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/sender.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
|
<tr><td>inbound-journal-milter <span class="sp-muted">· Inbound log</span><br><span class="sp-muted sp-mono sp-small">unix:/run/selfpost/inbound-journal.sock</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
<tr><td>rspamd <span class="sp-muted">· inbound spam filter</span><br><span class="sp-muted sp-mono sp-small">inet:antispam:11332</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
<tr><td>rspamd <span class="sp-muted">· inbound spam filter</span><br><span class="sp-muted sp-mono sp-small">inet:antispam:11332</span></td><td><span class="tag is-success is-light">ok</span></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>DKIM<br><span class="sp-muted sp-mono sp-small">sp2026._domainkey.mail.example.org</span></td><td><span class="tag is-success is-light">published</span></td></tr>
|
<tr><td>DKIM<br><span class="sp-muted sp-mono sp-small">sp2026._domainkey.mail.example.org</span></td><td><span class="tag is-success is-light">published</span></td></tr>
|
||||||
<tr><td>SPF<br><span class="sp-muted sp-mono sp-small">mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
<tr><td>SPF<br><span class="sp-muted sp-mono sp-small">mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
||||||
|
<tr><td>DMARC<br><span class="sp-muted sp-mono sp-small">_dmarc.mail.example.org</span></td><td><span class="tag is-warning is-light">missing</span></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
<li><a href="#recipients"><i class="ti ti-address-book"></i>Valid recipients</a></li>
|
<li><a href="#recipients"><i class="ti ti-address-book"></i>Valid recipients</a></li>
|
||||||
<li><a href="#filter"><i class="ti ti-shield-check"></i>Spam filter</a></li>
|
<li><a href="#filter"><i class="ti ti-shield-check"></i>Spam filter</a></li>
|
||||||
<li><a href="#inbound-log"><i class="ti ti-list-details"></i>Inbound log</a></li>
|
<li><a href="#inbound-log"><i class="ti ti-list-details"></i>Inbound log</a></li>
|
||||||
|
<li><a href="#inbound-queue"><i class="ti ti-stack-2"></i>Inbound queue</a></li>
|
||||||
<li><a href="#filter-lists"><i class="ti ti-filter"></i>Filter lists</a></li>
|
<li><a href="#filter-lists"><i class="ti ti-filter"></i>Filter lists</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="menu-label">Server</p>
|
<p class="menu-label">Server</p>
|
||||||
@@ -60,32 +61,33 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>Outbound</h2></div>
|
<div class="sp-box-head"><span class="sp-no">02</span><h2>Outbound</h2></div>
|
||||||
<div class="sp-help-topic" id="dns"><h3>DNS records</h3><p>DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow <code>include:</code> or <code>redirect=</code>, so a record that relies on them is reported as a warning, not a failure. <code>p=none</code> does not affect delivery; tighten to <code>quarantine</code>, then <code>reject</code>, once the reports look clean. A domain's report address is set in its Domain settings.</p></div>
|
<div class="sp-help-topic" id="dns"><h3>DNS records</h3><p>DKIM proves the message was signed by this relay, SPF lists the addresses allowed to send for the domain, DMARC tells receivers what to do when both fail and where to report. Publish all three exactly as shown; merge SPF into an existing record rather than adding a second one. A domain's status is the worst of its three records, and results are cached for a few minutes, so use Re-check DNS after publishing. A record DNS already holds as shown is folded to its name and status; Show values opens it to copy them again. The SPF check is shallow: it looks for this server's address in the record itself and does not follow <code>include:</code> or <code>redirect=</code>, so a record that relies on them is reported as a warning, not a failure. <code>p=none</code> does not affect delivery; tighten to <code>quarantine</code>, then <code>reject</code>, once the reports look clean. A domain's report address is set in its Domain settings.</p></div>
|
||||||
<div class="sp-help-topic" id="apps"><h3>Applications</h3><p>An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once at creation or regeneration and is not stored in readable form.</p></div>
|
<div class="sp-help-topic" id="apps"><h3>Applications</h3><p>An application is one SASL login, unique across all domains. It may send as any address of its domain or only as listed addresses, and, with a list of client IPs set, only from those addresses. The password is shown once, at creation or regeneration, and the panel does not show it again; the server keeps it, so a domain export can carry it.</p></div>
|
||||||
<div class="sp-help-topic" id="sender-check"><h3>Sender check</h3><p>An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The <code>From:</code> line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other <code>From:</code> - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with <code>550 5.7.1</code>. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A <code>Sender:</code> line, which a mail program shows beside <code>From:</code> as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in <code>From:</code> with the person's name, and the person's address in <code>Reply-To:</code>: their domain in <code>From:</code> would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.<br><br>The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.</p></div>
|
<div class="sp-help-topic" id="sender-check"><h3>Sender check</h3><p>An application sends only as itself, and that holds for every sender a message names. The envelope sender is held against the login by Postfix. The <code>From:</code> line, the one a mail program shows and the one DMARC holds the DKIM signature to, is held against it by the panel, under the same rule: an application set to any address of its domain may write any address of that domain, and one with a list of addresses only those. A message with any other <code>From:</code> - another of your domains, the server's own name, an address somewhere else, or none at all - is refused for good with <code>550 5.7.1</code>. The line must hold one address, read as a mail program reads it: what a display name around it says is not judged, and a line holding more than one address is refused. A <code>Sender:</code> line, which a mail program shows beside <code>From:</code> as "on behalf of", is held to the same rule when the message has one. A system that writes for people outside your domains writes its own address in <code>From:</code> with the person's name, and the person's address in <code>Reply-To:</code>: their domain in <code>From:</code> would fail DMARC at the receiver anyway. It is a rejected row of the Outbound log, and its page says which address was refused. This is what keeps one application from sending, under this server's signature, as another domain or another application.<br><br>The check is strict. While the panel does not answer, Postfix does not accept mail unchecked: it replies with a temporary error and the application sends again later. Server › Health counts what that costs: for how long the panel did not answer while Postfix itself was running; how many connections Postfix turned away because of it - it refuses from the connection on, before a message is offered, so a client that tries three times is three and a monitor that only connects is one; and how many messages were refused with a temporary error in the middle, when the panel stopped answering after a message had begun. A time marked ≈ includes a start at which supervisord could not say since when Postfix was running: the time since the panel's last sign of life is counted, which may be more than Postfix was up. The card warns when a connection was turned away or a message refused in the last 24 hours. If the numbers stay at nothing, the check is where it should be; if they do not, it belongs in a process of its own.</p></div>
|
||||||
<div class="sp-help-topic" id="limits"><h3>Rate limits</h3><p>Level 1 is a per-IP ceiling set in <code>.env</code>. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.</p></div>
|
<div class="sp-help-topic" id="limits"><h3>Rate limits</h3><p>Level 1 is a per-IP ceiling set in <code>.env</code>. Level 2 is optional, per domain and per application, entered by hand or computed from the last 30 days of sending, and can never exceed level 1. An application's own limit replaces its domain's.</p></div>
|
||||||
<div class="sp-help-topic" id="log"><h3>Outbound log</h3><p>One row per message and recipient, with the status Postfix last reported: queued (accepted, nothing reported yet), sent (the receiving server took it; what the mailbox does next is not reported back), deferred (not taken yet, Postfix tries again), bounced (failed for good, or the queue lifetime ran out; or closed by the panel when Postfix no longer held the message and <code>mail.log</code> never said what became of it) and rejected (refused before queueing, so it never reached the queue: under a level-2 rate limit, from a client address the application does not allow, or for a <code>From:</code> or <code>Sender:</code> line that is not the application's - the message's page says which). A message's page lists these steps and the lines Postfix wrote about it in <code>mail.log</code>, found by queue id - and for a message refused before it had one, Postfix's one line about the refusal. The lines are kept with the row: they stay on the page after that file has rotated, for as long as the row does. A message accepted before version 2.2.0 shows them only while they are still in the current file. Rows are deleted after the retention set in Server › Settings, 90 days by default, and their lines with them. A domain administrator sees only the rows of their own domains.</p></div>
|
<div class="sp-help-topic" id="log"><h3>Outbound log</h3><p>One row per message and recipient (a message refused for its From: or Sender: line is one row, however many recipients it had), with the status Postfix last reported: queued (accepted, nothing reported yet), sent (the receiving server took it; what the mailbox does next is not reported back), deferred (not taken yet, Postfix tries again), bounced (failed for good, or the queue lifetime ran out; or closed by the panel when Postfix no longer held the message and <code>mail.log</code> never said what became of it) and rejected (refused before queueing, so it never reached the queue: under a level-2 rate limit, from a client address the application does not allow, or for a <code>From:</code> or <code>Sender:</code> line that is not the application's - the message's page says which). A message's page lists these steps and the lines Postfix wrote about it in <code>mail.log</code>, found by queue id - and for a message refused before it had one, Postfix's one line about the refusal. The lines are kept with the row: they stay on the page after that file has rotated, for as long as the row does. A message accepted before version 2.2.0 shows them only while they are still in the current file. Rows are deleted after the retention set in Server › Settings, 90 days by default, and their lines with them. A domain administrator sees only the rows of their own domains.</p></div>
|
||||||
<div class="sp-help-topic" id="queue"><h3>Queue and retries</h3><p>The page lists, as <code>postqueue -p</code> prints it, the messages Postfix has accepted and not yet delivered: mail being sent right now and mail deferred for a later attempt. Only the global role can open it, and it only shows; nothing here deletes or forces a message. A deferred message is tried again after the first retry delay, then at gaps that double up to a cap, until the receiving server takes it or the queue lifetime runs out and it bounces; there is no attempt counter, and the box on the page shows this server's values. A queue that keeps growing means a receiver is refusing or not answering: open the message from the Outbound log to read the reason Postfix logged.</p></div>
|
<div class="sp-help-topic" id="queue"><h3>Queue and retries</h3><p>The page lists the messages Postfix has accepted and not yet delivered, one row per recipient: mail being sent right now and mail deferred for a later attempt, with the last reply of the receiving server, or why no connection was made. Mail for an inbound domain waits under Inbound › Queue instead. Only the global role can open it, and it only shows; nothing here deletes or forces a message, and Details opens the message in the Outbound log. A deferred message is tried again after the first retry delay, then at gaps that double up to a cap, until the receiving server takes it or the queue lifetime runs out and it bounces; there is no attempt counter, and the box on the page shows this server's values. A queue that keeps growing means a receiver is refusing or not answering; the last reply says which.</p></div>
|
||||||
<div class="sp-help-topic" id="dmarc"><h3>DMARC reports</h3><p>An aggregate report is the summary a receiving provider sends, usually daily, to the <code>rua=</code> address of your DMARC record: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as <em>this relay</em>. A fail from any other source is someone else sending as your domain — a forwarder, an application you forgot, or abuse — so do not tighten <code>p=</code> until that sender is fixed or removed. A domain administrator reads the reports of their own domains.</p></div>
|
<div class="sp-help-topic" id="dmarc"><h3>DMARC reports</h3><p>An aggregate report is the summary a receiving provider sends, usually daily, to the <code>rua=</code> address of your DMARC record - its day is the reporter's, counted in UTC, and that is the day the lists and a report's title name, while the report's page gives its window in this panel's zone: which servers sent mail as your domain and whether SPF or DKIM passed for it. A domain's report address is one of three choices, set in its Domain settings: no reports (the default for a new domain), the SelfPost hosted address (an address on this server whose reports are parsed and shown under DMARC reports; offered only while hosted reports are enabled on the server), or an address typed for the domain (an ordinary mailbox that receives the raw XML; a button fills in your own profile e-mail). When the typed address is on another domain than the one sending, that domain must publish a report-authorization record, or providers will not send; the domain's page shows it with the other DNS records and checks it. In the tables pass means SPF or DKIM passed and aligned, fail means neither did, and your own server shows as <em>this relay</em>. A fail from any other source is someone else sending as your domain — a forwarder, an application you forgot, or abuse — so do not tighten <code>p=</code> until that sender is fixed or removed. A domain administrator reads the reports of their own domains.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">03</span><h2>Inbound</h2></div>
|
<div class="sp-box-head"><span class="sp-no">03</span><h2>Inbound</h2></div>
|
||||||
<div class="sp-help-topic" id="inbound"><h3>Backup-MX and forwarding</h3><p>An optional backup-MX or forwarder, switched on in the server's <code>.env</code>; the Inbound menu exists only then. Postfix accepts mail on port 25 for the listed domains only and hands each message on to that domain's upstream; there are no mailboxes. For each domain publish the MX record its page shows, and keep a primary MX the domain already has if this is a backup. The MX tag is green when at least one MX of the domain points at this server, and a domain accepts no mail until an upstream is saved on its page. Adding and deleting domains is for the global role.</p></div>
|
<div class="sp-help-topic" id="inbound"><h3>Backup-MX and forwarding</h3><p>An optional backup-MX or forwarder, switched on in the server's <code>.env</code>; the Inbound menu exists only then. Postfix accepts mail on port 25 for the listed domains only and hands each message on to that domain's upstream; there are no mailboxes. For each domain publish the MX record its page shows, and keep a primary MX the domain already has if this is a backup. The MX tag is green when at least one MX of the domain points at this server, and a domain accepts no mail until an upstream is saved on its page. Adding and deleting domains is for the global role.</p></div>
|
||||||
<div class="sp-help-topic" id="upstream"><h3>Upstream</h3><p>The upstream is the server that actually holds the mailboxes. Give its host and port — used exactly as written, with no MX lookup — and choose the TLS for the hop to it: Opportunistic uses TLS when the upstream offers it and otherwise sends in the clear; Required sends only over TLS, and the mail waits in the queue while the upstream does not offer it or the TLS handshake fails - an upstream that answers STARTTLS with a permanent error bounces it; Off never uses TLS. While the upstream does not answer, Postfix keeps the mail and retries as for any other mail.</p></div>
|
<div class="sp-help-topic" id="upstream"><h3>Upstream</h3><p>The upstream is the server that actually holds the mailboxes. Give its host and port — used exactly as written, with no MX lookup — and choose the TLS for the hop to it: Opportunistic uses TLS when the upstream offers it and otherwise sends in the clear; Required sends only over TLS, and the mail waits in the queue while the upstream does not offer it or the TLS handshake fails - an upstream that answers STARTTLS with a permanent error bounces it. Neither checks the upstream's certificate: TLS here keeps the mail from whoever only listens on the way, not from one who answers in the upstream's place. Verified is Required with that check too: the upstream's certificate has to be issued by a trusted authority for the host given here, so the host is given by name, not by address, and the mail waits in the queue while the check fails - a certificate for another name, or one the upstream signed itself, included. Off never uses TLS. While the upstream does not answer, Postfix keeps the mail and retries as for any other mail.</p></div>
|
||||||
<div class="sp-help-topic" id="recipients"><h3>Valid recipients</h3><p>Choose whom this domain accepts. Listed addresses only: the relay accepts exactly the addresses you list, one per line or comma-separated, and refuses any other while the sender is still connected, so it never has to send a bounce for them. Any recipient: everything at the domain is accepted and passed on, and an address the upstream then rejects comes back as a bounce from this server to a sender who is often forged — backscatter. Prefer a list, and keep it in step with the mailboxes upstream.</p></div>
|
<div class="sp-help-topic" id="recipients"><h3>Valid recipients</h3><p>Choose whom this domain accepts. Listed addresses only: the relay accepts exactly the addresses you list, one per line or comma-separated, and refuses any other while the sender is still connected, so it never has to send a bounce for them. Any recipient: everything at the domain is accepted and passed on, and an address the upstream then rejects comes back as a bounce from this server to a sender who is often forged — backscatter. Prefer a list, and keep it in step with the mailboxes upstream.</p></div>
|
||||||
<div class="sp-help-topic" id="filter"><h3>Spam filter</h3><p>The spam filter belongs to the server, not to a domain. The server's administrator attaches it in the server's <code>.env</code>, and from then on every inbound domain is screened by it before mail is handed on; the box shows on or off and has nothing to switch. Off means mail goes to the upstream unfiltered. While the filter is down, mail is either accepted unfiltered (the default) or refused with a temporary error, so the sending server tries again later, as the server's setting says; the box states which.</p></div>
|
<div class="sp-help-topic" id="filter"><h3>Spam filter</h3><p>The spam filter belongs to the server, not to a domain. The server's administrator attaches it in the server's <code>.env</code>, and from then on every inbound domain is screened by it before mail is handed on; the box shows on or off and has nothing to switch. Off means mail goes to the upstream unfiltered. While the filter is down, mail is either accepted unfiltered (the default) or refused with a temporary error, so the sending server tries again later, as the server's setting says; the box states which.</p></div>
|
||||||
<div class="sp-help-topic" id="inbound-log"><h3>Inbound log</h3><p>One row per recipient of a message offered on port 25 for an inbound domain, with what was decided: <span class="tag is-success is-light">accept</span> — taken for the upstream; <span class="tag is-danger is-light">reject</span> — refused for good; <span class="tag is-warning is-light">tempfail</span> — the sender was asked to come back later; <span class="tag is-primary is-light">quarantine</span> — held by the filter. <em>Why</em> names who decided. <code>rspamd</code> is the spam filter, with its action, the score against the reject threshold and the heaviest symbols; <code>postfix</code> is the relay's own rule — an unknown recipient, a sender domain that does not exist — with its reply; <code>selfpost</code> means nobody judged the message: it was relayed with no filter attached. <em>Upstream</em> says what became of an accepted message: <span class="tag is-light">queued</span> — not tried yet; <span class="tag is-primary is-light">held</span> — held in the queue by the filter, not tried until it is released; <span class="tag is-success is-light">sent</span> — the upstream took it; <span class="tag is-warning is-light">deferred</span> — the upstream put it off or could not be reached, and Postfix tries again; <span class="tag is-danger is-light">bounced</span> — the upstream refused it, or it waited in the queue too long, and the sender was sent a bounce, if it had a return address. It opens the last reply and the host that gave it. A refused message has no upstream result, and neither has a row logged before 2.10.0.<br><br>Accepted mail is recorded as it passes; refused mail is read from <code>mail.log</code>, so its row has no subject, and a message refused after its text was sent is one row however many recipients it had. A refusal to relay for a domain that is not an inbound one is every scanner on the internet and is not recorded. Rows are kept for the number of days set in Settings, and never more than 10 000.</p></div>
|
<div class="sp-help-topic" id="inbound-log"><h3>Inbound log</h3><p>One row per recipient of a message offered on port 25 for an inbound domain, with what was decided: <span class="tag is-success is-light">accept</span> — taken for the upstream; <span class="tag is-danger is-light">reject</span> — refused for good; <span class="tag is-warning is-light">tempfail</span> — the sender was asked to come back later; <span class="tag is-primary is-light">quarantine</span> — held by the filter. <em>Why</em> names who decided. <code>rspamd</code> is the spam filter, with its action, the score against the reject threshold and the heaviest symbols; <code>postfix</code> is the relay's own rule — an unknown recipient, a sender domain that does not exist — with its reply; <code>selfpost</code> means nobody judged the message: it was relayed with no filter attached. <em>Upstream</em> says what became of an accepted message: <span class="tag is-light">queued</span> — not tried yet; <span class="tag is-primary is-light">held</span> — held in the queue by the filter, not tried until it is released; <span class="tag is-success is-light">sent</span> — the upstream took it; <span class="tag is-warning is-light">deferred</span> — the upstream put it off or could not be reached, and Postfix tries again; <span class="tag is-danger is-light">bounced</span> — the upstream refused it, or it waited in the queue too long, and the sender was sent a bounce, if it had a return address. It opens the last reply and the host that gave it. A refused message has no upstream result, and neither has a row logged before 2.10.0.<br><br>Accepted mail is recorded as it passes; refused mail is read from <code>mail.log</code>, so its row has no subject, and a message refused after its text was sent is one row however many recipients it had. A refusal to relay for a domain that is not an inbound one is every scanner on the internet and is not recorded. Rows are kept for the number of days set in Settings, and never more than 10 000.</p></div>
|
||||||
|
<div class="sp-help-topic" id="inbound-queue"><h3>Inbound queue</h3><p>Mail accepted on port 25 and not yet taken by its upstream, one row per recipient, with the upstream's last reply or why it could not be reached. It is the same Postfix queue as Outbound › Queue, split by the recipient's domain, and retried the same way; an upstream that refuses or does not answer is what makes it grow. Only the global role can open it, and it only shows.</p></div>
|
||||||
<div class="sp-help-topic" id="filter-lists"><h3>Filter lists</h3><p>Two lists for the whole server that correct the spam filter by hand. An entry is a sender address, a sender domain — that domain, not its subdomains — or a client address or network in CIDR form. A sender on the Deny list is refused outright. A sender on the Allow list is given a weight no spam score outweighs, so the filter lets it through. The sender's address is whatever the sending server says it is - anyone can claim to be a sender you allow - so allow a client address or network where you can, and a sender address only for mail you would rather receive forged than lose. When entries of both lists match one message, <strong>Deny wins</strong>.<br><br>SelfPost does not apply the lists itself: it writes them as files under <code>/data/antispam/</code>, and rspamd rereads a file within seconds of a change. With the spam filter off the lists are kept and can be edited, but nothing reads them. The sender matched is the envelope sender — the one in the log — not the From line a mail program shows. Matched counts the messages in the Inbound log that the entry decided, so it reaches back as far as the log does; never means not once in that time, and such an entry is one to consider removing.</p></div>
|
<div class="sp-help-topic" id="filter-lists"><h3>Filter lists</h3><p>Two lists for the whole server that correct the spam filter by hand. An entry is a sender address, a sender domain — that domain, not its subdomains — or a client address or network in CIDR form. A sender on the Deny list is refused outright. A sender on the Allow list is given a weight no spam score outweighs, so the filter lets it through. The sender's address is whatever the sending server says it is - anyone can claim to be a sender you allow - so allow a client address or network where you can, and a sender address only for mail you would rather receive forged than lose. When entries of both lists match one message, <strong>Deny wins</strong>.<br><br>SelfPost does not apply the lists itself: it writes them as files under <code>/data/antispam/</code>, and rspamd rereads a file within seconds of a change. With the spam filter off the lists are kept and can be edited, but nothing reads them. The sender matched is the envelope sender — the one in the log — not the From line a mail program shows. Matched counts the messages in the Inbound log that the entry decided, so it reaches back as far as the log does; never means not once in that time, and such an entry is one to consider removing.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">04</span><h2>Server</h2></div>
|
<div class="sp-box-head"><span class="sp-no">04</span><h2>Server</h2></div>
|
||||||
<div class="sp-help-topic" id="backup"><h3>Backup and moving a domain</h3><p>Full backup downloads one archive of the whole instance: <code>data/</code> (the database, DKIM keys and application credentials), <code>docker-compose.yml</code>, <code>.env</code> and <code>certs/</code>. Both it and a domain's export ask for your account password - the one you sign in with - at every download, so a session left open is not enough to take them; wrong guesses count against the same limit as a password change. The Postfix queue is not in it. The backup is taken while the server is running, and Postfix keeps moving messages through the queue, so a copy could hold a message twice or miss it; mail still waiting for delivery is therefore not carried over, and a restored server starts with an empty queue. To keep the queue, stop the container and archive the project directory instead. The private key in <code>certs/</code> has to be readable by the panel: give it to group 10998 on the host (<code>chgrp 10998</code>, <code>chmod 640</code>). Until then the panel refuses to make a backup and names the file, rather than hand over an archive without the key. It holds secrets, so leave Encrypt with a password ticked: the file is sealed as <code>.spbk</code>, the password is not stored, and without it nothing can be recovered. To restore, unpack it into an empty project directory (decrypt a <code>.spbk</code> first with <code>selfpost-backup -decrypt</code>) and start the same SelfPost version that made it; the panel refuses data from another version. The reverse-proxy configuration and <code>mail.log</code> are not included. One domain moves on its own: Export domain on its settings page writes a <code>.json</code> (or encrypted <code>.spde</code>) with its DKIM key and its applications' passwords, and Import a domain here reads it into another instance, so the published DNS needs no change.</p></div>
|
<div class="sp-help-topic" id="backup"><h3>Backup and moving a domain</h3><p>Full backup downloads one archive of the whole instance: <code>data/</code> (the database, DKIM keys and application credentials), <code>docker-compose.yml</code>, <code>.env</code> and <code>certs/</code>. Both it and a domain's export ask for your account password - the one you sign in with - at every download, so a session left open is not enough to take them; a wrong one counts against the limit of a password change, ten in 15 minutes. The Postfix queue is not in it. The backup is taken while the server is running, and Postfix keeps moving messages through the queue, so a copy could hold a message twice or miss it; mail still waiting for delivery is therefore not carried over, and a restored server starts with an empty queue. To keep the queue, stop the container and archive the project directory instead. The private key in <code>certs/</code> has to be readable by the panel: give it to group 10997 on the host (<code>chgrp 10997</code>, <code>chmod 640</code>), a group only the panel is in, so Postfix's daemons cannot read it. Until then the panel refuses to make a backup and names the file, rather than hand over an archive without the key. It holds secrets, so leave Encrypt with a password ticked: the file is sealed as <code>.spbk</code>, the password is not stored, and without it nothing can be recovered. To restore, unpack it into an empty project directory (decrypt a <code>.spbk</code> first with <code>selfpost-backup -decrypt</code>) and start the same SelfPost version that made it; the panel refuses data from another version. The reverse-proxy configuration and <code>mail.log</code> are not included. One domain moves on its own: Export domain on its settings page writes a <code>.json</code> (or encrypted <code>.spde</code>) with its DKIM key and its applications' passwords, and Import a domain here reads it into another instance, so the published DNS needs no change.</p></div>
|
||||||
<div class="sp-help-topic" id="users"><h3>Users and roles</h3><p>The two roles are a reach, not a rank: every user is an administrator and signs in with a username and password. Global reaches the whole server — Overview, Server, the queue and every domain — and is the only role that adds or deletes domains and users. Domain reaches only the domains assigned to it, outbound and inbound separately (the same name on both lists is two assignments); <em>All</em> includes domains added later, and a domain user needs at least one domain or <em>All</em> on either list. The only global user cannot be demoted or deleted, and you cannot delete the account you are signed in with. A new password set here signs that user out of their other sessions.</p></div>
|
<div class="sp-help-topic" id="users"><h3>Users and roles</h3><p>The two roles are a reach, not a rank: every user is an administrator and signs in with a username and password. Global reaches the whole server — Overview, Server, the queue and every domain — and is the only role that adds or deletes domains and users. Domain reaches only the domains assigned to it, outbound and inbound separately (the same name on both lists is two assignments); <em>All</em> includes domains added later, and a domain user needs at least one domain or <em>All</em> on either list. The only global user cannot be demoted or deleted, and you cannot delete the account you are signed in with. A new password set here signs that user out everywhere; set for yourself, it keeps the session you set it from. Adding a user, changing a user's password, e-mail or role, and deleting a user ask for your own account password, as a backup does, with the same limit on wrong guesses.</p></div>
|
||||||
<div class="sp-help-topic" id="settings"><h3>Settings</h3><p>Settings holds what is true of the whole instance. The time zone is the container's, <code>TZ</code> in <code>.env</code> (UTC when it is not set): every time the panel shows and every day it counts is that zone's, as are the stamps Postfix writes in <code>mail.log</code>, so the two always agree; changing it is a restart of the container. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast <code>/data</code> grows. With the inbound relay on there is a second number, for the Inbound log, with the same range. It does not change the rotation of <code>mail.log</code>, which is daily with about two weeks kept. The sending rate limit shown under it is level 1, the per-client-IP ceiling set in <code>.env</code>: changing it needs a restart, and no level-2 limit can exceed it. Notifications are turned on or off here for everyone.</p></div>
|
<div class="sp-help-topic" id="settings"><h3>Settings</h3><p>Settings holds what is true of the whole instance. The time zone is the container's, <code>TZ</code> in <code>.env</code> (UTC when it is not set): every time the panel shows and every day it counts is that zone's, as are the stamps Postfix writes in <code>mail.log</code>, so the two always agree; changing it is a restart of the container. The one exception is the day a DMARC report covers, which is the reporter's, counted in UTC. Log retention is how many days the Outbound log keeps its rows, from 7 to 365 and 90 by default; a sweep every six hours deletes older ones, and it is the main driver of how fast <code>/data</code> grows. With the inbound relay on there is a second number, for the Inbound log, with the same range. It does not change the rotation of <code>mail.log</code>, which is daily with about two weeks kept. The sending rate limit shown under it is level 1, the per-client-IP ceiling set in <code>.env</code>: changing it needs a restart, and no level-2 limit can exceed it. Notifications are turned on or off here for everyone.</p></div>
|
||||||
<div class="sp-help-topic" id="notifications"><h3>Notifications</h3><p>The panel writes to its users when something needs them, from <code>postmaster@</code> the server's hostname. Four things are reported. A sending domain's SPF, DKIM or DMARC record stops matching: sent once two hourly checks in a row have found it so, and once more when the record matches again. A level-2 rate limit refuses mail: sent at the first refusal, then at most once a day for the same limit. The TLS certificate is 14 days from expiry, and again at 3 days if it is still the same certificate. A message has waited in the queue for more than three days, with one more message when none is left. A global user is told all of it; a domain administrator is told about the DNS records and limits of their own domains.<br><br>Each user turns notifications on or off under Account and needs an e-mail there; Server › Settings turns them off for everyone. They are the panel's own mail: not rows of the Outbound log, and not counted against any limit.<br><br>For that mail to arrive, the server's own name needs two DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, and an SPF record, <code>v=spf1 a -all</code>. Server › Settings gives both with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains does not depend on either.</p></div>
|
<div class="sp-help-topic" id="notifications"><h3>Notifications</h3><p>The panel writes to its users when something needs them, from <code>postmaster@</code> the server's hostname. Four things are reported. A sending domain's SPF, DKIM or DMARC record stops matching: sent once two hourly checks in a row have found it so, and once more when the record matches again. A level-2 rate limit refuses mail: sent at the first refusal, then at most once a day for the same sending domain. The TLS certificate is 14 days from expiry, and again at 3 days if it is still the same certificate. A message has waited in the queue for more than three days, with one more message when none is left. A global user is told all of it; a domain administrator is told about the DNS records and limits of their own domains.<br><br>Each user turns notifications on or off under Account and needs an e-mail there; Server › Settings turns them off for everyone. They are the panel's own mail: not rows of the Outbound log, and not counted against any limit.<br><br>For that mail to arrive, the server's own name needs DNS records of its own, as each of your domains does: a DKIM key, which the panel generates and signs notifications with, an SPF record, <code>v=spf1 a -all</code>, and a DMARC record, <code>v=DMARC1; p=reject</code>, which has receivers refuse mail forged in the server's name - a false password-reset message among it; only the panel sends as this name, signed, so reject is safe. Server › Settings gives all three with the values to publish and whether they are in place, and its Re-check DNS checks them again once they are published; Server › Health shows them too. Without them notifications are still sent, and a receiver may file them as spam. Mail of your domains depends on none of them.</p></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">05</span><h2>Account</h2></div>
|
<div class="sp-box-head"><span class="sp-no">05</span><h2>Account</h2></div>
|
||||||
<div class="sp-help-topic" id="account"><h3>Your account</h3><p>Account is your own, whatever your role: your username and e-mail, whether the panel sends you notifications, and your password. Changing the password signs out every other session of this account and keeps the one you are using; applications have their own logins and are not affected.</p></div>
|
<div class="sp-help-topic" id="account"><h3>Your account</h3><p>Account is your own, whatever your role: your username and e-mail, whether the panel sends you notifications, and your password. Changing the password signs out every other session of this account and keeps the one you are using; applications have their own logins and are not affected. A new e-mail is sent a link that confirms it, which works once, for 24 hours; changing the e-mail asks for your password. Notifications go to the address whether it is confirmed or not, but only a confirmed one can get a link to reset a forgotten password: <em>Forgot your password?</em> on the sign-in page sends it, for 30 minutes and once, and setting the new password signs every session of the account out. Without a confirmed e-mail the global administrator sets a new password under Users, and the only global administrator recovers from the container.</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="in-domains.html">Inbound domains</a> /</p>
|
<p class="sp-kicker"><a href="in-domains.html">Inbound domains</a> /</p>
|
||||||
<h1 class="title is-3">lists.example.org</h1>
|
<h1 class="title is-3">lists.example.org</h1>
|
||||||
<p class="sp-lead">Accepted on port 25, handed to <span class="sp-mono">mx.internal:25</span> over required TLS · 4 recipients</p>
|
<p class="sp-lead">Accepted on port 25, handed to <span class="sp-mono">mx.internal:25</span> over verified TLS · 4 recipients</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Re-check DNS</button></form>
|
||||||
@@ -58,7 +58,8 @@
|
|||||||
<div class="field"><label class="label" for="port">Port</label><div class="control"><input class="input sp-mono" id="port" name="port" inputmode="numeric" value="25" required></div></div>
|
<div class="field"><label class="label" for="port">Port</label><div class="control"><input class="input sp-mono" id="port" name="port" inputmode="numeric" value="25" required></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field"><label class="label" for="tls">TLS to upstream</label>
|
<div class="field"><label class="label" for="tls">TLS to upstream</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="tls" name="tls_mode"><option>Opportunistic</option><option selected>Required</option><option>Off</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="tls" name="tls_mode"><option>Opportunistic</option><option>Required</option><option selected>Verified</option><option>Off</option></select></div></div>
|
||||||
|
<p class="help">Required encrypts the hop without checking who answers. Verified also checks the upstream's certificate: issued by a trusted authority for the host above, which is then given by name.</p></div>
|
||||||
<button type="submit" class="button is-primary">Save upstream</button>
|
<button type="submit" class="button is-primary">Save upstream</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
dev@lists.example.org
|
dev@lists.example.org
|
||||||
owner@lists.example.org
|
owner@lists.example.org
|
||||||
security@lists.example.org</textarea></div>
|
security@lists.example.org</textarea></div>
|
||||||
<p class="help">Unknown recipients are rejected at RCPT, so this relay generates no backscatter. Prefer a list unless the upstream itself rejects unknowns.</p></div>
|
<p class="help">One per line or comma-separated. Unknown recipients are rejected at RCPT, so this relay generates no backscatter. Prefer a list unless the upstream itself rejects unknowns.</p></div>
|
||||||
<button type="submit" class="button is-primary">Save recipients</button>
|
<button type="submit" class="button is-primary">Save recipients</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,10 +89,9 @@ security@lists.example.org</textarea></div>
|
|||||||
<div class="sp-box-head"><span class="sp-no">04</span><h2>Spam filter</h2><div class="sp-end"><span class="tag is-success is-light">on</span></div><a class="sp-help" href="help.html#filter" title="A server setting"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">04</span><h2>Spam filter</h2><div class="sp-end"><span class="tag is-success is-light">on</span></div><a class="sp-help" href="help.html#filter" title="A server setting"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Engine</dt><dd>rspamd, for every inbound domain of this server</dd></div>
|
<div><dt>Applies to</dt><dd>Every inbound domain of this server</dd></div>
|
||||||
<div><dt>Suspicious mail</dt><dd>Held — <a href="in-quarantine.html">1 message in quarantine</a> for this domain</dd></div>
|
<div><dt>Filter</dt><dd class="sp-mono">inet:antispam:11332</dd></div>
|
||||||
<div><dt>Spam</dt><dd>Refused at the door; see <em>reject</em> in the <a href="in-log.html">log</a></dd></div>
|
<div><dt>If the filter is down</dt><dd>Mail is accepted unfiltered</dd></div>
|
||||||
<div><dt>Allow and deny lists</dt><dd class="sp-muted">Kept by the global administrator, one pair for the whole server</dd></div>
|
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="table-container"><table class="table is-fullwidth is-hoverable">
|
<div class="table-container"><table class="table is-fullwidth is-hoverable">
|
||||||
<thead><tr><th>Domain</th><th>DNS</th><th>Upstream</th><th>Recipients</th><th>TLS to upstream</th><th></th></tr></thead>
|
<thead><tr><th>Domain</th><th>DNS</th><th>Upstream</th><th>Recipients</th><th>TLS to upstream</th><th></th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td><a href="in-domain.html"><strong>lists.example.org</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">mx.internal:25</td><td>4 addresses</td><td><span class="tag is-success is-light">required</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
<tr><td><a href="in-domain.html"><strong>lists.example.org</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">mx.internal:25</td><td>4 addresses</td><td><span class="tag is-success is-light">verified</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
||||||
<tr><td><a href="in-domain.html"><strong>acme.io</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">10.0.4.12:2525</td><td>any address</td><td><span class="tag is-light">opportunistic</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
<tr><td><a href="in-domain.html"><strong>acme.io</strong></a></td><td><span class="tag is-success is-light">MX</span></td><td class="sp-mono sp-muted">10.0.4.12:2525</td><td>any address</td><td><span class="tag is-light">opportunistic</span></td><td class="sp-actions"><a href="in-domain.html">Open</a><a class="has-text-danger" href="in-domain-delete.html">Delete</a></td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table></div>
|
</table></div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Waiting</h2><div class="sp-end">Refreshes every 10 s</div></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Waiting</h2><div class="sp-end">Refreshes on its own</div><a class="sp-help" href="help.html#inbound-queue" title="What waits here and why"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-empty"><i class="ti ti-stack-2"></i>Nothing is waiting. Every accepted message has reached its upstream.</div>
|
<div class="sp-empty"><i class="ti ti-stack-2"></i>Nothing is waiting. Every accepted message has reached its upstream.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,6 @@
|
|||||||
<h1 class="title is-3">No administrator yet</h1>
|
<h1 class="title is-3">No administrator yet</h1>
|
||||||
<p class="sp-muted">Open the one-time setup link printed in the container log to create one.</p>
|
<p class="sp-muted">Open the one-time setup link printed in the container log to create one.</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<!-- title: Sign in | shell: bare -->
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="overview.html">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Sign in</h1>
|
||||||
|
<div class="notification is-success is-light"><i class="ti ti-circle-check"></i>If that account has a confirmed e-mail, the link is on its way. It works once, for 30 minutes.</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="username">Username</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="username" name="username" autocomplete="username" required><span class="icon is-left"><i class="ti ti-user"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="password">Password</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
||||||
|
<p class="mt-4"><a href="login-reset.html">Forgot your password?</a></p>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<!-- title: New password | shell: bare -->
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="login.html">
|
||||||
|
<p class="sp-kicker">mail.example.org · admin</p>
|
||||||
|
<h1 class="title is-3">New password</h1>
|
||||||
|
<p class="sp-muted mb-4">Setting it signs every session of this account out; sign in with it afterwards.</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="new_password">New password</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="new_password" name="new_password" type="password" autocomplete="new-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="new_password_confirm">Repeat</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="new_password_confirm" name="new_password_confirm" type="password" autocomplete="new-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Set password</button></div>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!-- title: Reset password | shell: bare -->
|
||||||
|
<div class="columns is-gapless sp-signin">
|
||||||
|
<div class="column is-half sp-signin-brand">
|
||||||
|
<div>
|
||||||
|
<img src="../../selfpost-stamp.svg" alt="SelfPost">
|
||||||
|
<p>YOUR SMTP. YOUR RULES.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-half sp-signin-form">
|
||||||
|
<form class="sp-form" method="post" action="login-reset-sent.html">
|
||||||
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
|
<h1 class="title is-3">Reset password</h1>
|
||||||
|
<p class="sp-muted mb-4">A link to set a new password goes to the e-mail of the account, once that address is confirmed under Account. It works once, for 30 minutes.</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="username">Username</label>
|
||||||
|
<div class="control has-icons-left"><input class="input" id="username" name="username" autocomplete="username" required><span class="icon is-left"><i class="ti ti-user"></i></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Send the link</button></div>
|
||||||
|
<p class="mt-4"><a href="login.html">Back to sign in</a></p>
|
||||||
|
</form>
|
||||||
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
<div class="control has-icons-left"><input class="input" id="password" name="password" type="password" autocomplete="current-password" required><span class="icon is-left"><i class="ti ti-lock"></i></span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Sign in</button></div>
|
||||||
|
<p class="mt-4"><a href="login-reset.html">Forgot your password?</a></p>
|
||||||
</form>
|
</form>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="out-domains.html">Outbound domains</a> / <a href="out-domain.html">example.org</a> / Applications /</p>
|
<p class="sp-kicker"><a href="out-domains.html">Outbound domains</a> / <a href="out-domain.html">example.org</a> / Applications /</p>
|
||||||
<h1 class="title is-3 sp-mono">prod-server</h1>
|
<h1 class="title is-3 sp-mono">prod-server</h1>
|
||||||
<p class="sp-lead">1 102 messages in 30 days · peak 96 msg/h · average 1.5 msg/h. The same form, empty, adds a new application.</p>
|
<p class="sp-lead">1 102 messages in 30 days · peak 96 msg/h · average 1.5 msg/h.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button" href="out-app-created.html"><i class="ti ti-key"></i>New password</a>
|
<a class="button" href="out-app-created.html"><i class="ti ti-key"></i>New password</a>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<div class="field"><label class="label" for="login">Login</label>
|
<div class="field"><label class="label" for="login">Login</label>
|
||||||
<div class="control"><input class="input sp-mono" id="login" name="login" value="prod-server" autocomplete="off" spellcheck="false" required></div>
|
<div class="control"><input class="input sp-mono" id="login" name="login" value="prod-server" autocomplete="off" spellcheck="false" required></div>
|
||||||
<p class="help">Unique across domains; letters, digits, <code>.</code> <code>-</code> <code>_</code>.</p></div>
|
<p class="help">Fixed: add a new application to use another login.</p></div>
|
||||||
<div class="field"><label class="label" for="amode">May send as</label>
|
<div class="field"><label class="label" for="amode">May send as</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="amode" name="mode"><option selected>Any address of the domain</option><option>Specific addresses (list)</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="amode" name="mode"><option selected>Any address of the domain</option><option>Specific addresses (list)</option></select></div></div></div>
|
||||||
<div class="field"><label class="label" for="addresses">Addresses</label>
|
<div class="field"><label class="label" for="addresses">Addresses</label>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<ul class="sp-list-check">
|
<ul class="sp-list-check">
|
||||||
<li><i class="ti ti-x"></i>permanently delete its DKIM signing key;</li>
|
<li><i class="ti ti-x"></i>permanently delete its DKIM signing key;</li>
|
||||||
<li><i class="ti ti-x"></i>delete <strong>both applications</strong> (<span class="sp-mono">prod-server</span>, <span class="sp-mono">alerts</span>) with their credentials and sender bindings;</li>
|
<li><i class="ti ti-x"></i>delete <strong>both applications</strong> (<span class="sp-mono">prod-server</span>, <span class="sp-mono">alerts</span>) with their credentials and sender bindings;</li>
|
||||||
|
<li><i class="ti ti-x"></i>delete the <strong>12 DMARC reports</strong> received for it;</li>
|
||||||
<li><i class="ti ti-x"></i>stop signing mail as the domain.</li>
|
<li><i class="ti ti-x"></i>stop signing mail as the domain.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="sp-muted mb-4">The published DKIM record becomes invalid — remove it from DNS unless you plan to re-add the domain. Inbound domains are not touched.</p>
|
<p class="sp-muted mb-4">The published DKIM record becomes invalid — remove it from DNS unless you plan to re-add the domain. Inbound domains are not touched.</p>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<form class="sp-form" method="post" action="#">
|
<form class="sp-form" method="post" action="#">
|
||||||
<div class="field"><label class="label" for="mode">Limit mode</label>
|
<div class="field"><label class="label" for="mode">Limit mode</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="mode" name="mode"><option>Manual</option><option selected>Auto (from statistics)</option></select></div></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="mode" name="mode"><option>Manual</option><option selected>Auto (from statistics)</option></select></div></div></div>
|
||||||
<div class="field"><label class="label" for="mult">Multiplier (1.5–10, default 3)</label>
|
<div class="field"><label class="label" for="mult">Multiplier (1.5–5, default 2.5)</label>
|
||||||
<div class="control"><input class="input" id="mult" name="auto_multiplier" type="number" min="1.5" max="10" step="0.1" value="3"></div>
|
<div class="control"><input class="input" id="mult" name="auto_multiplier" type="number" min="1.5" max="10" step="0.1" value="3"></div>
|
||||||
<p class="help">Computed limit: <strong>288</strong> messages / 3600 s. Peak was 96 msg/h. Last recalculated yesterday 03:00.</p></div>
|
<p class="help">Computed limit: <strong>288</strong> messages / 3600 s. Peak was 96 msg/h. Last recalculated yesterday 03:00.</p></div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|||||||
@@ -28,9 +28,9 @@
|
|||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>History</h2><a class="sp-help" href="help.html#log" title="What the statuses mean"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">02</span><h2>History</h2><a class="sp-help" href="help.html#log" title="What the statuses mean"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<ol class="sp-timeline">
|
<ol class="sp-timeline">
|
||||||
<li><time>13:58:07</time><strong>Accepted</strong> from <span class="sp-mono">alerts</span> at 198.51.100.7</li>
|
<li><time>2026-09-21 13:58:07</time><strong>Accepted and queued</strong> — Postfix accepted the message over an authenticated submission and the journal-milter recorded it.</li>
|
||||||
<li class="sp-warn"><time>13:58:09</time><strong>Deferred</strong> — the receiving server asked to retry later</li>
|
<li class="sp-warn"><time>2026-09-21 13:58:09</time><strong>Deferred, will be retried</strong> — the receiving server could not take the message yet. Postfix retries: first after 5 minutes, then with increasing gaps up to 1 hour 7 minutes, for up to 5 days.</li>
|
||||||
<li class="sp-pending"><time>next attempt ≈ 14:08</time>Still in the <a href="out-queue.html">queue</a>; kept for 5 days, then bounced</li>
|
<li class="sp-pending"><time>not yet</time>Still in the queue; the next attempt is Postfix's own, and after 5 days it is bounced.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Outbound</p>
|
<p class="sp-kicker">Outbound</p>
|
||||||
<h1 class="title is-3">Outbound queue</h1>
|
<h1 class="title is-3">Outbound queue</h1>
|
||||||
<p class="sp-lead">Messages accepted from applications and still waiting for the receiving server. Refreshes every 10 s.</p>
|
<p class="sp-lead">Messages accepted from applications and still waiting for the receiving server. Refreshes on its own.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">mail.example.org</p>
|
<p class="sp-kicker">mail.example.org</p>
|
||||||
<h1 class="title is-3">Mail is flowing. One thing needs attention.</h1>
|
<h1 class="title is-3">Mail is flowing. One thing needs attention.</h1>
|
||||||
<p class="sp-lead">The TLS certificate expires in 12 days and has not renewed yet.</p>
|
<p class="sp-lead"><a href="health.html">TLS certificate</a>: expires in 12 days.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sp-muted sp-small">Updated 14:02:31<br>refreshes every 10 s</div>
|
<div class="sp-muted sp-small">Updated 14:02:31<br>refreshes on its own</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-server-cog"></i></span><span><span class="sp-h-name">Processes</span><span class="sp-h-value">5 of 5 running</span><span class="sp-h-sub">up 18 days</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-server-cog"></i></span><span><span class="sp-h-name">Processes</span><span class="sp-h-value">5 of 5 running</span><span class="sp-h-sub">up 18 days</span></span></a>
|
||||||
<a href="health.html" class="sp-warn"><span class="sp-h-icon"><i class="ti ti-certificate"></i></span><span><span class="sp-h-name">TLS certificate</span><span class="sp-h-value">Expires in 12 days</span><span class="sp-h-sub">renewal has not run</span></span></a>
|
<a href="health.html" class="sp-warn"><span class="sp-h-icon"><i class="ti ti-certificate"></i></span><span><span class="sp-h-name">TLS certificate</span><span class="sp-h-value">Expires in 12 days</span><span class="sp-h-sub">renewal has not run</span></span></a>
|
||||||
<a href="out-queue.html"><span class="sp-h-icon"><i class="ti ti-stack-2"></i></span><span><span class="sp-h-name">Queues</span><span class="sp-h-value">Both empty</span><span class="sp-h-sub">outbound and inbound</span></span></a>
|
<a href="out-queue.html"><span class="sp-h-icon"><i class="ti ti-stack-2"></i></span><span><span class="sp-h-name">Queues</span><span class="sp-h-value">Both empty</span><span class="sp-h-sub">outbound and inbound</span></span></a>
|
||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-plug-connected"></i></span><span><span class="sp-h-name">Milter sockets</span><span class="sp-h-value">5 of 5 answering</span><span class="sp-h-sub">incl. the spam filter</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-plug-connected"></i></span><span><span class="sp-h-name">Milter sockets</span><span class="sp-h-value">6 of 6 answering</span><span class="sp-h-sub">incl. the spam filter</span></span></a>
|
||||||
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-arrows-exchange"></i></span><span><span class="sp-h-name">Reverse DNS</span><span class="sp-h-value">Forward = reverse</span><span class="sp-h-sub">203.0.113.25</span></span></a>
|
<a href="health.html"><span class="sp-h-icon"><i class="ti ti-arrows-exchange"></i></span><span><span class="sp-h-name">Reverse DNS</span><span class="sp-h-value">Forward = reverse</span><span class="sp-h-sub">203.0.113.25</span></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
<!-- title: Preflight | section: server | page: preflight -->
|
|
||||||
<div class="sp-head">
|
|
||||||
<div class="sp-postmark sp-warn"><span>PREFLIGHT</span><b>WARN</b><span>1 OF 7 CHECKS</span></div>
|
|
||||||
<div class="sp-grow">
|
|
||||||
<p class="sp-kicker">Server</p>
|
|
||||||
<h1 class="title is-3">Preflight</h1>
|
|
||||||
<p class="sp-lead">Is this installation ready to deliver mail? Run it after the first start, after moving the server, or when receivers start refusing you. Health watches what runs; Preflight checks what the outside world sees.</p>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="#"><button type="submit" class="button"><i class="ti ti-refresh"></i>Run again</button></form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Checks</h2><div class="sp-end">Ran 14:02 · 3.1 s</div></div>
|
|
||||||
<div class="table-container"><table class="table is-fullwidth">
|
|
||||||
<thead><tr><th>Check</th><th>Result</th><th>Found</th><th>What to do</th></tr></thead>
|
|
||||||
<tbody>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Reverse DNS</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">203.0.113.25 ⇄ mail.example.org</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>TLS certificate</strong></td><td><span class="tag is-warning is-light">warn</span></td><td class="sp-small">Valid for <span class="sp-mono">mail.example.org</span>, expires in 12 days</td><td>Renewal has not run. Check that port 80 reaches the ACME client and look for <code>acme</code> in the <a href="system-log.html">system log</a>.</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Port 25</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">220 mail.example.org ESMTP</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Ports 465, 587</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">TLS handshake, 220 banner on both</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>HELO name</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">mail.example.org</td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>Proxy headers</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-small">Peer <span class="sp-mono">172.18.0.2</span> is trusted; <span class="sp-mono">X-Forwarded-Proto: https</span></td><td class="sp-muted">—</td></tr>
|
|
||||||
<tr><td class="sp-nowrap"><strong>DKIM signing</strong></td><td><span class="tag is-success is-light">ok</span></td><td class="sp-mono sp-small">inet:127.0.0.1:8891 answers</td><td class="sp-muted">—</td></tr>
|
|
||||||
</tbody>
|
|
||||||
</table></div>
|
|
||||||
<div class="sp-box-foot"><span>Ports are dialled from inside the container at the public address; a firewall that treats inside and outside differently can make this pass while the world is still blocked. SPF, DKIM and DMARC are per domain — each <a href=\"out-domains.html\">outbound domain</a> shows its own.</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<div class="sp-box-head"><span class="sp-no">02</span><h2>Send a test e-mail</h2><div class="sp-end">Confirms the relay accepted it, not that it arrived</div></div>
|
|
||||||
<div class="sp-box-body">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-6">
|
|
||||||
<form class="sp-form" method="post" action="#">
|
|
||||||
<div class="field"><label class="label" for="rcpt">Recipient</label>
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded has-icons-left"><input class="input" id="rcpt" name="recipient" type="email" placeholder="you@elsewhere.example" required><span class="icon is-left"><i class="ti ti-mail"></i></span></div>
|
|
||||||
<div class="control"><button type="submit" class="button is-primary"><i class="ti ti-send"></i>Send</button></div>
|
|
||||||
</div>
|
|
||||||
<p class="help">Sent from <span class="sp-mono">postmaster@mail.example.org</span> through the local Postfix.</p></div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="column is-6">
|
|
||||||
<dl class="sp-facts">
|
|
||||||
<div><dt>Last test</dt><dd>14:03 to <span class="sp-mono">mix@elsewhere.example</span></dd></div>
|
|
||||||
<div><dt>Result</dt><dd><span class="tag is-success is-light">queued</span> <span class="sp-mono sp-small">4XcC1d8Pq2z1</span></dd></div>
|
|
||||||
<div><dt>Then</dt><dd><a href="out-message.html">Follow it in the outbound log</a></dd></div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -62,6 +62,18 @@
|
|||||||
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=spf1 a -all"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=spf1 a -all"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
<p class="help">Notifications are sent without it, and a receiver may file them as spam.</p>
|
<p class="help">Notifications are sent without it, and a receiver may file them as spam.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sp-record">
|
||||||
|
<div class="sp-record-head"><h3>DMARC</h3><span class="tag is-warning is-light">missing</span><small>For the server's own name, not for your domains</small></div>
|
||||||
|
<div class="sp-row">
|
||||||
|
<div class="field"><label class="label">Host / name</label>
|
||||||
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="_dmarc.mail.example.org"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
|
<div class="field"><label class="label">Type</label><div class="control"><input class="input" readonly value="TXT"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="field"><label class="label">Value to publish</label>
|
||||||
|
<div class="field has-addons"><div class="control is-expanded"><input class="input" readonly value="v=DMARC1; p=reject"></div><div class="control"><button type="button" class="button"><i class="ti ti-copy"></i>Copy</button></div></div></div>
|
||||||
|
<p class="help">Without it a receiver does not refuse mail forged in the server's name - a false password-reset message included. Only the panel sends as this name, signed, so reject is safe.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box" id="rate-limits">
|
<div class="box" id="rate-limits">
|
||||||
@@ -80,7 +92,7 @@
|
|||||||
<dl class="sp-facts">
|
<dl class="sp-facts">
|
||||||
<div><dt>Zone</dt><dd class="sp-mono">Europe/Moscow</dd><dd class="sp-muted">UTC+03:00. Set in <code>.env</code> (<code>TZ</code>); restart to change.</dd></div>
|
<div><dt>Zone</dt><dd class="sp-mono">Europe/Moscow</dd><dd class="sp-muted">UTC+03:00. Set in <code>.env</code> (<code>TZ</code>); restart to change.</dd></div>
|
||||||
</dl>
|
</dl>
|
||||||
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>.</p>
|
<p class="mt-4 sp-muted">Every time the panel shows, and every day it counts - today on Health, the days of the statistics - is this zone's, and so are the stamps of <code>mail.log</code>. One exception: the day a DMARC report covers is the reporter's, in UTC.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Create administrator</button></div>
|
<div class="field mt-5"><button type="submit" class="button is-primary is-fullwidth">Create administrator</button></div>
|
||||||
</form>
|
</form>
|
||||||
<p class="sp-foot">© 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
<p class="sp-foot">Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Server</p>
|
<p class="sp-kicker">Server</p>
|
||||||
<h1 class="title is-3">System log</h1>
|
<h1 class="title is-3">System log</h1>
|
||||||
<p class="sp-lead">The tail of <code>mail.log</code>, the log Postfix writes. For one message, open it from a log page instead.</p>
|
<p class="sp-lead">The tail of <code>mail.log</code>, the log Postfix writes. For one message, open it from the <a href="out-log.html">Outbound log</a> instead.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Recent lines</h2><div class="sp-end">Last 200 · refreshes every 10 s</div></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Recent lines</h2><div class="sp-end">Last 200 lines · refreshes on its own</div></div>
|
||||||
<pre class="sp-log"><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=<m.keller@gmx.de>, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
|
<pre class="sp-log"><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=<m.keller@gmx.de>, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
|
||||||
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
|
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
|
||||||
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
|
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
<p class="mb-3">Deleting the panel user <strong>acme-ops</strong> will:</p>
|
<p class="mb-3">Deleting the panel user <strong>acme-ops</strong> will:</p>
|
||||||
<ul class="sp-list-check">
|
<ul class="sp-list-check">
|
||||||
<li><i class="ti ti-x"></i>end any signed-in session of this user at once;</li>
|
<li><i class="ti ti-x"></i>end any signed-in session of this user at once;</li>
|
||||||
<li><i class="ti ti-x"></i>remove the assignments to <strong>all outbound domains</strong> and to <strong>acme.io</strong> (inbound).</li>
|
<li><i class="ti ti-x"></i>remove the assignments to <strong>all outbound domains</strong> (outbound) and to <strong>acme.io</strong> (inbound).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="sp-muted mb-4">The domains, their applications and passwords, upstreams and recipients are not affected.</p>
|
<p class="sp-muted mb-4">Applications and their passwords, upstreams and recipients are not affected.</p>
|
||||||
<form method="post" action="users.html">
|
<form method="post" action="users.html">
|
||||||
|
<div class="field"><label class="label" for="acpw">Your account password</label><div class="control"><input class="input" id="acpw" name="account_password" type="password" autocomplete="current-password" required></div>
|
||||||
|
<p class="help">The one you sign in with: a session left open must not be enough to remove an administrator.</p></div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="button is-danger"><i class="ti ti-trash"></i>Delete acme-ops</button>
|
<button type="submit" class="button is-danger"><i class="ti ti-trash"></i>Delete acme-ops</button>
|
||||||
<a class="button is-text" href="user.html">Keep it</a>
|
<a class="button is-text" href="user.html">Keep it</a>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="users.html">Users</a> /</p>
|
<p class="sp-kicker"><a href="users.html">Users</a> /</p>
|
||||||
<h1 class="title is-3">acme-ops</h1>
|
<h1 class="title is-3">acme-ops</h1>
|
||||||
<p class="sp-lead">The same form, empty, creates a new user.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<a class="button is-danger is-light" href="user-delete.html"><i class="ti ti-trash"></i>Delete user</a>
|
<a class="button is-danger is-light" href="user-delete.html"><i class="ti ti-trash"></i>Delete user</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -15,13 +14,15 @@
|
|||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Account</h2><a class="sp-help" href="help.html#users" title="Roles and domain assignments"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Account</h2><a class="sp-help" href="help.html#users" title="Roles and domain assignments"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<div class="field"><label class="label" for="username">Username</label><div class="control"><input class="input" id="username" name="username" value="acme-ops" autocomplete="username" spellcheck="false" required></div></div>
|
<div class="field"><label class="label" for="username">Username</label><div class="control"><input class="input" id="username" name="username" value="acme-ops" autocomplete="username" spellcheck="false" required></div></div>
|
||||||
<div class="field"><label class="label" for="email">E-mail</label><div class="control"><input class="input" id="email" name="email" type="email" value="ops@acme.io" autocomplete="off"></div>
|
<div class="field"><label class="label" for="email">E-mail <span class="tag is-success is-light">confirmed</span></label><div class="control"><input class="input" id="email" name="email" type="email" value="ops@acme.io" autocomplete="off"></div>
|
||||||
<p class="help">For notifications and a password-reset link. The user can change it under Account.</p></div>
|
<p class="help">The user's own address. They can change it under Account. A new one is sent a link to confirm it; until it is confirmed no reset link goes to it.</p></div>
|
||||||
<div class="field"><label class="label" for="password">Password</label><div class="control"><input class="input" id="password" name="password" type="password" autocomplete="new-password"></div>
|
<div class="field"><label class="label" for="password">Password</label><div class="control"><input class="input" id="password" name="password" type="password" autocomplete="new-password"></div>
|
||||||
<p class="help">Leave empty to keep the current one.</p></div>
|
<p class="help">Leave empty to keep the current one.</p></div>
|
||||||
<div class="field"><label class="label" for="role">Role</label>
|
<div class="field"><label class="label" for="role">Role</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="role" name="role"><option selected>Domain — the domains assigned below</option><option>Global — the whole server</option></select></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="role" name="role"><option selected>Domain — the domains assigned below</option><option>Global — the whole server</option></select></div></div>
|
||||||
<p class="help">Everyone here is an administrator; the role is the reach. <em>Global</em> also sees Server and is the only one who adds or deletes domains. The only global user cannot be changed to <em>domain</em> or deleted.</p></div>
|
<p class="help">Everyone here is an administrator; the role is the reach. <em>Global</em> also sees Server and is the only one who adds or deletes domains. The only global user cannot be changed to <em>domain</em> or deleted.</p></div>
|
||||||
|
<div class="field"><label class="label" for="acpw">Your account password</label><div class="control"><input class="input" id="acpw" name="account_password" type="password" autocomplete="current-password"></div>
|
||||||
|
<p class="help">Yours, the one you sign in with, not this user's. Asked when you add a user, or change a password, an e-mail or a role here: a session left open must not be enough to hand out an account.</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +34,7 @@
|
|||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> example.org</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> example.org</label></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> shop.example.org</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> shop.example.org</label></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> notify.acme.io</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> notify.acme.io</label></div>
|
||||||
<p class="help">With <em>All</em> ticked the list below it is ignored — and greyed out where scripts run.</p>
|
<p class="help">With <em>All</em> ticked the single domains are ignored.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html" aria-current="page"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html" aria-current="page"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item is-active" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item is-active" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,19 +61,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html" aria-current="page"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html" aria-current="page"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker">Server</p>
|
<p class="sp-kicker">Server</p>
|
||||||
<h1 class="title is-3">System log</h1>
|
<h1 class="title is-3">System log</h1>
|
||||||
<p class="sp-lead">The tail of <code>mail.log</code>, the log Postfix writes. For one message, open it from a log page instead.</p>
|
<p class="sp-lead">The tail of <code>mail.log</code>, the log Postfix writes. For one message, open it from the <a href="out-log.html">Outbound log</a> instead.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Recent lines</h2><div class="sp-end">Last 200 · refreshes every 10 s</div></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Recent lines</h2><div class="sp-end">Last 200 lines · refreshes on its own</div></div>
|
||||||
<pre class="sp-log"><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=<m.keller@gmx.de>, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
|
<pre class="sp-log"><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/smtp[2131]: 4XcB9q0Lw2z1: to=<m.keller@gmx.de>, relay=mx00.gmx.net[212.227.15.9]:25, delay=0.8, status=sent (250 Requested mail action okay)
|
||||||
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
|
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:01:52</span> postfix/qmgr[58]: 4XcB9q0Lw2z1: removed
|
||||||
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
|
</span><span class="sp-ll"><span class="sp-d">Sep 21 14:00:18</span> postfix/smtpd[2129]: connect from mail.uni.example[192.0.2.77]
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
</span></pre>
|
</span></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -224,6 +224,12 @@ code { background: var(--sp-well); color: var(--sp-ink); border-radius: 3px; pad
|
|||||||
/* A label may carry a tag, the verdict on its value (the certificate's
|
/* A label may carry a tag, the verdict on its value (the certificate's
|
||||||
names against the hostname); it keeps the tag's own type. */
|
names against the hostname); it keeps the tag's own type. */
|
||||||
.sp-facts dt .tag { margin-left: .4rem; text-transform: none; letter-spacing: 0; vertical-align: 1px; }
|
.sp-facts dt .tag { margin-left: .4rem; text-transform: none; letter-spacing: 0; vertical-align: 1px; }
|
||||||
|
/* A tag beside a label's text (a field's or a fact's) is no taller than the
|
||||||
|
text's line: Bulma's tag is 2em high, which made the line taller and left
|
||||||
|
less room under the label than under one without a tag (an e-mail's
|
||||||
|
"not confirmed" on Account, the owner's phone, 2026-10-17). */
|
||||||
|
.label .tag, .sp-facts dt .tag { height: auto; padding-top: 0; padding-bottom: 0; line-height: 1.5; }
|
||||||
|
.label .tag { margin-left: .25rem; vertical-align: 1px; }
|
||||||
/* A fact that takes the whole row: a line a server wrote, read as one. */
|
/* A fact that takes the whole row: a line a server wrote, read as one. */
|
||||||
.sp-facts .sp-wide { grid-column: 1 / -1; }
|
.sp-facts .sp-wide { grid-column: 1 / -1; }
|
||||||
.sp-facts dd.sp-big { font-size: 1.5rem; font-weight: 600; line-height: 1.2; }
|
.sp-facts dd.sp-big { font-size: 1.5rem; font-weight: 600; line-height: 1.2; }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
@@ -80,10 +79,12 @@
|
|||||||
<p class="mb-3">Deleting the panel user <strong>acme-ops</strong> will:</p>
|
<p class="mb-3">Deleting the panel user <strong>acme-ops</strong> will:</p>
|
||||||
<ul class="sp-list-check">
|
<ul class="sp-list-check">
|
||||||
<li><i class="ti ti-x"></i>end any signed-in session of this user at once;</li>
|
<li><i class="ti ti-x"></i>end any signed-in session of this user at once;</li>
|
||||||
<li><i class="ti ti-x"></i>remove the assignments to <strong>all outbound domains</strong> and to <strong>acme.io</strong> (inbound).</li>
|
<li><i class="ti ti-x"></i>remove the assignments to <strong>all outbound domains</strong> (outbound) and to <strong>acme.io</strong> (inbound).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="sp-muted mb-4">The domains, their applications and passwords, upstreams and recipients are not affected.</p>
|
<p class="sp-muted mb-4">Applications and their passwords, upstreams and recipients are not affected.</p>
|
||||||
<form method="post" action="users.html">
|
<form method="post" action="users.html">
|
||||||
|
<div class="field"><label class="label" for="acpw">Your account password</label><div class="control"><input class="input" id="acpw" name="account_password" type="password" autocomplete="current-password" required></div>
|
||||||
|
<p class="help">The one you sign in with: a session left open must not be enough to remove an administrator.</p></div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="button is-danger"><i class="ti ti-trash"></i>Delete acme-ops</button>
|
<button type="submit" class="button is-danger"><i class="ti ti-trash"></i>Delete acme-ops</button>
|
||||||
<a class="button is-text" href="user.html">Keep it</a>
|
<a class="button is-text" href="user.html">Keep it</a>
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,14 +61,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
<div class="sp-grow">
|
<div class="sp-grow">
|
||||||
<p class="sp-kicker"><a href="users.html">Users</a> /</p>
|
<p class="sp-kicker"><a href="users.html">Users</a> /</p>
|
||||||
<h1 class="title is-3">acme-ops</h1>
|
<h1 class="title is-3">acme-ops</h1>
|
||||||
<p class="sp-lead">The same form, empty, creates a new user.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<a class="button is-danger is-light" href="user-delete.html"><i class="ti ti-trash"></i>Delete user</a>
|
<a class="button is-danger is-light" href="user-delete.html"><i class="ti ti-trash"></i>Delete user</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,13 +79,15 @@
|
|||||||
<div class="sp-box-head"><span class="sp-no">01</span><h2>Account</h2><a class="sp-help" href="help.html#users" title="Roles and domain assignments"><i class="ti ti-help-circle"></i></a></div>
|
<div class="sp-box-head"><span class="sp-no">01</span><h2>Account</h2><a class="sp-help" href="help.html#users" title="Roles and domain assignments"><i class="ti ti-help-circle"></i></a></div>
|
||||||
<div class="sp-box-body">
|
<div class="sp-box-body">
|
||||||
<div class="field"><label class="label" for="username">Username</label><div class="control"><input class="input" id="username" name="username" value="acme-ops" autocomplete="username" spellcheck="false" required></div></div>
|
<div class="field"><label class="label" for="username">Username</label><div class="control"><input class="input" id="username" name="username" value="acme-ops" autocomplete="username" spellcheck="false" required></div></div>
|
||||||
<div class="field"><label class="label" for="email">E-mail</label><div class="control"><input class="input" id="email" name="email" type="email" value="ops@acme.io" autocomplete="off"></div>
|
<div class="field"><label class="label" for="email">E-mail <span class="tag is-success is-light">confirmed</span></label><div class="control"><input class="input" id="email" name="email" type="email" value="ops@acme.io" autocomplete="off"></div>
|
||||||
<p class="help">For notifications and a password-reset link. The user can change it under Account.</p></div>
|
<p class="help">The user's own address. They can change it under Account. A new one is sent a link to confirm it; until it is confirmed no reset link goes to it.</p></div>
|
||||||
<div class="field"><label class="label" for="password">Password</label><div class="control"><input class="input" id="password" name="password" type="password" autocomplete="new-password"></div>
|
<div class="field"><label class="label" for="password">Password</label><div class="control"><input class="input" id="password" name="password" type="password" autocomplete="new-password"></div>
|
||||||
<p class="help">Leave empty to keep the current one.</p></div>
|
<p class="help">Leave empty to keep the current one.</p></div>
|
||||||
<div class="field"><label class="label" for="role">Role</label>
|
<div class="field"><label class="label" for="role">Role</label>
|
||||||
<div class="control"><div class="select is-fullwidth"><select id="role" name="role"><option selected>Domain — the domains assigned below</option><option>Global — the whole server</option></select></div></div>
|
<div class="control"><div class="select is-fullwidth"><select id="role" name="role"><option selected>Domain — the domains assigned below</option><option>Global — the whole server</option></select></div></div>
|
||||||
<p class="help">Everyone here is an administrator; the role is the reach. <em>Global</em> also sees Server and is the only one who adds or deletes domains. The only global user cannot be changed to <em>domain</em> or deleted.</p></div>
|
<p class="help">Everyone here is an administrator; the role is the reach. <em>Global</em> also sees Server and is the only one who adds or deletes domains. The only global user cannot be changed to <em>domain</em> or deleted.</p></div>
|
||||||
|
<div class="field"><label class="label" for="acpw">Your account password</label><div class="control"><input class="input" id="acpw" name="account_password" type="password" autocomplete="current-password"></div>
|
||||||
|
<p class="help">Yours, the one you sign in with, not this user's. Asked when you add a user, or change a password, an e-mail or a role here: a session left open must not be enough to hand out an account.</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> example.org</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> example.org</label></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> shop.example.org</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> shop.example.org</label></div>
|
||||||
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> notify.acme.io</label></div>
|
<div class="field"><label class="checkbox"><input type="checkbox" name="domain_ids" checked disabled> notify.acme.io</label></div>
|
||||||
<p class="help">With <em>All</em> ticked the list below it is ignored — and greyed out where scripts run.</p>
|
<p class="help">With <em>All</em> ticked the single domains are ignored.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="navbar is-primary" aria-label="main navigation">
|
<nav class="navbar is-primary" aria-label="main navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
<div class="navbar-brand"><a class="navbar-item" href="overview.html"><img src="../../selfpost-wordmark.svg" alt="SelfPost"></a><details class="sp-touchnav"><summary>Menu</summary><div class="sp-touchnav-panel"><a href="overview.html">Overview</a><details class="sp-touchnav-group"><summary>Outbound</summary><a href="out-domains.html"><i class="ti ti-world-upload"></i>Domains</a><a href="out-log.html"><i class="ti ti-list-details"></i>Log</a><a href="out-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="dmarc.html"><i class="ti ti-report-analytics"></i>DMARC reports</a></details><details class="sp-touchnav-group"><summary>Inbound</summary><a href="in-domains.html"><i class="ti ti-world-download"></i>Domains</a><a href="in-log.html"><i class="ti ti-list-details"></i>Log</a><a href="in-queue.html"><i class="ti ti-stack-2"></i>Queue</a><a href="in-quarantine.html"><i class="ti ti-mail-pause"></i>Quarantine</a><a href="in-filter-lists.html"><i class="ti ti-filter"></i>Filter lists</a></details><details class="sp-touchnav-group" open><summary>Server</summary><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></details><details class="sp-touchnav-group"><summary><i class="ti ti-user-circle"></i>admin</summary><a href="account.html"><i class="ti ti-key"></i>Account</a><a href="help.html"><i class="ti ti-help"></i>Help</a><form method="post" action="login.html"><button type="submit"><i class="ti ti-logout"></i>Sign out</button></form></details></div></details></div>
|
||||||
<div class="navbar-menu">
|
<div class="navbar-menu">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a class="navbar-item" href="overview.html">Overview</a>
|
<a class="navbar-item" href="overview.html">Overview</a>
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
<a class="navbar-link" href="health.html">Server</a>
|
<a class="navbar-link" href="health.html">Server</a>
|
||||||
<div class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
<a class="navbar-item" href="health.html"><i class="ti ti-heartbeat"></i>Health</a>
|
||||||
<a class="navbar-item" href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a>
|
|
||||||
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
<a class="navbar-item" href="system-log.html"><i class="ti ti-file-text"></i>System log</a>
|
||||||
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
<a class="navbar-item" href="backup.html"><i class="ti ti-archive"></i>Backup</a>
|
||||||
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
<a class="navbar-item is-active" href="users.html"><i class="ti ti-users"></i>Users</a>
|
||||||
@@ -62,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sp-perf" aria-hidden="true"></div>
|
<div class="sp-perf" aria-hidden="true"></div>
|
||||||
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="preflight.html"><i class="ti ti-checklist"></i>Preflight</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
<div class="sp-subnav"><div class="container"><a href="health.html"><i class="ti ti-heartbeat"></i>Health</a><a href="system-log.html"><i class="ti ti-file-text"></i>System log</a><a href="backup.html"><i class="ti ti-archive"></i>Backup</a><a href="users.html" aria-current="page"><i class="ti ti-users"></i>Users</a><a href="settings.html"><i class="ti ti-settings"></i>Settings</a></div></div>
|
||||||
<main>
|
<main>
|
||||||
<div class="container sp-page">
|
<div class="container sp-page">
|
||||||
<div class="sp-head">
|
<div class="sp-head">
|
||||||
@@ -88,7 +87,7 @@
|
|||||||
</table></div>
|
</table></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="sp-foot">SelfPost 1.8.0 · © 2026 Mix · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
<footer class="sp-foot">SelfPost 1.8.0 · Copyright © 2026 Mikhail Yenuchenko · <a href="#">License (AGPL-3.0)</a> · <a href="#">Source</a> · No warranty</footer>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# What SelfPost assumes of other programs - the register of 2026-10-14
|
# What SelfPost assumes of other programs - the register of 2026-10-14
|
||||||
|
|
||||||
|
> **Closed 2026-10-14** (stage 2c of [audit-2026-10-12](audit-2026-10-12.md),
|
||||||
|
> shipped `2.5.2`): every entry answered, the citations written into the
|
||||||
|
> code, what the sources contradicted fixed. The file stays because the code
|
||||||
|
> names its entries (`assumptions C-V5`, `A-43`); its `file:line` references
|
||||||
|
> are of the tree at `0f984ff`, not of today's, and OpenDKIM, named
|
||||||
|
> throughout, left the image in `2.8.0`.
|
||||||
|
|
||||||
The heuristics review of audit-2026-10-12, stage 2c. The owner's rule
|
The heuristics review of audit-2026-10-12, stage 2c. The owner's rule
|
||||||
(development.md § "Code that relies on another component's behaviour"):
|
(development.md § "Code that relies on another component's behaviour"):
|
||||||
what Postfix, OpenDKIM, Cyrus SASL, rspamd, supervisord, logrotate, Docker,
|
what Postfix, OpenDKIM, Cyrus SASL, rspamd, supervisord, logrotate, Docker,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# Code audit — 2026-10-12
|
# Code audit — 2026-10-12
|
||||||
|
|
||||||
|
> **Closed 2026-10-15.** Every stage shipped (`2.4.2` to `2.6.0`; stage 6,
|
||||||
|
> documents and tests, without a cut). The file stays because the code cites
|
||||||
|
> its finding ids (`H4`, `M2`, `L9`...); its `file:line` references are of the
|
||||||
|
> tree at `8f226bd`, not of today's.
|
||||||
|
|
||||||
**What this is.** A full-tree audit of the code and infrastructure at `8f226bd`
|
**What this is.** A full-tree audit of the code and infrastructure at `8f226bd`
|
||||||
(release `2.4.0`): leftovers, unfinished work, defects, performance, doc drift
|
(release `2.4.0`): leftovers, unfinished work, defects, performance, doc drift
|
||||||
and test gaps — explicit ones (there are no `TODO`/`FIXME` markers anywhere in
|
and test gaps — explicit ones (there are no `TODO`/`FIXME` markers anywhere in
|
||||||
@@ -1570,7 +1575,7 @@ tested (the count and the spared are).
|
|||||||
sleeps said where they are); roadmap.md's closing paragraph. Already right
|
sleeps said where they are); roadmap.md's closing paragraph. Already right
|
||||||
by then: the `.alive` mark, `dmarc-ingest` as a second writer, the chain
|
by then: the `.alive` mark, `dmarc-ingest` as a second writer, the chain
|
||||||
head, M1's wording, the version stamp.
|
head, M1's wording, the version stamp.
|
||||||
- [ ] roadmap.md: done ahead on 2026-10-13/14 with the owner -
|
- [x] roadmap.md: done ahead on 2026-10-13/14 with the owner -
|
||||||
`template-data-typing` struck, `structured-logging` dropped,
|
`template-data-typing` struck, `structured-logging` dropped,
|
||||||
`review-2026-08-followups` closed with each row placed in a stage or
|
`review-2026-08-followups` closed with each row placed in a stage or
|
||||||
dropped with its reason. What is left: check that every *Test gaps* entry
|
dropped with its reason. What is left: check that every *Test gaps* entry
|
||||||
|
|||||||
+46
-54
@@ -1,7 +1,7 @@
|
|||||||
# SelfPost — development
|
# SelfPost — development
|
||||||
|
|
||||||
**What this file is.** How to build, test, document, and ship changes. Open
|
**What this file is.** How to build, test, document, and ship changes. Open
|
||||||
work after 1.0 (1.x+) lives in [roadmap.md](roadmap.md) and linked
|
work lives in [roadmap.md](roadmap.md) and linked
|
||||||
[plans/](plans/). Product boundaries: [product.md](product.md). As-built layout:
|
[plans/](plans/). Product boundaries: [product.md](product.md). As-built layout:
|
||||||
[architecture.md](architecture.md).
|
[architecture.md](architecture.md).
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ checklist changes; update the numerator when steps are checked off.
|
|||||||
|
|
||||||
### Roadmap track: commit and version
|
### Roadmap track: commit and version
|
||||||
|
|
||||||
For the current 1.x+ roadmap queue ([roadmap.md](roadmap.md) index), stricter
|
For the roadmap queue ([roadmap.md](roadmap.md) index), stricter
|
||||||
than the default «meaningful step» rule in [§ Commits and release build](#commits-and-release-build):
|
than the default «meaningful step» rule in [§ Commits and release build](#commits-and-release-build):
|
||||||
|
|
||||||
**After each checklist step**
|
**After each checklist step**
|
||||||
@@ -106,50 +106,10 @@ than the default «meaningful step» rule in [§ Commits and release build](#com
|
|||||||
[deploy/docker-compose.yml](../deploy/docker-compose.yml).
|
[deploy/docker-compose.yml](../deploy/docker-compose.yml).
|
||||||
2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see
|
2. Git tag `vX.Y.Z` and publish the GitHub Release only on explicit request (see
|
||||||
[§ Release image](#release-image)).
|
[§ Release image](#release-image)).
|
||||||
|
3. The item leaves [roadmap.md](roadmap.md) and its plan file is deleted; what
|
||||||
**Planned version cuts** (from pin `2.13.0`; adjust if semver changes mid-track):
|
it did is in the CHANGELOG, the rest in git history. A report whose
|
||||||
|
finding ids the code cites (the audits, the assumptions register) stays,
|
||||||
| Stage | ID | Cut |
|
marked closed.
|
||||||
|---|---|---|
|
|
||||||
| 1 | queue-retries | `1.3.1` (PATCH) |
|
|
||||||
| 2 | inbound-relay | `1.4.0` |
|
|
||||||
| 3 | send-log-retention | `1.5.0` |
|
|
||||||
| 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 | 2026-08-19 code review pass | `1.9.2` (PATCH) |
|
|
||||||
| 10 | panel-redesign prep (docs only, owner-requested cut) | `1.9.3` (PATCH) |
|
|
||||||
| 11 | panel-redesign plan: component kit, stages, reorder (docs only, owner-requested cut) | `1.9.4` (PATCH) |
|
|
||||||
| 12 | GHCR release fix: keep per-arch images, sweep orphaned versions (CI only) | `1.9.5` (PATCH) |
|
|
||||||
| 13 | panel-redesign | `2.0.0` (MAJOR, no compatibility) |
|
|
||||||
| 14 | DMARC ingest drops and bounds, full backup from the panel, no queue in a backup | `2.0.1` (PATCH) |
|
|
||||||
| 15 | inbound-antispam-panel | `2.1.0` (was `1.10.0`, reordered 2026-10-09) |
|
|
||||||
| 16 | delivery-log-storage | `2.2.0` |
|
|
||||||
| 17 | panel-notifications | `2.3.0` |
|
|
||||||
| 18 | from-header-binding | `2.4.0` |
|
|
||||||
| 19 | code audit 2026-10-12 and its plan (docs only, owner-requested cut) | `2.4.1` (PATCH) |
|
|
||||||
| 20 | audit-2026-10-12 stage 0: release hygiene | `2.4.2` (PATCH) |
|
|
||||||
| 21 | audit-2026-10-12 stages 1 and 2 | `2.5.0` |
|
|
||||||
| 22 | audit-2026-10-12 stage 2b: the log tailer | `2.5.1` |
|
|
||||||
| 23 | audit-2026-10-12 stage 2c: the sender check reads From: as OpenDKIM does | `2.5.2` |
|
|
||||||
| 24 | audit-2026-10-12 stage 3: persistence and restore | `2.5.3` |
|
|
||||||
| 25 | audit-2026-10-12 stage 4: panel behaviour | `2.5.4` |
|
|
||||||
| 26 | audit-2026-10-12 stage 5: image and Postfix hardening | `2.6.0` |
|
|
||||||
| 27 | port25-bare-newline | `2.6.1` (PATCH) |
|
|
||||||
| 28 | base-trixie | `2.7.0` |
|
|
||||||
| 29 | panel-dkim-signing | `2.8.0` |
|
|
||||||
| 30 | dns-record-collapse, settings-save-row, notification-mail-recheck; `/data/opendkim` removed | `2.9.0` |
|
|
||||||
| 31 | log panes wrap on a phone | `2.9.1` (PATCH) |
|
|
||||||
| 32 | inbound-upstream-result | `2.10.0` |
|
|
||||||
| 33 | long, non-repeating queue ids | `2.10.1` (PATCH) |
|
|
||||||
| 34 | phone-desktop-mode | `2.10.2` (PATCH) |
|
|
||||||
| 35 | backup-reauth | `2.10.3` (PATCH) |
|
|
||||||
| 36 | origin-strict | `2.10.4` (PATCH) |
|
|
||||||
| 37 | health-setup-checks | `2.11.0` |
|
|
||||||
| 38 | test-email | `2.12.0` |
|
|
||||||
| 39 | local-time | `2.13.0` |
|
|
||||||
|
|
||||||
Docs-only prep (checklists in plans, no product code) uses the same per-step
|
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.
|
commit rule but **no** version cut until the next product stage ships.
|
||||||
@@ -275,12 +235,33 @@ re-read what changed, update the citations, then this table:
|
|||||||
| `logrotate` | `3.22.0-1` |
|
| `logrotate` | `3.22.0-1` |
|
||||||
<!-- /cited-package-versions -->
|
<!-- /cited-package-versions -->
|
||||||
|
|
||||||
|
A security update of one of these, or of OpenSSL or Berkeley DB, reaches
|
||||||
|
operators only with the next release: published tags are never rebuilt.
|
||||||
|
`.github/workflows/base-packages.yml` compares every Monday what
|
||||||
|
`debian:trixie-slim` would install with the versions the last release was
|
||||||
|
built with, below, and fails when one has moved - the sign to cut a patch
|
||||||
|
release. Cutting a release updates this table from the image
|
||||||
|
(`dpkg-query -W` of these names; security audit 2026-10-17, I5):
|
||||||
|
|
||||||
|
<!-- base-package-versions -->
|
||||||
|
| Package | Built with |
|
||||||
|
|---|---|
|
||||||
|
| `postfix` | `3.10.13-0+deb13u1` |
|
||||||
|
| `libsasl2-2` | `2.1.28+dfsg1-9` |
|
||||||
|
| `libsasl2-modules-db` | `2.1.28+dfsg1-9` |
|
||||||
|
| `sasl2-bin` | `2.1.28+dfsg1-9` |
|
||||||
|
| `openssl` | `3.5.7-1~deb13u3` |
|
||||||
|
| `libssl3t64` | `3.5.7-1~deb13u3` |
|
||||||
|
| `libdb5.3t64` | `5.3.28+dfsg2-9` |
|
||||||
|
<!-- /base-package-versions -->
|
||||||
|
|
||||||
A document instead of source: its URL and section. Where neither answers the
|
A document instead of source: its URL and section. Where neither answers the
|
||||||
question, the comment says so and what was measured instead - "measured on
|
question, the comment says so and what was measured instead - "measured on
|
||||||
the e2e stand, 2026-10-14: ..." - and a measurement is never written as if
|
the e2e stand, 2026-10-14: ..." - and a measurement is never written as if
|
||||||
it were read. A test fixture copied from a real log says where and when it
|
it were read. A test fixture copied from a real log says where and when it
|
||||||
was seen; one composed by hand says that it was composed. The heuristics
|
was seen; one composed by hand says that it was composed. The heuristics
|
||||||
review of 2026-10-14 (audit-2026-10-12, stage 2c) brought the code that
|
review of 2026-10-14 ([audit-2026-10-12](audit-2026-10-12.md), stage 2c; the
|
||||||
|
[assumptions register](assumptions-2026-10-14.md)) brought the code that
|
||||||
existed then up to this rule.
|
existed then up to this rule.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -470,6 +451,15 @@ that changes the mockups, a guard or the script touches nothing else under
|
|||||||
[plans/panel-redesign.md](plans/panel-redesign.md) § The contract and
|
[plans/panel-redesign.md](plans/panel-redesign.md) § The contract and
|
||||||
§ Enforcement have the full rules.
|
§ Enforcement have the full rules.
|
||||||
|
|
||||||
|
**A drawn screen that is not built has a row.** A screen the mockups draw and
|
||||||
|
the panel does not build, or builds otherwise (the guards' `deferredScreens`
|
||||||
|
and `outlineExempt`), is named in [roadmap.md](roadmap.md) - a row of the index,
|
||||||
|
or an idea under *Ideas* - in the same commit that exempts it.
|
||||||
|
`TestUnbuiltScreensAreOnTheRoadmap` fails otherwise. The queue as a table and
|
||||||
|
the Inbound queue were drawn for 2.0, deferred on 2026-10-09 with no row of
|
||||||
|
their own, and then forgotten for nine releases, the guards green because they
|
||||||
|
had been told to be (owner, 2026-10-18: "Фиаско"); built in `2.16.0`.
|
||||||
|
|
||||||
### End-to-end (container suite)
|
### End-to-end (container suite)
|
||||||
|
|
||||||
Separate Go module `test/e2e/`; **not** included in the main module's
|
Separate Go module `test/e2e/`; **not** included in the main module's
|
||||||
@@ -483,7 +473,8 @@ make e2e
|
|||||||
**Stack:** [deploy/docker-compose.yml](../deploy/docker-compose.yml) +
|
**Stack:** [deploy/docker-compose.yml](../deploy/docker-compose.yml) +
|
||||||
[test/e2e/compose.override.yml](../test/e2e/compose.override.yml) — same
|
[test/e2e/compose.override.yml](../test/e2e/compose.override.yml) — same
|
||||||
`cap_drop`/`cap_add`/`no-new-privileges` as production. Override: high ports
|
`cap_drop`/`cap_add`/`no-new-privileges` as production. Override: high ports
|
||||||
(`20465`/`20587`/`20080`), test hostname, self-signed TLS,
|
(`20465`/`20587`/`20080`, port 25 at `20025`, the stand's CoreDNS at
|
||||||
|
`20053/udp`), test hostname, self-signed TLS,
|
||||||
`PANEL_COOKIE_SECURE=false`, isolated compose project, and the image's
|
`PANEL_COOKIE_SECURE=false`, isolated compose project, and the image's
|
||||||
`VERSION` from `SELFPOST_E2E_VERSION` (unset locally: `e2e`). Mail is hermetic:
|
`VERSION` from `SELFPOST_E2E_VERSION` (unset locally: `e2e`). Mail is hermetic:
|
||||||
CoreDNS (fake zone) + Postfix `smtp-sink` as sink-MX; DKIM TXT is scraped from
|
CoreDNS (fake zone) + Postfix `smtp-sink` as sink-MX; DKIM TXT is scraped from
|
||||||
@@ -536,10 +527,10 @@ Workflows in [.github/workflows/](../.github/workflows/). What each job runs —
|
|||||||
### `test.yml` — every push and PR to `main`
|
### `test.yml` — every push and PR to `main`
|
||||||
|
|
||||||
`design-first` (needs the full history) → `gofmt -l` → `go vet ./...` →
|
`design-first` (needs the full history) → `gofmt -l` → `go vet ./...` →
|
||||||
`go test ./...` (main module, no e2e; includes the panel guard tests, see
|
Staticcheck 2026.1 → `go test ./...` (main module, no e2e; includes the panel guard tests, see
|
||||||
[§ Panel pages](#panel-pages-the-component-kit-and-its-guards)) → the e2e
|
[§ Panel pages](#panel-pages-the-component-kit-and-its-guards)) → the e2e
|
||||||
module compiled, not run (`go vet ./...` and `go test -c -o /dev/null .` in
|
module compiled, not run (`go vet ./...`, Staticcheck and
|
||||||
`test/e2e`).
|
`go test -c -o /dev/null .` in `test/e2e`).
|
||||||
|
|
||||||
### `release.yml` — published GitHub Release, or `workflow_dispatch` with SemVer
|
### `release.yml` — published GitHub Release, or `workflow_dispatch` with SemVer
|
||||||
|
|
||||||
@@ -594,7 +585,8 @@ There is no `docs/archive/` directory.
|
|||||||
| Development process (this file) | [development.md](development.md) |
|
| Development process (this file) | [development.md](development.md) |
|
||||||
| Security requirements and accepted risks | [security.md](security.md) |
|
| Security requirements and accepted risks | [security.md](security.md) |
|
||||||
| SQLite schema and migrations | [schema-migrations.md](schema-migrations.md) |
|
| SQLite schema and migrations | [schema-migrations.md](schema-migrations.md) |
|
||||||
| Roadmap (1.x+) | [roadmap.md](roadmap.md) |
|
| Roadmap | [roadmap.md](roadmap.md) |
|
||||||
|
| Closed audits, cited by finding id from the code | [audit-2026-10-12.md](audit-2026-10-12.md), [assumptions-2026-10-14.md](assumptions-2026-10-14.md), [security-audit-2026-10-17.md](security-audit-2026-10-17.md) |
|
||||||
| Active design plans | [plans/](plans/) |
|
| Active design plans | [plans/](plans/) |
|
||||||
| Release history | [CHANGELOG.md](../CHANGELOG.md) |
|
| Release history | [CHANGELOG.md](../CHANGELOG.md) |
|
||||||
|
|
||||||
@@ -611,8 +603,8 @@ There is no `docs/archive/` directory.
|
|||||||
| [deploy/.env.example](../deploy/.env.example) | Public env template; full reference in [guide.md](guide.md) |
|
| [deploy/.env.example](../deploy/.env.example) | Public env template; full reference in [guide.md](guide.md) |
|
||||||
| [CHANGELOG.md](../CHANGELOG.md) | Keep a Changelog |
|
| [CHANGELOG.md](../CHANGELOG.md) | Keep a Changelog |
|
||||||
|
|
||||||
Out of scope for v1.x: `CONTRIBUTING.md`, man pages, a separate docs site
|
Out of scope: `CONTRIBUTING.md` (dropped, with its condition, in
|
||||||
(candidates in [roadmap.md](roadmap.md)).
|
[roadmap.md](roadmap.md)), man pages, a separate docs site.
|
||||||
|
|
||||||
### Maintaining documentation
|
### Maintaining documentation
|
||||||
|
|
||||||
|
|||||||
+304
-73
@@ -19,6 +19,7 @@ changed (see the CHANGELOG, *Removed*).
|
|||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
|
- [Machine requirements](#machine-requirements)
|
||||||
- [Ports](#ports)
|
- [Ports](#ports)
|
||||||
- [Local trial](#local-trial)
|
- [Local trial](#local-trial)
|
||||||
- [Initial setup](#initial-setup)
|
- [Initial setup](#initial-setup)
|
||||||
@@ -56,6 +57,98 @@ changed (see the CHANGELOG, *Removed*).
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Machine requirements
|
||||||
|
|
||||||
|
What SelfPost needs of the host depends on what is switched on. The options
|
||||||
|
that change it are independent of each other: port 587 costs nothing until a
|
||||||
|
client uses it; port 25 is one listener whether the inbound relay, DMARC
|
||||||
|
reports or both open it; the spam filter is a second container, which
|
||||||
|
needs port 25 (an rspamd elsewhere, given by `INBOUND_ANTISPAM_MILTER`,
|
||||||
|
costs this host nothing); and the reverse proxy is either a program on the
|
||||||
|
host or one of the containers under `deploy/`. The figures were measured on
|
||||||
|
the test installation on 2026-10-19 - SelfPost 2.16.0, a host of 2 vCPU and
|
||||||
|
4 GB - at rest, under load made for the purpose (SMTP sessions to port 25
|
||||||
|
held open; messages given to rspamd to scan, nothing delivered), and, for
|
||||||
|
the build, in a container held to a given memory and one CPU.
|
||||||
|
|
||||||
|
**Running.** What each part uses, measured:
|
||||||
|
|
||||||
|
| Part | At rest | Under load |
|
||||||
|
|---|---|---|
|
||||||
|
| SelfPost: the panel, Postfix, supervisord | 55-60 MB (the panel and supervisord about 30 MB each, Postfix's daemons 6-12 MB each, mostly shared libraries) | 123 MB with 20 SMTP sessions open at once - about 3 MB a session |
|
||||||
|
| The spam filter: rspamd, five processes | 118 MB | 153 MB scanning ten 100 KB messages at once; 232 MB scanning one 25 MiB message; 424 MB scanning three of them at once |
|
||||||
|
| A reverse-proxy container from `deploy/` | Caddy 10 MB, nginx 3 MB, Traefik 21 MB | not measured |
|
||||||
|
|
||||||
|
rspamd sees only mail on port 25, and a message there may be as large as
|
||||||
|
`INBOUND_MESSAGE_SIZE_LIMIT` (25 MiB by default): each such message rspamd
|
||||||
|
is scanning adds about 100 MB while it does. That, not SelfPost, is what
|
||||||
|
sets the memory a host with the spam filter needs.
|
||||||
|
|
||||||
|
The host's own system and Docker come on top. They were not measured on a
|
||||||
|
fresh host, and the table below allows them 300 MB; on the test server
|
||||||
|
`systemd-journald` and `containerd` took about 120 MB, and Docker's daemon
|
||||||
|
had grown to 600 MB after a week of image builds for three projects - prune
|
||||||
|
the build cache (below) and it stays smaller.
|
||||||
|
|
||||||
|
| Switched on | Host memory, at least | Images on disk |
|
||||||
|
|---|---|---|
|
||||||
|
| Outbound only (465, and 587 if enabled), with or without port 25 | **512 MB**: SelfPost's 123 MB under load and the allowance | 315 MB |
|
||||||
|
| With the spam filter, messages on port 25 kept small (`INBOUND_MESSAGE_SIZE_LIMIT` of a few MiB) or rarely large | **768 MB**: 123 + 153 MB under load and the allowance, with room for one large message (+100 MB) | 626 MB |
|
||||||
|
| With the spam filter and the default 25 MiB, several large messages at once | **1 GB**: 123 + 424 MB and the allowance | 626 MB |
|
||||||
|
| A reverse proxy as a container | the proxy's own (3-21 MB at rest) on top | Caddy 92 MB, nginx 280 MB, Traefik 231 MB |
|
||||||
|
|
||||||
|
512 MB is not enough with the spam filter: at rest SelfPost, rspamd and the
|
||||||
|
allowance come to about 480 MB, and one large message is another 100 MB.
|
||||||
|
Without swap the kernel then kills a process - the panel, Postfix or rspamd.
|
||||||
|
|
||||||
|
One vCPU is enough to run it: at rest SelfPost and rspamd use under 0.1 % of
|
||||||
|
a CPU. The image is built and run on `amd64` and `arm64`; rspamd's image is
|
||||||
|
published for both.
|
||||||
|
|
||||||
|
**Building the image.** As long as no 2.x image is published (see [Fixed
|
||||||
|
image tag](#fixed-image-tag)), every installation builds its own, and the
|
||||||
|
build needs more than running does:
|
||||||
|
|
||||||
|
- **Memory: 1.5 GB.** The Go compiler is the largest part: the build was
|
||||||
|
killed for want of memory at 1 GB and at 512 MB (on `modernc.org/sqlite`
|
||||||
|
and `modernc.org/libc`) and passed at 1.5 GB - from cold, on one CPU, in
|
||||||
|
4.5 minutes (2.5 on two). On a smaller machine, build the image on
|
||||||
|
another one and bring it over:
|
||||||
|
`docker save ghcr.io/mixeme/selfpost:<tag> | ssh <host> docker load`.
|
||||||
|
Swap would give the compiler room too, but was not tried.
|
||||||
|
- **Disk: about 3 GB free while it builds** - the Go image (1.3 GB), the
|
||||||
|
downloaded modules (0.6 GB), the compile step (0.4 GB) and the image
|
||||||
|
(315 MB). Each rebuild leaves another 0.4 GB compile step in Docker's build
|
||||||
|
cache (the test server had gathered 16 GB of them in a week);
|
||||||
|
`docker builder prune` gives it back.
|
||||||
|
|
||||||
|
**Data, under `./data`.** What grows, and what bounds it:
|
||||||
|
|
||||||
|
- **The send log** - the main driver: a row per recipient, with the
|
||||||
|
`mail.log` lines Postfix wrote about it, about 2-3 KB a message, kept for
|
||||||
|
the send-log retention (90 days by default, Server › Settings). Worked
|
||||||
|
out from that: 1 000 messages a day keep about 250 MB, 10 000 about
|
||||||
|
2.5 GB.
|
||||||
|
- **`mail.log`** - today's file and 14 rotated, compressed ones. About
|
||||||
|
750 bytes of log a message before compression, and the noise of scanners
|
||||||
|
on port 25 when it is open (400 KB a day on the test server). Worked out
|
||||||
|
from that: under 20 MB in all at 10 000 messages a day.
|
||||||
|
- **The mail queue** (`/data/postfix/queue`) - empty while mail flows. While
|
||||||
|
a receiving server, or an inbound domain's upstream, does not take mail,
|
||||||
|
it holds that mail up to the queue lifetime (5 days by default, [Queue and
|
||||||
|
System log](#queue-and-system-log)): leave room for an outage's worth -
|
||||||
|
on port 25 a message may be up to `INBOUND_MESSAGE_SIZE_LIMIT` (25 MiB).
|
||||||
|
Postfix itself refuses mail with `452 4.3.1 Insufficient system storage`
|
||||||
|
once the free space is under 1.5 times a listener's message size limit
|
||||||
|
(Postfix 3.10.13, `src/smtpd/smtpd_check.c`, `smtpd_check_queue()`).
|
||||||
|
- **The inbound log** keeps at most 10 000 rows; DMARC reports, the DKIM
|
||||||
|
keys and the rest of the database are small.
|
||||||
|
|
||||||
|
A full backup holds the database, the DKIM keys, the SASL credentials, the
|
||||||
|
deployment files and `certs/`, so it is about the size of the send log; `mail.log` and
|
||||||
|
the queue are left out of it ([Full backup and
|
||||||
|
restore](#full-backup-and-restore)).
|
||||||
|
|
||||||
### Ports
|
### Ports
|
||||||
|
|
||||||
`deploy/docker-compose.yml` maps **465**, **587**, and **25** to the host. Port
|
`deploy/docker-compose.yml` maps **465**, **587**, and **25** to the host. Port
|
||||||
@@ -84,8 +177,9 @@ Since `2.7.0` a message sent on 465 or 587 does not tell its recipients
|
|||||||
where the application runs: the `Received:` header SelfPost adds reads
|
where the application runs: the `Received:` header SelfPost adds reads
|
||||||
`Received: by mail.example.com (Postfix) id 4AbC12xyz for <...>; <date>`,
|
`Received: by mail.example.com (Postfix) id 4AbC12xyz for <...>; <date>`,
|
||||||
without the application's address, host name or login. To trace a message
|
without the application's address, host name or login. To trace a message
|
||||||
someone complains about, take that queue id to *Outbound › Log*; mail.log
|
someone complains about, find its row in *Outbound › Log* (by domain,
|
||||||
keeps the client address on smtpd's `client=` line for the same id.
|
application and day; the message's page shows its queue id); mail.log keeps
|
||||||
|
the client address on smtpd's `client=` line for the same id.
|
||||||
|
|
||||||
### Local trial
|
### Local trial
|
||||||
|
|
||||||
@@ -182,6 +276,19 @@ docker compose up -d
|
|||||||
The nginx/Caddy/Traefik fragments already include `docker compose up -d` —
|
The nginx/Caddy/Traefik fragments already include `docker compose up -d` —
|
||||||
skip this if you ran one of those.
|
skip this if you ran one of those.
|
||||||
|
|
||||||
|
With Apache on the host, the panel sees every visitor at the Docker
|
||||||
|
network's gateway. Tell it that address is your proxy, so that each visitor
|
||||||
|
has a sign-in limit of their own, and recreate the container:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker network inspect "$(basename "$PWD")_default" -f '{{(index .IPAM.Config 0).Gateway}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Put what it prints into `.env` as `TRUSTED_PROXY_CIDR=` and run `docker
|
||||||
|
compose up -d` again. Until then the panel's log says, after the first
|
||||||
|
requests, that every visitor shares one limit and what to set; Server ›
|
||||||
|
Health shows the same under *Your address*.
|
||||||
|
|
||||||
**Get the setup URL** — open it in a browser to create the admin account (see
|
**Get the setup URL** — open it in a browser to create the admin account (see
|
||||||
[Initial setup](#initial-setup)):
|
[Initial setup](#initial-setup)):
|
||||||
|
|
||||||
@@ -204,14 +311,14 @@ cat ./data/setup-token
|
|||||||
#### Fixed image tag
|
#### Fixed image tag
|
||||||
|
|
||||||
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
|
`deploy/docker-compose.yml` pins an explicit version (`ghcr.io/mixeme/selfpost:X.Y.Z`),
|
||||||
deliberately never `:latest`. The current pin is `2.13.0`. **No 2.x image is
|
deliberately never `:latest`. The current pin is `2.16.0`. **No 2.x image is
|
||||||
published on ghcr.io yet** (the newest there is `1.3.0`); until one is, build
|
published on ghcr.io yet** (the newest there is `1.3.0`); until one is, build
|
||||||
it from the checkout under the pinned name, and Compose uses the local image
|
it from the checkout under the pinned name, and Compose uses the local image
|
||||||
instead of pulling:
|
instead of pulling:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -f build/Dockerfile --build-arg VERSION=2.13.0 \
|
docker build -f build/Dockerfile --build-arg VERSION=2.16.0 \
|
||||||
-t ghcr.io/mixeme/selfpost:2.13.0 .
|
-t ghcr.io/mixeme/selfpost:2.16.0 .
|
||||||
```
|
```
|
||||||
|
|
||||||
`VERSION` must equal the tag: it is the version the backup check compares
|
`VERSION` must equal the tag: it is the version the backup check compares
|
||||||
@@ -250,7 +357,12 @@ expected to set; defaults match the code exactly.
|
|||||||
| `PANEL_SESSION_IDLE_DAYS` | Sliding idle timeout for the panel login session, in days. There is no absolute cap: a user who keeps coming back stays signed in indefinitely. | `7` | `.env` |
|
| `PANEL_SESSION_IDLE_DAYS` | Sliding idle timeout for the panel login session, in days. There is no absolute cap: a user who keeps coming back stays signed in indefinitely. | `7` | `.env` |
|
||||||
| `SELFPOST_DNS_RESOLVERS` | Comma-separated recursive resolvers the panel's PTR/SPF/DKIM/DMARC checks query directly (so they report what the internet sees, not what this host's stub resolver synthesises). Each is given 2 seconds to answer before the next is asked, and the one that answered last is asked first; a lookup none of them answered shows as not known, never as a wrong record. | `1.1.1.1:53`, `8.8.8.8:53`, `9.9.9.9:53` when unset | `.env` |
|
| `SELFPOST_DNS_RESOLVERS` | Comma-separated recursive resolvers the panel's PTR/SPF/DKIM/DMARC checks query directly (so they report what the internet sees, not what this host's stub resolver synthesises). Each is given 2 seconds to answer before the next is asked, and the one that answered last is asked first; a lookup none of them answered shows as not known, never as a wrong record. | `1.1.1.1:53`, `8.8.8.8:53`, `9.9.9.9:53` when unset | `.env` |
|
||||||
| `TZ` | Time zone of the container, an IANA name such as `Europe/Moscow`: every time the panel shows, every day it counts (today on Health, the days of the statistics, the days of the log filters) and the stamps Postfix writes in `mail.log`, so the two agree. Stored times stay UTC, so changing it is only a restart. *Server › Settings* shows the zone in use, and says so when the image does not know the name (the times are then UTC). | UTC | `.env` |
|
| `TZ` | Time zone of the container, an IANA name such as `Europe/Moscow`: every time the panel shows, every day it counts (today on Health, the days of the statistics, the days of the log filters) and the stamps Postfix writes in `mail.log`, so the two agree. Stored times stay UTC, so changing it is only a restart. *Server › Settings* shows the zone in use, and says so when the image does not know the name (the times are then UTC). | UTC | `.env` |
|
||||||
| `TRUSTED_PROXY_CIDR` | Comma-separated CIDRs (bare IPs allowed) of reverse proxies allowed to supply `X-Forwarded-For` for login, setup, and account-change rate-limiting. **Leave unset unless you know the exact address of your reverse proxy.** A wrong value lets a client spoof its rate-limit key by sending a forged `X-Forwarded-For` header — the panel trusts the last hop only when the TCP peer matches one of these CIDRs. With Apache on the host (the default), the panel sees the proxy's connections come from the Docker network's gateway, e.g. `172.18.0.1` (`docker network inspect` shows it; measured 2026-10-15: the panel's `127.0.0.1:8080` is published through Docker's proxy, and a connection to such a port from the host arrives from the gateway). | *(empty — XFF ignored)* | `.env` |
|
| `TRUSTED_PROXY_CIDR` | Comma-separated CIDRs (bare IPs allowed) of reverse proxies allowed to supply `X-Forwarded-For` for sign-in and setup rate-limiting (account-change attempts are counted per user, whatever the address). **Leave unset unless you know the exact address of your reverse proxy.** A wrong value lets a client spoof its rate-limit key by sending a forged `X-Forwarded-For` header — the panel trusts the last hop only when the TCP peer matches one of these CIDRs. With Apache on the host (the default), the panel sees the proxy's connections come from the Docker network's gateway, e.g. `172.18.0.1` (`docker network inspect` shows it; measured 2026-10-15: the panel's `127.0.0.1:8080` is published through Docker's proxy, and a connection to such a port from the host arrives from the gateway). | *(empty — XFF ignored)* | `.env` |
|
||||||
|
|
||||||
|
`PANEL_COOKIE_SECURE` is not in the table: the session cookie is `Secure`
|
||||||
|
unless it is `false`, which only a trial over plain HTTP wants, and the
|
||||||
|
compose file does not pass it - the local trial in the README sets it on
|
||||||
|
`docker run` (`-e PANEL_COOKIE_SECURE=false`).
|
||||||
|
|
||||||
TLS certificate paths (`TLS_CERT_FILE`, `TLS_KEY_FILE`) are fixed in
|
TLS certificate paths (`TLS_CERT_FILE`, `TLS_KEY_FILE`) are fixed in
|
||||||
[deploy/docker-compose.yml](../deploy/docker-compose.yml) to match the `./certs`
|
[deploy/docker-compose.yml](../deploy/docker-compose.yml) to match the `./certs`
|
||||||
@@ -330,13 +442,13 @@ ln -s /etc/letsencrypt/live/mail.example.com/privkey.pem certs/privkey.pem
|
|||||||
```
|
```
|
||||||
|
|
||||||
The panel reads them as its own user (Health shows the certificate, a full
|
The panel reads them as its own user (Health shows the certificate, a full
|
||||||
backup carries both files), through the container's group 10998. certbot's
|
backup carries both files), through the container's group 10997. certbot's
|
||||||
documentation gives the way for a server that is not root, and certbot keeps
|
documentation gives the way for a server that is not root, and certbot keeps
|
||||||
the key's group and mode at every renewal:
|
the key's group and mode at every renewal:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo chmod 0755 /etc/letsencrypt/live /etc/letsencrypt/archive
|
sudo chmod 0755 /etc/letsencrypt/live /etc/letsencrypt/archive
|
||||||
sudo chgrp 10998 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
sudo chgrp 10997 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
||||||
sudo chmod 0640 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
sudo chmod 0640 /etc/letsencrypt/archive/mail.example.com/privkey*.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -357,7 +469,7 @@ docker compose -f docker-compose.yml -f nginx/docker-compose.nginx.yml \
|
|||||||
The fragment mounts certbot's directory into both nginx and SelfPost, at
|
The fragment mounts certbot's directory into both nginx and SelfPost, at
|
||||||
`/etc/letsencrypt`; `./certs` holds two links to
|
`/etc/letsencrypt`; `./certs` holds two links to
|
||||||
`/etc/letsencrypt/live/mail.example.com/` — a path that exists inside the
|
`/etc/letsencrypt/live/mail.example.com/` — a path that exists inside the
|
||||||
container, not on the host — and the panel reads the key through group 10998,
|
container, not on the host — and the panel reads the key through group 10997,
|
||||||
as in the Apache case. Then start everything:
|
as in the Apache case. Then start everything:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -365,7 +477,7 @@ mkdir certs
|
|||||||
ln -s /etc/letsencrypt/live/mail.example.com/fullchain.pem certs/fullchain.pem
|
ln -s /etc/letsencrypt/live/mail.example.com/fullchain.pem certs/fullchain.pem
|
||||||
ln -s /etc/letsencrypt/live/mail.example.com/privkey.pem certs/privkey.pem
|
ln -s /etc/letsencrypt/live/mail.example.com/privkey.pem certs/privkey.pem
|
||||||
sudo chmod 0755 nginx/certbot-etc/live nginx/certbot-etc/archive
|
sudo chmod 0755 nginx/certbot-etc/live nginx/certbot-etc/archive
|
||||||
sudo chgrp 10998 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
sudo chgrp 10997 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
||||||
sudo chmod 0640 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
sudo chmod 0640 nginx/certbot-etc/archive/mail.example.com/privkey*.pem
|
||||||
|
|
||||||
docker compose -f docker-compose.yml -f nginx/docker-compose.nginx.yml up -d
|
docker compose -f docker-compose.yml -f nginx/docker-compose.nginx.yml up -d
|
||||||
@@ -388,6 +500,12 @@ docker compose -f docker-compose.yml -f caddy/docker-compose.caddy.yml up -d
|
|||||||
|
|
||||||
Verify Caddy's on-disk cert path for your version before relying on the
|
Verify Caddy's on-disk cert path for your version before relying on the
|
||||||
default mount — see the comment at the top of the Caddy compose fragment.
|
default mount — see the comment at the top of the Caddy compose fragment.
|
||||||
|
With Caddy the certificate lives in Caddy's own data, not in `certs/`, and
|
||||||
|
Caddy writes its keys readable by itself alone: the panel's full backup then
|
||||||
|
refuses with "certs/ holds no certificate". Back up `./caddy/caddy-data`
|
||||||
|
with the project directory instead, or keep a copy of the pair in `certs/`,
|
||||||
|
the key given to group 10997 (`chgrp 10997`, `chmod 640`), refreshed after
|
||||||
|
each renewal.
|
||||||
|
|
||||||
**Traefik (containerised).** Edit the `Host(...)` label and ACME email in
|
**Traefik (containerised).** Edit the `Host(...)` label and ACME email in
|
||||||
[deploy/traefik/docker-compose.traefik.yml](../deploy/traefik/docker-compose.traefik.yml),
|
[deploy/traefik/docker-compose.traefik.yml](../deploy/traefik/docker-compose.traefik.yml),
|
||||||
@@ -422,7 +540,7 @@ changes.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Overview | Overview | `/overview` |
|
| Overview | Overview | `/overview` |
|
||||||
| Outbound | Domains · Log · Queue · DMARC reports | `/outbound/domains`, `/outbound/log`, `/outbound/queue`, `/outbound/dmarc` |
|
| Outbound | Domains · Log · Queue · DMARC reports | `/outbound/domains`, `/outbound/log`, `/outbound/queue`, `/outbound/dmarc` |
|
||||||
| Inbound | Domains | `/inbound/domains` |
|
| Inbound | Domains · Log · Queue and Filter lists (global role) | `/inbound/domains`, `/inbound/log`, `/inbound/queue`, `/inbound/filter-lists` |
|
||||||
| Server | Health · System log · Backup · Users · Settings | `/server/health`, `/server/log`, `/server/backup`, `/server/users`, `/server/settings` |
|
| Server | Health · System log · Backup · Users · Settings | `/server/health`, `/server/log`, `/server/backup`, `/server/users`, `/server/settings` |
|
||||||
| User menu (your name) | Account · Help · Sign out | `/account`, `/help`, `POST /logout` |
|
| User menu (your name) | Account · Help · Sign out | `/account`, `/help`, `POST /logout` |
|
||||||
|
|
||||||
@@ -433,6 +551,11 @@ menu: a group with nothing in it is absent. *Inbound* appears only with
|
|||||||
queue, answer 404 to a user with the *domain* role — the same answer a missing
|
queue, answer 404 to a user with the *domain* role — the same answer a missing
|
||||||
page gets.
|
page gets.
|
||||||
|
|
||||||
|
Two pages are outside the menu: `/license`, the AGPL-3.0 text the footer links
|
||||||
|
to, open to anyone; and `/server/components` (global role), the panel's
|
||||||
|
component kit - every building block of its pages on one page, a reference
|
||||||
|
for whoever works on the panel.
|
||||||
|
|
||||||
Pages that used to be one are now several. The old Status page is **Overview**
|
Pages that used to be one are now several. The old Status page is **Overview**
|
||||||
(one verdict per check) and **Server › Health** (the tables behind them). The
|
(one verdict per check) and **Server › Health** (the tables behind them). The
|
||||||
old Settings page is **Account** (your own name, e-mail and password) and
|
old Settings page is **Account** (your own name, e-mail and password) and
|
||||||
@@ -454,28 +577,21 @@ assumptions stay here in `docs/guide.md`.
|
|||||||
### Overview and Health
|
### Overview and Health
|
||||||
|
|
||||||
**Overview** (`/overview`, global role) shows the stamp — the worst of its
|
**Overview** (`/overview`, global role) shows the stamp — the worst of its
|
||||||
checks — and one card per check: machine (processor and memory), supervised
|
checks — with a sentence beside it ("Mail is flowing. One thing needs
|
||||||
|
attention.", "Mail may not be flowing." when a check fails) and, under it,
|
||||||
|
each check that is not fine with its value; then one card per check: machine (processor and memory), supervised
|
||||||
processes (Postfix, the panel - whose roles include the DKIM signer - and
|
processes (Postfix, the panel - whose roles include the DKIM signer - and
|
||||||
their helpers), TLS certificate, queue, milter sockets and reverse DNS. A
|
their helpers), TLS certificate, queue, milter sockets and reverse DNS. A
|
||||||
check the panel could not run shows as a warning, never as fine. Below the cards are the outbound domain list and, with the inbound relay
|
check the panel could not run shows as a warning, never as fine. Below the cards are the outbound domain list and, with the inbound relay
|
||||||
on, the inbound one. Each card leads to its table on **Server › Health**
|
on, the inbound one. Each card leads to its table on **Server › Health**
|
||||||
(`/server/health`) — the queue card to the Outbound queue — and Health has the
|
(`/server/health`) — the queue card to the Outbound queue — and Health has the
|
||||||
readings, in the order of what stops mail first:
|
readings, numbered in the order of what stops mail first:
|
||||||
|
|
||||||
- **Machine** — processor (core and thread counts), memory and swap of the
|
- **Processes** and **Milter sockets**: the DKIM signer's two - `dkim-signer · applications` for what
|
||||||
host (a memory limit set on the container is not reflected), and
|
|
||||||
per-interface network throughput and totals of the container's own network
|
|
||||||
interfaces, read from the kernel's counters; CPU and throughput are measured between refreshes, so they appear
|
|
||||||
one refresh after the page opens. A fully busy processor or a machine out of
|
|
||||||
memory is a warning, because both delay or kill the mail path; throughput is
|
|
||||||
only reported.
|
|
||||||
- **Processes**, **TLS certificate** (validity and expiry, and whether its
|
|
||||||
names cover `SELFPOST_HOSTNAME` - the tag beside *Names*; a certificate for
|
|
||||||
another name fails, as a client that checks the name refuses it) and
|
|
||||||
**Milter sockets**: the DKIM signer's two - `dkim-signer · applications` for what
|
|
||||||
applications send on 465/587, `dkim-signer · notifications` for the
|
applications send on 465/587, `dkim-signer · notifications` for the
|
||||||
panel's own mail - the sender check, the send log (the journal-milter)
|
panel's own mail - the sender check, the send log (the journal-milter),
|
||||||
and, with one configured, the spam filter. Postfix attaches the signer
|
with the inbound relay on the Inbound log's (the inbound-journal-milter,
|
||||||
|
fail-open like the send log's), and, with one configured, the spam filter. Postfix attaches the signer
|
||||||
strictly, so either of its rows down means the mail that goes through it
|
strictly, so either of its rows down means the mail that goes through it
|
||||||
waits.
|
waits.
|
||||||
- **Sender check** — what the strict check of the `From:` line has cost
|
- **Sender check** — what the strict check of the `From:` line has cost
|
||||||
@@ -493,12 +609,25 @@ readings, in the order of what stops mail first:
|
|||||||
a connection was turned away or a message refused in the last 24 hours. If these numbers do not stay
|
a connection was turned away or a message refused in the last 24 hours. If these numbers do not stay
|
||||||
at nothing, the panel is in the way of your mail more than it should be -
|
at nothing, the panel is in the way of your mail more than it should be -
|
||||||
report it.
|
report it.
|
||||||
|
- **TLS certificate** — validity and expiry, and whether its names cover
|
||||||
|
`SELFPOST_HOSTNAME` (the tag beside *Names*); a certificate for another
|
||||||
|
name fails, as a client that checks the name refuses it.
|
||||||
- **Hostname and reverse DNS** — compares `SELFPOST_HOSTNAME` against the PTR
|
- **Hostname and reverse DNS** — compares `SELFPOST_HOSTNAME` against the PTR
|
||||||
record the internet publishes for this server's IP (forward-confirmed
|
record the internet publishes for this server's IP (forward-confirmed
|
||||||
reverse DNS), see [Server-level DNS](#server-level-dns-ptrrdns); use
|
reverse DNS), see [Server-level DNS](#server-level-dns-ptrrdns); use
|
||||||
*Re-check DNS* after changing DNS. *Greeting* is the `220` line Postfix
|
*Re-check DNS* after changing DNS (twenty re-checks in 15 minutes per user;
|
||||||
|
past that the page shows the last result). *Greeting* is the `220` line Postfix
|
||||||
opens every connection with; it must name the hostname, and another name
|
opens every connection with; it must name the hostname, and another name
|
||||||
there is a warning.
|
there is a warning.
|
||||||
|
- **Notification mail** — whether the DNS records the panel's own mail is
|
||||||
|
signed and authorised with are in place (see [Notifications](#notifications)).
|
||||||
|
- **Machine** — processor (core and thread counts), memory and swap of the
|
||||||
|
host (a memory limit set on the container is not reflected), and
|
||||||
|
per-interface network throughput and totals of the container's own network
|
||||||
|
interfaces, read from the kernel's counters; CPU and throughput are measured between refreshes, so they appear
|
||||||
|
one refresh after the page opens. A fully busy processor or a machine out of
|
||||||
|
memory is a warning, because both delay or kill the mail path; throughput is
|
||||||
|
only reported.
|
||||||
- **Your address** (in the Configuration card) — the address the panel sees
|
- **Your address** (in the Configuration card) — the address the panel sees
|
||||||
your browser at, and whether it took it from `X-Forwarded-For`. Behind a
|
your browser at, and whether it took it from `X-Forwarded-For`. Behind a
|
||||||
reverse proxy that the panel does not trust it is the proxy's own: every
|
reverse proxy that the panel does not trust it is the proxy's own: every
|
||||||
@@ -509,7 +638,8 @@ readings, in the order of what stops mail first:
|
|||||||
selector table (`/data/dkim/selectors`), the Postfix sender map, the
|
selector table (`/data/dkim/selectors`), the Postfix sender map, the
|
||||||
inbound relay maps and the filter lists (with `INBOUND_RELAY_ENABLE=true`),
|
inbound relay maps and the filter lists (with `INBOUND_RELAY_ENABLE=true`),
|
||||||
the DMARC report maps (with `DMARC_REPORTS_ENABLE=true`) - removes the keys
|
the DMARC report maps (with `DMARC_REPORTS_ENABLE=true`) - removes the keys
|
||||||
of deleted domains, and has Postfix re-read them; the signer needs no
|
of deleted domains, and has Postfix re-read them (a save elsewhere that
|
||||||
|
changes no file asks no reload; this button always does); the signer needs no
|
||||||
reload, it reads the table for every message. This is
|
reload, it reads the table for every message. This is
|
||||||
what the first start after a restore does (see [Restore](#restore)). Use it
|
what the first start after a restore does (see [Restore](#restore)). Use it
|
||||||
if the daemons drifted from what the panel shows after manual edits under
|
if the daemons drifted from what the panel shows after manual edits under
|
||||||
@@ -527,14 +657,25 @@ readings, in the order of what stops mail first:
|
|||||||
### Queue and System log
|
### Queue and System log
|
||||||
|
|
||||||
- **Outbound › Queue** (`/outbound/queue`, global role) — live view of
|
- **Outbound › Queue** (`/outbound/queue`, global role) — live view of
|
||||||
messages Postfix is still trying to deliver, deferring or holding, as
|
messages Postfix is still trying to deliver, deferring or holding, a row
|
||||||
`postqueue -p` lists them. It only shows: nothing there deletes or forces a message. A box
|
per recipient, oldest first: queue id, when it arrived, sender, recipient,
|
||||||
|
and the last reply of the receiving server or why no connection was made
|
||||||
|
(from `postqueue -j`). *Details* opens the message in the Outbound log when
|
||||||
|
it has a row there; the panel's own mail and bounces have none. The box
|
||||||
|
head counts the messages and their size; past 200 recipients the oldest 200
|
||||||
|
are listed and a line says how many there are - `postqueue -p` in the
|
||||||
|
container lists them all. It only shows: nothing there deletes or forces a message. A box
|
||||||
states this instance's retry policy — first retry delay, later backoff cap,
|
states this instance's retry policy — first retry delay, later backoff cap,
|
||||||
how long a message stays in the queue — from `postconf -h`, read once when
|
how long a message stays in the queue — from `postconf -h`, read once when
|
||||||
the panel starts. A `postconf -e` override inside the container is visible
|
the panel starts. A `postconf -e` override inside the container is visible
|
||||||
after the next panel (or container) restart. There is no maximum attempt
|
after the next panel (or container) restart. There is no maximum attempt
|
||||||
count: Postfix retries until the message is delivered or the queue lifetime
|
count: Postfix retries until the message is delivered or the queue lifetime
|
||||||
runs out.
|
runs out.
|
||||||
|
- **Inbound › Queue** (`/inbound/queue`, global role, with the inbound relay
|
||||||
|
on) — the same Postfix queue, the recipients of inbound domains: mail
|
||||||
|
accepted on port 25 that its upstream has not yet taken, with the
|
||||||
|
upstream's last reply. A recipient belongs here when its domain is an
|
||||||
|
inbound domain, and to Outbound › Queue otherwise.
|
||||||
- **Server › System log** (`/server/log`) — tail of `/data/log/mail.log`
|
- **Server › System log** (`/server/log`) — tail of `/data/log/mail.log`
|
||||||
(Postfix and related daemon lines). The log rotates daily (14 files kept)
|
(Postfix and related daemon lines). The log rotates daily (14 files kept)
|
||||||
with a `postfix reload` after each rotation; a background loop checks every
|
with a `postfix reload` after each rotation; a background loop checks every
|
||||||
@@ -555,14 +696,37 @@ readings, in the order of what stops mail first:
|
|||||||
### Account
|
### Account
|
||||||
|
|
||||||
**Account** (`/account`, every role) is the signed-in user's own: **Profile**
|
**Account** (`/account`, every role) is the signed-in user's own: **Profile**
|
||||||
(username and e-mail) and **Password**. Saving the profile does not ask for the
|
(username and e-mail) and **Password**. Changing the username or the
|
||||||
password; changing the password asks for the current one and is rate-limited
|
notifications switch does not ask for the password; changing the e-mail does,
|
||||||
like a sign-in. The e-mail is the user's own address: it is where the panel
|
and so does changing the password, both on the limit of a sign-in's guesses
|
||||||
|
(ten in 15 minutes per user). The e-mail is the user's own address: it is where the panel
|
||||||
sends [notifications](#notifications) while *Send me notifications* is ticked
|
sends [notifications](#notifications) while *Send me notifications* is ticked
|
||||||
beside it, and what a domain's *Use my e-mail* button fills in (see
|
beside it, and what a domain's *Use my e-mail* button fills in (see
|
||||||
[Domain-level DNS](#domain-level-dns-spf-dkim-dmarc)). Application SASL logins
|
[Domain-level DNS](#domain-level-dns-spf-dkim-dmarc)). Application SASL logins
|
||||||
are separate and are not changed here.
|
are separate and are not changed here.
|
||||||
|
|
||||||
|
**A confirmed e-mail and a forgotten password.** A new address - saved under
|
||||||
|
Account, or typed for a user by the global role on the user form - is sent a
|
||||||
|
link that confirms it (`https://<SELFPOST_HOSTNAME>/confirm/…`), which works
|
||||||
|
once, for 24 hours, opened in a browser signed in as that account; its page
|
||||||
|
has a button, and only the button confirms. The mail does not name the
|
||||||
|
account, so a mistyped address cannot be confirmed by whoever reads it.
|
||||||
|
Account tags the address *confirmed* or *not confirmed* and, while it is not,
|
||||||
|
offers *Send the link again* (at most five links an hour to one address).
|
||||||
|
Notifications go to the address either way. Only a confirmed address can be
|
||||||
|
sent a reset link: *Forgot your password?* on the sign-in page asks for the
|
||||||
|
username and always answers the same - whether the name exists, and whether
|
||||||
|
its address is confirmed, is not said; another link is not sent within ten
|
||||||
|
minutes of the last, and after five in a day the owner is mailed once that
|
||||||
|
further requests are ignored for 24 hours - and the link
|
||||||
|
(`https://<SELFPOST_HOSTNAME>/login/reset/…`) works once, for 30 minutes, and
|
||||||
|
only while the account's address is still the one it was sent to. Setting the
|
||||||
|
new password signs every session of the account out. The links name the
|
||||||
|
server by `SELFPOST_HOSTNAME`, so the panel must be served at that name. An
|
||||||
|
account without a confirmed address recovers as before: the global role sets
|
||||||
|
a new password on the user form, and the only global user from the container
|
||||||
|
(see [Users](#users)).
|
||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
**Server › Settings** (`/server/settings`, global role) holds what is true for
|
**Server › Settings** (`/server/settings`, global role) holds what is true for
|
||||||
@@ -586,7 +750,10 @@ the `mail.log` lines on Server › System log and on a message's page read the
|
|||||||
same clock as the rest of the page. The database keeps its times in UTC, so
|
same clock as the rest of the page. The database keeps its times in UTC, so
|
||||||
changing the zone is a restart and nothing more; the days counted before it
|
changing the zone is a restart and nothing more; the days counted before it
|
||||||
stay days of the old zone, a shift of a few hours in the statistics. A name
|
stay days of the old zone, a shift of a few hours in the statistics. A name
|
||||||
the image does not know leaves everything in UTC, and the box says so.
|
the image does not know leaves everything in UTC, and the box says so. One
|
||||||
|
thing keeps UTC on purpose: the day a DMARC report covers is its reporter's,
|
||||||
|
who counts days in UTC, so the lists and a report's title name that day; the
|
||||||
|
report's page gives its window's times in this zone and says which day it is.
|
||||||
|
|
||||||
### Notifications
|
### Notifications
|
||||||
|
|
||||||
@@ -606,7 +773,9 @@ assigned to them. A message goes to the e-mail on the user's
|
|||||||
[Account](#account) page and only while *Send me notifications* is ticked
|
[Account](#account) page and only while *Send me notifications* is ticked
|
||||||
there; a user without an e-mail gets none. **Server › Settings** has one switch
|
there; a user without an e-mail gets none. **Server › Settings** has one switch
|
||||||
that turns notifications off for everyone. With it off nothing is checked, so
|
that turns notifications off for everyone. With it off nothing is checked, so
|
||||||
what is wrong when it is turned on again is reported then.
|
what is wrong when it is turned on again is reported then. The first check runs
|
||||||
|
three minutes after the panel starts, then every hour; the panel sends at
|
||||||
|
most 60 notifications an hour, a ceiling nothing should come near.
|
||||||
|
|
||||||
Notifications are the panel's own mail. They are sent from
|
Notifications are the panel's own mail. They are sent from
|
||||||
`postmaster@<SELFPOST_HOSTNAME>`, one message per recipient, are not rows of
|
`postmaster@<SELFPOST_HOSTNAME>`, one message per recipient, are not rows of
|
||||||
@@ -614,18 +783,24 @@ the Outbound log and count against no rate limit. They name the page of the
|
|||||||
panel to open and carry no link: the panel does not know the address its
|
panel to open and carry no link: the panel does not know the address its
|
||||||
reverse proxy serves it under.
|
reverse proxy serves it under.
|
||||||
|
|
||||||
**Two DNS records make them arrive.** The server's own name is not one of your
|
**Three DNS records make them arrive.** The server's own name is not one of your
|
||||||
sending domains, so it needs records of its own, shown with the values to
|
sending domains, so it needs records of its own, shown with the values to
|
||||||
publish under **Server › Settings › Notification mail**:
|
publish under **Server › Settings › Notification mail**:
|
||||||
|
|
||||||
- a **DKIM** key for the hostname, which the panel generates and signs
|
- a **DKIM** key for the hostname, which the panel generates and signs
|
||||||
notifications with (`<selector>._domainkey.<hostname>`, TXT);
|
notifications with (`<selector>._domainkey.<hostname>`, TXT);
|
||||||
- an **SPF** record for the hostname: `v=spf1 a -all` (TXT).
|
- an **SPF** record for the hostname: `v=spf1 a -all` (TXT);
|
||||||
|
- a **DMARC** record for the hostname: `v=DMARC1; p=reject` at
|
||||||
|
`_dmarc.<hostname>` (TXT). It has receivers refuse mail forged in the
|
||||||
|
server's name - a false "reset your password" message among it. Only the
|
||||||
|
panel sends as this name, signed with its key, from the address its SPF
|
||||||
|
names, so reject costs nothing; `p=quarantine` is accepted too, `p=none`
|
||||||
|
is shown as weak, since it refuses nothing.
|
||||||
|
|
||||||
**Server › Health** shows whether each is in place in the *Notification mail*
|
**Server › Health** shows whether each is in place in the *Notification mail*
|
||||||
card. Without them notifications are still sent, and a receiver may file them
|
card. Without them notifications are still sent, and a receiver may file them
|
||||||
as spam or, if the parent domain publishes a strict DMARC policy, refuse them.
|
as spam or, if the parent domain publishes a strict DMARC policy, refuse them.
|
||||||
Mail of your sending domains depends on neither. If the hostname is itself one
|
Mail of your sending domains depends on none of them. If the hostname is itself one
|
||||||
of your sending domains, the DKIM record is that domain's and is already
|
of your sending domains, the DKIM record is that domain's and is already
|
||||||
published - and whoever administers that domain can then send mail that
|
published - and whoever administers that domain can then send mail that
|
||||||
looks like the panel's; a hostname that is nobody's sending domain is the
|
looks like the panel's; a hostname that is nobody's sending domain is the
|
||||||
@@ -662,7 +837,8 @@ What the *domain* role reaches inside what is assigned:
|
|||||||
| Overview, Server, the Outbound queue | yes | no (404) |
|
| Overview, Server, the Outbound queue | yes | no (404) |
|
||||||
| Outbound: domain page, applications, Domain settings, log | every domain | assigned domains |
|
| Outbound: domain page, applications, Domain settings, log | every domain | assigned domains |
|
||||||
| Outbound › DMARC reports | every report, with the ingest statistics | reports of assigned domains, without the ingest box |
|
| Outbound › DMARC reports | every report, with the ingest statistics | reports of assigned domains, without the ingest box |
|
||||||
| Inbound: domain page, upstream, recipients | every domain | assigned inbound domains |
|
| Inbound: domain page, upstream, recipients, log | every domain | assigned inbound domains |
|
||||||
|
| Inbound › Filter lists | yes | no (404) |
|
||||||
| Add or delete a domain, either direction | yes | no |
|
| Add or delete a domain, either direction | yes | no |
|
||||||
| Export a domain (Domain settings) | yes | assigned domains |
|
| Export a domain (Domain settings) | yes | assigned domains |
|
||||||
| Import a domain (Server › Backup) | yes | no |
|
| Import a domain (Server › Backup) | yes | no |
|
||||||
@@ -675,6 +851,12 @@ The panel refuses to remove or demote the **last** global user, so it can never
|
|||||||
end up with none. A user cannot delete the account they are signed in with.
|
end up with none. A user cannot delete the account they are signed in with.
|
||||||
The user form also sets the user's e-mail, which the users list shows.
|
The user form also sets the user's e-mail, which the users list shows.
|
||||||
|
|
||||||
|
Adding a user, setting a user's password, changing their e-mail or role -
|
||||||
|
your own account's included - and deleting a user ask for **your account
|
||||||
|
password**, the one you sign in with, as a backup does and under the same
|
||||||
|
limit on wrong guesses: a session left open is not enough to hand out an
|
||||||
|
account. Saving only the name or the domain assignments is not asked.
|
||||||
|
|
||||||
### Sessions
|
### Sessions
|
||||||
|
|
||||||
A login survives a container restart: sessions live in SQLite, not in
|
A login survives a container restart: sessions live in SQLite, not in
|
||||||
@@ -702,6 +884,13 @@ published, then `docker compose up -d`. The backup version check requires the ru
|
|||||||
to match the version that created a full backup — see [Fixed image
|
to match the version that created a full backup — see [Fixed image
|
||||||
tag](#fixed-image-tag).
|
tag](#fixed-image-tag).
|
||||||
|
|
||||||
|
**Base-package fixes come with releases.** The image is Debian trixie, and a
|
||||||
|
security update of Postfix, Cyrus SASL, OpenSSL or Berkeley DB reaches you with
|
||||||
|
the next SelfPost release built after it: a published image tag is never
|
||||||
|
rebuilt. The project checks every week whether one of those has moved and cuts
|
||||||
|
a patch release when it has; a release whose notes say "base packages" is one
|
||||||
|
of those.
|
||||||
|
|
||||||
**To 2.8.0** there is nothing to do. From 2.8.0 the panel signs outgoing
|
**To 2.8.0** there is nothing to do. From 2.8.0 the panel signs outgoing
|
||||||
mail itself, in a milter of its own, and OpenDKIM is no longer in the image;
|
mail itself, in a milter of its own, and OpenDKIM is no longer in the image;
|
||||||
the keys, the selectors and the DNS records stay as they are. The keys move
|
the keys, the selectors and the DNS records stay as they are. The keys move
|
||||||
@@ -755,15 +944,18 @@ again whole and loses the keys of domains added under 2.8.x.
|
|||||||
### Container health
|
### Container health
|
||||||
|
|
||||||
The image declares a Docker `HEALTHCHECK` that probes `GET /healthz` on port
|
The image declares a Docker `HEALTHCHECK` that probes `GET /healthz` on port
|
||||||
8080 (unauthenticated). It returns `200 ok` when the panel - whose roles
|
8080 (unauthenticated, and answered only to a request that did not come
|
||||||
|
through a reverse proxy - no `X-Forwarded-For` - from loopback or a private
|
||||||
|
address; anyone else gets `404`). It returns `200 ok` when the panel - whose roles
|
||||||
include the DKIM signer - and Postfix are both `RUNNING` under supervisord;
|
include the DKIM signer - and Postfix are both `RUNNING` under supervisord;
|
||||||
otherwise `503 unhealthy`. This
|
otherwise `503 unhealthy`. This
|
||||||
catches a dead mail path that would still leave the HTTP server up, but it
|
catches a dead mail path that would still leave the HTTP server up, but it
|
||||||
does **not** verify TLS certificates, DNS records, or end-to-end delivery —
|
does **not** verify TLS certificates, DNS records, or end-to-end delivery —
|
||||||
use the authenticated [Overview and Health](#overview-and-health) pages for
|
use the authenticated [Overview and Health](#overview-and-health) pages for
|
||||||
that. External monitoring
|
that. External monitoring runs on the host - `curl -fsS
|
||||||
can use the same endpoint through the reverse proxy if you expose it, or poll
|
http://127.0.0.1:8080/healthz` - or polls `docker inspect` health state;
|
||||||
`docker inspect` health state on the host.
|
through the reverse proxy the endpoint is not found, so its answer tells a
|
||||||
|
stranger nothing.
|
||||||
|
|
||||||
### Server-level DNS (PTR/rDNS)
|
### Server-level DNS (PTR/rDNS)
|
||||||
|
|
||||||
@@ -837,11 +1029,13 @@ instance keeps running, what goes into the archive, and what happens on restore.
|
|||||||
→ *Full backup*. *Encrypt with a password* is ticked; untick it for a plain
|
→ *Full backup*. *Encrypt with a password* is ticked; untick it for a plain
|
||||||
archive. Same archive as the CLI below. The panel runs as its own user, not as
|
archive. Same archive as the CLI below. The panel runs as its own user, not as
|
||||||
root, so **the private key in `certs/` has to be readable by it**: give the
|
root, so **the private key in `certs/` has to be readable by it**: give the
|
||||||
file to the container's `selfpost` group, which has the fixed id 10998 and holds
|
file to the container's `backupkey` group, which has the fixed id 10997 and
|
||||||
both the panel and Postfix —
|
holds the panel alone - not Postfix, whose daemons face the internet (until
|
||||||
|
`2.14.4` the guide said group 10998, `selfpost`, which Postfix is in; a key
|
||||||
|
given to it then is to be given to 10997 now) —
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
chgrp 10998 certs/privkey.pem && chmod 640 certs/privkey.pem
|
chgrp 10997 certs/privkey.pem && chmod 640 certs/privkey.pem
|
||||||
```
|
```
|
||||||
|
|
||||||
— and have whatever renews the certificate do the same; certbot does it by
|
— and have whatever renews the certificate do the same; certbot does it by
|
||||||
@@ -918,7 +1112,7 @@ methods avoid it by stopping first.
|
|||||||
| SQLite consistency | `VACUUM INTO` snapshot | Safe when stopped | Safe when stopped |
|
| SQLite consistency | `VACUUM INTO` snapshot | Safe when stopped | Safe when stopped |
|
||||||
| `data/log/` | Excluded | Included (optional exclude) | Included (optional exclude) |
|
| `data/log/` | Excluded | Included (optional exclude) | Included (optional exclude) |
|
||||||
| Postfix queue | No | Yes | Yes |
|
| Postfix queue | No | Yes | Yes |
|
||||||
| `certs/` private key | Yes (the panel needs it readable by group 10998; links are followed) | Yes when `certs/` holds the files; links are stored as links unless `tar -h` | No — deploy files separate |
|
| `certs/` private key | Yes (the panel needs it readable by group 10997; links are followed) | Yes when `certs/` holds the files; links are stored as links unless `tar -h` | No — deploy files separate |
|
||||||
| `data/manifest.json` | Written (version stamp) | Absent | Absent |
|
| `data/manifest.json` | Written (version stamp) | Absent | Absent |
|
||||||
| Restore version check | Yes — must match image tag | No | No |
|
| Restore version check | Yes — must match image tag | No | No |
|
||||||
| Post-restore *Resync* | Yes, on first boot | No | No |
|
| Post-restore *Resync* | Yes, on first boot | No | No |
|
||||||
@@ -983,7 +1177,7 @@ A version mismatch at step 5 refuses to start and leaves `/data` untouched —
|
|||||||
the panel exits with a message naming the tag to use, e.g.:
|
the panel exits with a message naming the tag to use, e.g.:
|
||||||
|
|
||||||
```
|
```
|
||||||
backup: this backup was created by SelfPost 2.12.0 but this image is 2.13.0 — restore into the matching image (selfpost:2.12.0)
|
backup: this backup was created by SelfPost 2.15.0 but this image is 2.16.0 — restore into the matching image (selfpost:2.15.0)
|
||||||
```
|
```
|
||||||
|
|
||||||
Fix the tag in `docker-compose.yml` (build that tag as under [Fixed image
|
Fix the tag in `docker-compose.yml` (build that tag as under [Fixed image
|
||||||
@@ -1088,7 +1282,10 @@ the file is unrecoverable, which is the entire point.
|
|||||||
*Import a domain* takes an encrypted export directly: choose a `.spde` file and
|
*Import a domain* takes an encrypted export directly: choose a `.spde` file and
|
||||||
the password field appears (driven by the file extension in the browser; the
|
the password field appears (driven by the file extension in the browser; the
|
||||||
server also detects the envelope by its magic bytes). A plain `.json` export
|
server also detects the envelope by its magic bytes). A plain `.json` export
|
||||||
needs no password.
|
needs no password. An import is refused, and says why, for a DKIM key outside
|
||||||
|
1024-4096 bits, more than 500 applications, a domain named as the server
|
||||||
|
itself (its key signs the panel's own mail), and a report address on the
|
||||||
|
server's name other than the domain's own hosted one.
|
||||||
|
|
||||||
A full backup has to be turned back into a plain archive before it can be
|
A full backup has to be turned back into a plain archive before it can be
|
||||||
unpacked into `/data`, which the CLI does with the same password:
|
unpacked into `/data`, which the CLI does with the same password:
|
||||||
@@ -1112,7 +1309,10 @@ The CLI also *writes* encrypted backups for scripted/cron use. The password
|
|||||||
must be at least 12 characters, as on the panel's forms (decrypting takes
|
must be at least 12 characters, as on the panel's forms (decrypting takes
|
||||||
any), and it comes from `SELFPOST_BACKUP_PASSWORD` or `-password-file <path>`
|
any), and it comes from `SELFPOST_BACKUP_PASSWORD` or `-password-file <path>`
|
||||||
(first line), never from a command-line argument, which would be visible in
|
(first line), never from a command-line argument, which would be visible in
|
||||||
the process list.
|
the process list. Its other flags: `-o <file>` writes the output to a file
|
||||||
|
instead of standard output, `-i <file>` reads the encrypted archive from a
|
||||||
|
file instead of standard input (with `-decrypt`), and `-version` prints the
|
||||||
|
version.
|
||||||
`-e` with the name only takes the value from your shell's environment, so it is
|
`-e` with the name only takes the value from your shell's environment, so it is
|
||||||
not written on the command line:
|
not written on the command line:
|
||||||
|
|
||||||
@@ -1141,7 +1341,21 @@ for such a sender.
|
|||||||
|
|
||||||
**Panel** (`/inbound/domains`): the global role adds and deletes domains. On a
|
**Panel** (`/inbound/domains`): the global role adds and deletes domains. On a
|
||||||
domain's page, set the upstream host/port and TLS to that hop (opportunistic /
|
domain's page, set the upstream host/port and TLS to that hop (opportunistic /
|
||||||
required / off), and choose recipients. An upstream has one TLS mode: Postfix
|
required / verified / off), and choose recipients. *Required* is Postfix's `encrypt`
|
||||||
|
level: the hop is encrypted, but the upstream's certificate is not checked,
|
||||||
|
so it guards against someone who listens on the way, not against one who
|
||||||
|
answers in the upstream's place. *Verified* is Postfix's `secure` level: the
|
||||||
|
certificate must be issued by an authority Debian's `ca-certificates`
|
||||||
|
trusts, for the upstream's host name - so the upstream is given by name,
|
||||||
|
not by address (the panel refuses an address with it), and mail waits in
|
||||||
|
the queue while the check fails, a self-signed certificate or one for
|
||||||
|
another name included. To try an upstream before choosing it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker exec <container> posttls-finger -c -l secure -F /etc/ssl/certs/ca-certificates.crt "[mx.example.com]:25"
|
||||||
|
```
|
||||||
|
|
||||||
|
"Verified TLS connection established" means it will pass. An upstream has one TLS mode: Postfix
|
||||||
keeps the TLS policy per next hop, not per domain, so the mode saved for one
|
keeps the TLS policy per next hop, not per domain, so the mode saved for one
|
||||||
domain is the mode of every domain forwarded to the same host and port, and
|
domain is the mode of every domain forwarded to the same host and port, and
|
||||||
they are changed with it; a *domain* user who does not manage all of them is
|
they are changed with it; a *domain* user who does not manage all of them is
|
||||||
@@ -1207,6 +1421,13 @@ rereads the list files by itself and reads the key anew for every message. A
|
|||||||
restart is needed only for a change of its own configuration - the files of
|
restart is needed only for a change of its own configuration - the files of
|
||||||
`antispam/rspamd/`.
|
`antispam/rspamd/`.
|
||||||
|
|
||||||
|
The third file there, [worker-proxy.inc](../deploy/antispam/rspamd/worker-proxy.inc),
|
||||||
|
keeps rspamd's scanner and its controller - whose default password is public -
|
||||||
|
on the sidecar's own loopback and puts only the milter on the compose network;
|
||||||
|
the fragment runs rspamd with no Linux capability. Upgrading SelfPost to
|
||||||
|
`2.14.4` brings rspamd 4.2.2 with it: copy `antispam/` again, then
|
||||||
|
`docker compose ... up -d` recreates the sidecar.
|
||||||
|
|
||||||
The same directory holds
|
The same directory holds
|
||||||
[multimap.conf](../deploy/antispam/rspamd/multimap.conf), the rules for the
|
[multimap.conf](../deploy/antispam/rspamd/multimap.conf), the rules for the
|
||||||
**allow and deny lists** kept in the panel. SelfPost does not apply the lists
|
**allow and deny lists** kept in the panel. SelfPost does not apply the lists
|
||||||
@@ -1256,7 +1477,8 @@ inbound mail is being refused with a temporary error (`tempfail`) or going throu
|
|||||||
(`accept`).
|
(`accept`).
|
||||||
|
|
||||||
**Inbound mail is not in the Outbound log.** Port 25 runs its own milter chain:
|
**Inbound mail is not in the Outbound log.** Port 25 runs its own milter chain:
|
||||||
the optional anti-spam milter, and nothing else. The DKIM signer does not sign relayed
|
the optional anti-spam milter and the panel's inbound journal (Inbound › Log,
|
||||||
|
fail-open), not the signer, the sender check or the outbound journal. The DKIM signer does not sign relayed
|
||||||
mail (and a signing outage therefore cannot hold it up), and the journal-milter
|
mail (and a signing outage therefore cannot hold it up), and the journal-milter
|
||||||
does not file it — the [Outbound log](#outbound-log) stays a record of what
|
does not file it — the [Outbound log](#outbound-log) stays a record of what
|
||||||
*you* sent.
|
*you* sent.
|
||||||
@@ -1289,7 +1511,8 @@ settings) and nowhere else:
|
|||||||
server whose reports are parsed and shown under *DMARC reports*. Offered only
|
server whose reports are parsed and shown under *DMARC reports*. Offered only
|
||||||
while `DMARC_REPORTS_ENABLE=true`.
|
while `DMARC_REPORTS_ENABLE=true`.
|
||||||
- **A specific address** — any mailbox, typed for this domain; it receives the
|
- **A specific address** — any mailbox, typed for this domain; it receives the
|
||||||
raw XML. Beside the field, *Use my e-mail* fills it with the e-mail of your
|
raw XML. Not one on `SELFPOST_HOSTNAME`: to have this server receive the
|
||||||
|
reports, choose *SelfPost hosted*. Beside the field, *Use my e-mail* fills it with the e-mail of your
|
||||||
profile (Account); it only fills the field, and saving is the form's own
|
profile (Account); it only fills the field, and saving is the form's own
|
||||||
button. The button is absent when your profile has no e-mail.
|
button. The button is absent when your profile has no e-mail.
|
||||||
|
|
||||||
@@ -1297,8 +1520,8 @@ Changing the choice changes the DMARC record to publish; the domain page shows
|
|||||||
the new value. There is no panel-wide or per-user default. Only addresses on
|
the new value. There is no panel-wide or per-user default. Only addresses on
|
||||||
`SELFPOST_HOSTNAME` that a domain has chosen are accepted on port 25 - the
|
`SELFPOST_HOSTNAME` that a domain has chosen are accepted on port 25 - the
|
||||||
hosted address `dmarc-reports+<domain>@<hostname>` of a domain set to
|
hosted address `dmarc-reports+<domain>@<hostname>` of a domain set to
|
||||||
*SelfPost hosted*, or a typed address that happens to be on this host. There
|
*SelfPost hosted* - and a report is kept only when it is for the domain the
|
||||||
is no mailbox for the server as a whole.
|
address is tagged with. There is no mailbox for the server as a whole.
|
||||||
|
|
||||||
**DNS.** Publish the usual `_dmarc` TXT on each sending domain with
|
**DNS.** Publish the usual `_dmarc` TXT on each sending domain with
|
||||||
`rua=mailto:…` pointing at the address the domain shows. Receivers deliver to the
|
`rua=mailto:…` pointing at the address the domain shows. Receivers deliver to the
|
||||||
@@ -1313,7 +1536,11 @@ sees the *Ingest* box — whether ingest works, the last report received, report
|
|||||||
kept this week and parse failures. Open a domain's roll-up from the list or
|
kept this week and parse failures. Open a domain's roll-up from the list or
|
||||||
from *DMARC reports* in the domain page's menu. A user with the *domain* role
|
from *DMARC reports* in the domain page's menu. A user with the *domain* role
|
||||||
sees the reports of the domains assigned to them, without the *Ingest* box.
|
sees the reports of the domains assigned to them, without the *Ingest* box.
|
||||||
Summaries are pruned (500 kept for each domain, 90 days max).
|
Summaries are pruned (500 kept for each domain, 90 days max). A report's
|
||||||
|
page is `/outbound/dmarc/reports/<id>` and a domain's roll-up
|
||||||
|
`/outbound/dmarc/domains/<id>`. A report's day - in the lists and its title -
|
||||||
|
is the reporter's, counted in UTC as reports count their days; its page shows
|
||||||
|
the window's times in the panel's zone, with a line naming that day.
|
||||||
|
|
||||||
A message that is not a report the relay keeps — unreadable as an aggregate
|
A message that is not a report the relay keeps — unreadable as an aggregate
|
||||||
report, about a domain not sent from here, or delivered to another domain's
|
report, about a domain not sent from here, or delivered to another domain's
|
||||||
@@ -1436,12 +1663,13 @@ DMARC counts that signature only when its domain aligns with the domain of
|
|||||||
signature as another of its domains, or as the server itself.
|
signature as another of its domains, or as the server itself.
|
||||||
|
|
||||||
**Lines of more than 998 bytes.** The signer signs the message as the
|
**Lines of more than 998 bytes.** The signer signs the message as the
|
||||||
application handed it over. Postfix's SMTP client breaks a body line longer
|
application handed it over. Postfix's SMTP client breaks a line longer
|
||||||
than 998 bytes when it delivers the message (`smtp_line_length_limit`),
|
than 998 bytes, in the body or in a header, when it delivers the message (`smtp_line_length_limit`),
|
||||||
after the signature was made, and the receiver's check of the signature
|
after the signature was made, and the receiver's check of the signature
|
||||||
then fails. Such a message is still sent; the container log names its
|
then fails. Such a message is still sent; the container log names its
|
||||||
domain and login (`dkim-signer: example.com (login "shop"): a body line
|
domain and login (`dkim-signer: example.com (login "shop"): a body line
|
||||||
longer than 998 bytes - ...`). Mail libraries keep lines short with
|
longer than 998 bytes - ...`, or `a line of the Subject header is longer
|
||||||
|
than 998 bytes - ...`). Mail libraries keep lines short with
|
||||||
quoted-printable or base64; an application that writes its own body has to
|
quoted-printable or base64; an application that writes its own body has to
|
||||||
as well. A message whose header section is over 1 MiB is refused,
|
as well. A message whose header section is over 1 MiB is refused,
|
||||||
`552 5.3.4`.
|
`552 5.3.4`.
|
||||||
@@ -1521,9 +1749,10 @@ gateway, not its own address (see [Rate limiting — level
|
|||||||
1](#rate-limiting--level-1-ip-backstop)).
|
1](#rate-limiting--level-1-ip-backstop)).
|
||||||
|
|
||||||
It is **not an authentication boundary.** The SASL login itself still succeeds;
|
It is **not an authentication boundary.** The SASL login itself still succeeds;
|
||||||
the check runs in the journal-milter, on the same fail-open path as level 2
|
the check runs in the journal-milter and is fail-open like level 2 (below):
|
||||||
(below), so if that milter does not answer the restriction is skipped rather
|
if that milter does not answer, or its lookup hits a store error, the
|
||||||
than enforced. Use it to narrow where an application may send from, not to contain a
|
restriction is skipped rather than enforced. A message whose client address
|
||||||
|
Postfix did not pass is refused, not let through. Use it to narrow where an application may send from, not to contain a
|
||||||
leaked password — for that, regenerate the password.
|
leaked password — for that, regenerate the password.
|
||||||
|
|
||||||
**Level 2 — application** — optional override of the domain limit for one
|
**Level 2 — application** — optional override of the domain limit for one
|
||||||
@@ -1544,8 +1773,9 @@ hours and on demand via **Recalculate now**.
|
|||||||
|
|
||||||
**Level 2 is best-effort, not a guarantee.** It runs inside the
|
**Level 2 is best-effort, not a guarantee.** It runs inside the
|
||||||
journal-milter and is deliberately fail-open: if the rate-limit lookup hits
|
journal-milter and is deliberately fail-open: if the rate-limit lookup hits
|
||||||
a store error, or the connecting client's IP is not available to the
|
a store error, level 2 is skipped and the message is accepted rather than
|
||||||
milter, level 2 is skipped and the message is accepted rather than held up.
|
held up. The client address plays no part: a ceiling is on a domain or an
|
||||||
|
application, wherever its mail comes from.
|
||||||
The same applies if the journal-milter itself does not answer — Postfix is
|
The same applies if the journal-milter itself does not answer — Postfix is
|
||||||
configured to accept rather than refuse it when it cannot reach it. (Since
|
configured to accept rather than refuse it when it cannot reach it. (Since
|
||||||
`2.4.0` a panel that is down altogether makes Postfix refuse mail with a
|
`2.4.0` a panel that is down altogether makes Postfix refuse mail with a
|
||||||
@@ -1556,9 +1786,9 @@ the only one of the two that does not depend on the panel process.
|
|||||||
|
|
||||||
### Outbound log
|
### Outbound log
|
||||||
|
|
||||||
**Outbound › Log** (`/outbound/log`) is a searchable send log with server-side
|
**Outbound › Log** (`/outbound/log`) is the send log, with server-side
|
||||||
filters by domain, application, status and day (from and to, days of the container's zone, as on
|
filters by domain, application, status and day (from and to, days of the container's zone, as on
|
||||||
the Inbound log); the status *rejected* is the three kinds of refusal
|
the Inbound log); the status *rejected* is the four kinds of refusal
|
||||||
together, which the table tells apart by their tags. A user with the *domain*
|
together, which the table tells apart by their tags. A user with the *domain*
|
||||||
role sees only the rows of their domains. The filters are kept by the pages,
|
role sees only the rows of their domains. The filters are kept by the pages,
|
||||||
the refresh and the way back from a message, and the log refreshes its rows
|
the refresh and the way back from a message, and the log refreshes its rows
|
||||||
@@ -1575,19 +1805,20 @@ the row as a failure rather than show a delivery it cannot evidence, and
|
|||||||
counts that it did. A
|
counts that it did. A
|
||||||
refused row says why in its tag: `rate-limited` (a [level-2 rate
|
refused row says why in its tag: `rate-limited` (a [level-2 rate
|
||||||
limit](#rate-limiting--level-2-domain-and-application)), `wrong client` (the
|
limit](#rate-limiting--level-2-domain-and-application)), `wrong client` (the
|
||||||
application's client IP list) or `wrong From` (see [Who an application may
|
application's client IP list), `wrong From` or `wrong Sender` (see [Who an
|
||||||
send as](#who-an-application-may-send-as)); a row refused before `2.4.0`
|
application may send as](#who-an-application-may-send-as)); a row refused before `2.4.0`
|
||||||
says `rejected`. A refused message's page shows what Postfix wrote about the
|
says `rejected`. A refused message's page shows what Postfix wrote about the
|
||||||
refusal: for `wrong From` the lines of the queue id the message had, for the
|
refusal: for `wrong From` and `wrong Sender` the lines of the queue id the
|
||||||
other two the one `NOQUEUE` line; a row refused before `2.5.1` says the line
|
message had, for the other two the one `NOQUEUE` line; a row refused before `2.5.1` says the line
|
||||||
was not kept, and one the panel wrote from `mail.log` after a restart - it
|
was not kept, and one the panel wrote from `mail.log` after a restart - it
|
||||||
had stopped before writing the refusal itself - says so;
|
had stopped before writing the refusal itself - says so;
|
||||||
*Details* opens that row's own page (`/outbound/log/{id}`). That page carries
|
*Details* opens that row's own page (`/outbound/log/{id}`). That page carries
|
||||||
the sending domain, the application it was submitted under, the Postfix
|
the sending domain, the application it was submitted under, the Postfix
|
||||||
queue id and the journal id, beside the message's history — when it was
|
queue id and the journal id, beside the message's history — when it was
|
||||||
accepted and what Postfix later reported for the recipient. A `deferred`
|
accepted and what Postfix later reported for the recipient. A `deferred`
|
||||||
or `bounced` row includes this Postfix's retry intervals (first delay,
|
row includes this Postfix's retry intervals (first delay, backoff cap, queue
|
||||||
backoff cap, queue lifetime), the same numbers the Outbound queue shows; users
|
lifetime), a `bounced` one the queue lifetime, the same numbers the Outbound
|
||||||
|
queue shows; users
|
||||||
with the *domain* role see them here because they cannot open the queue. Under
|
with the *domain* role see them here because they cannot open the queue. Under
|
||||||
both sit the `mail.log` lines for its queue id: the connection to the
|
both sit the `mail.log` lines for its queue id: the connection to the
|
||||||
receiving server, the server's reply, and the status that reply was filed
|
receiving server, the server's reply, and the status that reply was filed
|
||||||
@@ -1597,7 +1828,7 @@ the row is deleted by retention, and not while another row carries the same
|
|||||||
queue id (Postfix reuses ids); a
|
queue id (Postfix reuses ids); a
|
||||||
message accepted before `2.2.0` shows them only while they are still in the
|
message accepted before `2.2.0` shows them only while they are still in the
|
||||||
current file, and the page says so when there are none. Retention is set under **Server › Settings** (global
|
current file, and the page says so when there are none. Retention is set under **Server › Settings** (global
|
||||||
role); `SEND_LOG_RETENTION_DAYS` in `.env` is only the initial default until it
|
role; with the inbound relay on, the Inbound log's has a field of its own there); `SEND_LOG_RETENTION_DAYS` in `.env` is only the initial default until it
|
||||||
is changed there.
|
is changed there.
|
||||||
|
|
||||||
### Exporting and importing a single domain
|
### Exporting and importing a single domain
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user