Review pass over `1.4.0`…`1.9.1`: port 25 no longer shares the submission milter chain, DMARC reports are validated before they are stored, and the auto rate limit is correct for a non-default level-1 window. Schema migration `0010` (index only). Upgrading is a tag bump. Added: - SQLite migration `0010_send_log_app_login_index.sql` — - DMARC aggregate reports are accepted as **zip** as well as gzip or plain XML - e2e: `checkPort25ConfigCombinations` regenerates the Postfix configuration Changed: - README hero line and a **Positioning** block in `docs/product.md` record the Fixed: - The setup account's password is hashed at bcrypt cost `12` like every other - Login/setup rate limiter: protecting blocked buckets from eviction is now - `postqueue -p` output is bounded to 4 MiB (the timeout alone left the size of - [schema-migrations.md](docs/schema-migrations.md): the schema section was - postqueue reader is now time-bounded: `postqueue -p` runs under a 5-second … (11 more in CHANGELOG) Changed: - [roadmap.md](docs/roadmap.md) now carries everything the 2026-08-19 code - Extracted `internal/configsafe` — the empty-value and forbidden-character - Build, deploy and migration comments no longer cite the archived - Dropped the `parseDomainRateLimitForm` / `parseAppRateLimitForm` pass-through - Extracted shared helpers: `internal/atomicfile` (atomic temp-write+rename) and … (7 more in CHANGELOG) Removed: - Unused legacy asset `docs/assets/icon.png` (superseded by the four brand SVGs - Plan documents for already-shipped features: `domain-stats-auto-ratelimit`, Full detail: CHANGELOG.md (1.9.2).
4.9 KiB
SelfPost — product boundaries
What this file is. Stable product definition for SelfPost v1.0: purpose, deployment assumptions, explicit out-of-scope items, and the multi-domain model. User-facing overview in README.md; install and operations in guide.md; as-built technical detail in architecture.md.
Purpose
SelfPost is a self-hosted SMTP relay with a web control panel, shipped as a single Docker image. It sends mail directly from your server with per-domain DKIM signing; an optional inbound relay forwards mail on port 25 without storing it.
Primary workflow: configure the relay once (domains, DKIM, SASL applications), then point scripts and apps at the SMTP endpoint. SelfPost delivers as the configured sending domain.
Positioning
| Text | |
|---|---|
| Stamp subtitle | SELF-HOSTED SMTP RELAY |
| Wordmark | The stamp's own letter outlines without the stamp (docs/assets/selfpost-wordmark.svg, -ink for light grounds) — for places too small for the stamp, such as the panel header |
| Tagline | YOUR SMTP. YOUR RULES. |
| Hero line (README, GitHub) | Self-hosted SMTP relay with a web panel — one Docker image, no provider. |
Brand assets and metrics: docs/assets/selfpost-proof.html.
Deployment context (fixed assumptions)
These constraints are intentional; changing them requires an explicit product decision:
- VPS or home server — one image for both. Raspberry Pi and similar boards are not a current target but not ruled out forever.
- Send from your own IP (DIY) — no intermediate relay provider. Requires outbound port 25, static IP, and configurable PTR/rDNS from the operator.
- Single container — Postfix, OpenDKIM, and the panel run under one
supervisordinside one image. - Panel is internet-facing — mandatory security requirements in security.md.
Infrastructure the operator provides (not SelfPost features): unblocked outbound TCP 25, static IP, PTR/rDNS, acceptable IP reputation. SelfPost does not detect, bypass, or compensate for missing prerequisites; mail simply fails to deliver when they are absent.
Out of scope
Explicitly excluded to prevent scope creep:
- Inbound mail (IMAP/POP3, mailboxes, delivery to user inboxes)
- Webmail
- Organisations / tenancy beyond global + domain-admin roles; managing multiple sending domains under one global administrator is in scope (see below)
- Inbound antispam/antivirus engines (rspamd, ClamAV, etc.) — SelfPost may expose a milter hook; it does not ship or start a filter
- A custom MTA — Postfix is used as-is
- Dovecot or a full mail stack for SASL — Cyrus SASL (
sasldb2) only
The domain-admin role ships in the current line (global administrator plus
domain administrators with assigned domains). The optional inbound relay
(backup-MX / forwarder on port 25) ships in [1.4.0], off by default behind
INBOUND_RELAY_ENABLE; it is relay/forward, not IMAP/webmail. Send-log
retention in Settings ships in [1.5.0]. 30-day send statistics and
auto level-2 rate limits ship in [1.6.0]. DMARC aggregate report
ingest ships in [1.7.0], off by default behind DMARC_REPORTS_ENABLE.
Items marked candidate in the
roadmap require explicit approval before coding.
Multi-domain model
SelfPost is a multi-domain outbound relay. Two linked entities:
Sending domain
Example: example.com. Has its own DKIM key and selector.
Application (account)
A SASL login/password bound to one domain. A domain may have several applications (e.g. newsletter vs alerts), each with its own credentials, but each may send only from its own domain, never from another.
From-address mode (per application)
Set when creating or editing an application:
- Any address in the domain —
*@example.com(wildcard insmtpd_sender_login_maps). - Explicit address list — only listed From addresses within the domain; anything else is rejected even if it belongs to the same domain.
In both modes the From address must belong to the application's domain.
What the panel manages per domain
- DKIM key + selector (shared by all applications on that domain)
- One or more applications (SASL credentials + From mode + optional rate limits)
- DNS guidance (DKIM TXT; reminders for SPF/DMARC)
Adding a domain does not create an application automatically.
Lifecycle
- Add domain — record, DKIM key, DNS instructions; no application yet.
- Add application — SASL pair (password shown once), From mode, map entries,
postfix reload. - Delete domain — removes DKIM key and all its applications.
- Delete application — removes only that app's SASL and map entries.
This is not multi-tenancy; it is one owner (or a small team with global and domain-scoped roles) operating several sending domains with independent application credentials.