[RELEASE] Email Rate Limit Extension v2 for ISPConfig 3.3 — Rspamd native, no daemon

Discussion in 'Plugins/Modules/Addons' started by Donno, Jun 24, 2026.

  1. Donno

    Donno Member

    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 erl:DATE:<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).
     

    Attached Files:

  2. divinul

    divinul New Member

    This is a really useful extension. Thanks for taking the time to develop and share it with the community.
    I was wondering if there are any plans to make this an official feature of ISPConfig in the future.
    Maybe Till has already considered integrating it into the ISPConfig core
    Email rate limiting has become an essential feature for modern mail hosting. It helps prevent compromised accounts from sending large amounts of spam, protects the server's reputation, and reduces the risk of IP blacklisting. Having it built into ISPConfig would provide a standardized, officially maintained solution that would benefit all users without requiring an additional extension.
    I think this would be a great addition to ISPConfig.
     
    Donno likes this.
  3. till

    till Super Moderator Staff Member ISPConfig Developer

    Email rate limiting is definitely on the to-do list for ISPConfig.
     

Share This Page