Add optional inbound relay (backup-MX) behind INBOUND_RELAY_ENABLE.
test / test (push) Waiting to run

Port 25 accepts only configured domains and listed recipients, then forwards to an upstream; the outbound path is unchanged when the flag is off.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 23:17:30 +03:00
parent 6218540211
commit 0d98d92642
49 changed files with 2495 additions and 86 deletions
+15
View File
@@ -11,6 +11,21 @@ SELFPOST_HOSTNAME=mail.example.com
# primary 465/smtps listener.
SUBMISSION_ENABLE=false
# Optional inbound relay (backup-MX / forwarder). When true, Postfix accepts
# mail on port 25 only for domains configured in the panel and forwards them
# to the upstream you set. Off by default — the outbound path is unchanged.
INBOUND_RELAY_ENABLE=false
# Optional milter on the inbound listener only (not 465/587). Empty = off.
# Example with deploy/antispam/docker-compose.antispam.yml:
# INBOUND_ANTISPAM_MILTER=inet:antispam:11332
# INBOUND_ANTISPAM_MILTER_ACTION=accept
# Coarse per-client-IP limit and message size on inbound smtpd (port 25).
# INBOUND_RATE_LIMIT_MESSAGES_PER_IP=20
# INBOUND_MESSAGE_SIZE_LIMIT=26214400
# Level-1 backstop rate limit (anvil) — see docs/guide.md "Rate limiting".
RATE_LIMIT_MESSAGES_PER_IP=100
RATE_LIMIT_WINDOW_SECONDS=3600
@@ -0,0 +1,29 @@
# Optional anti-spam sidecar for inbound relay (docs/plans/inbound-relay.md).
#
# SelfPost does not ship or start an anti-spam engine. This fragment brings up
# rspamd next to SelfPost and points the inbound smtpd milter at it. Merge it
# only when you want filtering on port 25:
#
# docker compose -f docker-compose.yml -f antispam/docker-compose.antispam.yml up -d
#
# 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.
#
# Pin the rspamd tag the same way the SelfPost image is pinned; bump it
# deliberately. Default milter action is fail-open (accept) so a down sidecar
# does not block backup-MX. Set INBOUND_ANTISPAM_MILTER_ACTION=tempfail to
# defer instead.
services:
selfpost:
environment:
INBOUND_RELAY_ENABLE: "${INBOUND_RELAY_ENABLE:-true}"
INBOUND_ANTISPAM_MILTER: "${INBOUND_ANTISPAM_MILTER:-inet:antispam:11332}"
INBOUND_ANTISPAM_MILTER_ACTION: "${INBOUND_ANTISPAM_MILTER_ACTION:-accept}"
depends_on:
- antispam
antispam:
image: rspamd/rspamd:3.11
restart: unless-stopped
# No host ports: only SelfPost talks to the milter on the compose network.
+1
View File
@@ -23,6 +23,7 @@ services:
ports: !override
- "465:465"
- "587:587"
- "25:25"
environment:
# Caddy names certificate files after the hostname, not
# fullchain.pem/privkey.pem like certbot — override the base file's
+9 -1
View File
@@ -33,6 +33,11 @@ services:
# Set to true to also publish RFC 6409 submission (587/STARTTLS)
# alongside the primary 465/smtps listener (spec 5).
SUBMISSION_ENABLE: "${SUBMISSION_ENABLE:-false}"
INBOUND_RELAY_ENABLE: "${INBOUND_RELAY_ENABLE:-false}"
INBOUND_ANTISPAM_MILTER: "${INBOUND_ANTISPAM_MILTER:-}"
INBOUND_ANTISPAM_MILTER_ACTION: "${INBOUND_ANTISPAM_MILTER_ACTION:-accept}"
INBOUND_RATE_LIMIT_MESSAGES_PER_IP: "${INBOUND_RATE_LIMIT_MESSAGES_PER_IP:-20}"
INBOUND_MESSAGE_SIZE_LIMIT: "${INBOUND_MESSAGE_SIZE_LIMIT:-26214400}"
# Level-1 backstop rate limit (anvil, spec 5.5, 7.4); per-domain/app
# limits (level 2) are configured later from the panel itself.
RATE_LIMIT_MESSAGES_PER_IP: "${RATE_LIMIT_MESSAGES_PER_IP:-100}"
@@ -76,6 +81,9 @@ services:
# 587 is mapped even when SUBMISSION_ENABLE=false; Postfix listens only
# when the variable is true — see docs/guide.md "Published ports".
- "587:587"
# 25 is mapped even when INBOUND_RELAY_ENABLE=false; Postfix listens only
# when the variable is true.
- "25:25"
- "127.0.0.1:8080:8080"
# Hardening (spec 10 p.6). SelfPost's entrypoint still needs to run as
# root very briefly to fix /data ownership and normalise permissions
@@ -85,7 +93,7 @@ services:
# self-healing. What IS applied: no privilege escalation past what the
# image already grants, and every Linux capability dropped except the
# small set the root startup phase and Postfix/OpenDKIM genuinely need:
# - NET_BIND_SERVICE — bind 465/587 (and 25 outbound) below 1024;
# - NET_BIND_SERVICE — bind 465/587 and inbound 25 below 1024;
# - CHOWN — entrypoint re-owns /data (bind mount) to `panel`;
# - FOWNER — entrypoint then chmods those now panel-owned /data
# dirs/files while still root (owner-check bypass);
+1
View File
@@ -23,6 +23,7 @@ services:
ports: !override
- "465:465"
- "587:587"
- "25:25"
# No host publish for 8080 here: nginx reaches it over the compose
# network at selfpost:8080 instead (see nginx.conf.example).
volumes: !override
@@ -15,6 +15,7 @@ services:
ports: !override
- "465:465"
- "587:587"
- "25:25"
volumes: !override
- ./data:/data
- .:/selfpost-deploy:ro