Email Rate Limit Extension v2 for ISPConfig 3.3 — Rspamd-native, no daemon A rebuild of the v1 email rate limit extension. v2 moves enforcement inside Rspamd — the filter stack ISPConfig 3.3 already ships — and retires the standalone Python policy daemon. The GUI stays the same; the daemon, the /etc/postfix/main.cf edit, and the never-scheduled cron are gone. This rebuild came out of @till's critique of v1: Rspamd already has rate limiting built in, so a custom daemon is unnecessary, heavier, and riskier than "a GUI form that writes the matching config files." v2 takes that fully on board — enforcement is a small custom Lua prefilter running inside Rspamd, and the GUI writes the matching config files. No daemon, no custom TCP port, no check_policy_service wiring, no main.cf edits. What it does - Per-mailbox limit (opt-in): 50/day default, configurable per mailbox + a server-wide default. Only mailboxes with the Rate Limit tab checkbox ON are capped; unchecked mailboxes have no per-user cap (only the global limit applies). - Global (server-wide) limit: 999/day default, configurable. - Per-recipient counting: 1 message × N recipients = N against the cap (To + CC + BCC all count). A 3-recipient send counts as 3. - Hard midnight reset: counters are Redis keys erl:YYYY-MM-DD:<email> and erl:YYYY-MM-DD:global. A new day = new keys = automatic reset. Redis TTL ~26h cleans old days. No cron. - Only accepted messages count: a two-key atomic Redis script checks the limit before incrementing either counter. An over-limit message increments neither — a rejected burst burns no quota on either counter. - Fail-open: if Redis is unreachable or the Lua module errors, mail is allowed (never blocked by a counter outage). - Defer, not reject: over-limit → 4xx soft-reject at SMTP time (the client retries; nothing is lost). - Unauthenticated mail is not counted (no SASL user → skipped). - Distinct SMTP errors: the 4xx names the sender + the actual limit hit ("Your daily limit of N messages reached" for a mailbox cap, "Global daily limit of N reached" for the server cap). - Recent Violations panel: each over-limit attempt is logged to a Redis list (erl:violations) with timestamp, type, user, limit, count, and client IP; the admin panel reads it (newest first, last 24h, capped at 200). How it differs from v1 Aspect | v1 | v2 Enforcement | Python Postfix policy daemon (TCP 10040) | Rspamd Lua prefilter + Redis /etc/postfix/main.cf | edited (mua_sender_restrictions = check_policy_service …) | never touched Daily reset | a cron job (never properly scheduled → broken) | date-keyed Redis keys (automatic) Per-mailbox counter / "sent today" | MySQL er_ratelimit_history | Redis erlATE:<email> Violations audit | MySQL er_ratelimit_violations | Redis list erl:violations (Recent Violations panel) Per-recipient counting | per message (not per recipient) | per recipient Rejected burst | could burn quota | burns nothing (atomic check-then-increment) GUI tabs | per-mailbox + admin | same (kept) Same GUI; the daemon + main.cf edit + broken cron are retired. Requirements - ISPConfig 3.3+ with content_filter = rspamd and Redis (the standard 3.3 setup — Rspamd + Redis are already configured, nothing to add). - Single Redis (Redis Cluster is not supported — the two-key atomic script needs both keys in the same hash slot). Install (staged — mail never stops) chmod +x install_email_ratelimit_v2.sh ./install_email_ratelimit_v2.sh # install — log-only: counts, never defers # observe first (zero risk — nothing is enforced yet): redis-cli get "erl:$(date +%F):global" tail -f /var/log/rspamd/rspamd.log | grep erl # "erl ok" / "erl DEFER" ./install_email_ratelimit_v2.sh enforce # flip to deferring (4xx over-limit) Enable per-mailbox caps in Mail → Mailbox → Rate Limit. Set the global limit + the default per-mailbox limit in System → Server Config → Email Rate Limit. A 1-minute sync cron keeps the Rspamd config + per-user map in sync with GUI saves (idempotent; no-op when already in sync — it's a config-sync cron, not the v1 policy daemon). Migrating from v1 v2 reuses v1's er_mail_user_ratelimit + er_global_ratelimit tables, so your existing per-mailbox limits carry over. Remove only v1's enforcement layer (the daemon + main.cf line + cron), keep the tables, then install v2. The README's "Migrating from v1" section has the exact paths + commands. If you already fully uninstalled v1 (which DROPs all er_* tables), the per-mailbox limits are gone — just install v2 and re-enter them in the GUI. Verified live Per-recipient counting (a 3-recipient send → counter +3), whole-message defer (an over-limit multi-recipient send is refused — none delivered), quota preservation (the rejected burst increments nothing), per-user + global violations in the Recent Violations panel, distinct SMTP messages naming the sender + the limit. Fail-open is by design (every Redis/Lua call is pcall-wrapped) — verify it on your box with systemctl stop redis-server → send a test → must be accepted → systemctl start redis-server. Developed + validated on a live ISPConfig 3.3.0p3 / Rspamd 3.8.1 / Debian 12 server. Limitations - Single Redis only (no Cluster). - Recent Violations is per-server (each mail server logs to its own Redis); on multiserver the master interface sees only the master's violations. - IDN: works as long as mail_user.email (stored punycode) matches task:get_user() (SASL uses the stored email) — verify if you host IDN domains. Rollback ./install_email_ratelimit_v2.sh disable # deactivate (keep files) ./install_email_ratelimit_v2.sh uninstall # remove files (DB tables + Redis counters left) No main.cf restore is needed — v2 never edited it. Clear the Redis counters if you want a clean slate: redis-cli --scan --pattern 'erl:*' | xargs -r redis-cli del. Target: ISPConfig 3.3.0p3, Rspamd 3.8.1, Debian 12. Developed with the assistance of GLM-5.2 (AI coding assistant).