We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.Privacy Policy

EvilMailEvilMail
FeaturesEverything EvilMail can doTemp Mail for…Temp mail for Discord, ChatGPT & moreNetwork ToolsDNS, WHOIS, port & network toolsCompareEvilMail vs other temp mail services
APIREST API reference & guidesTemp Mail APICopy-paste code in every language
AppsPricingBlog
About UsWho we are & what we buildTrust & SecurityHow we protect your dataFAQAnswers to common questionsContactGet in touch with the team
LoginRegister
EvilMailEvilMail
AppsPricingBlog
LoginRegister
HomeBlogCatching Compromised Mailboxes: Outbound Spam Detection with rspamd and Volume Baselines
Security

Catching Compromised Mailboxes: Outbound Spam Detection with rspamd and Volume Baselines

Your inbound rspamd stack is airtight, and that is exactly why you still get blocklisted. When an attacker logs into port 587 with valid SASL credentials, their spam run is authenticated, trusted, and completely unfiltered. Here is how to put rspamd on the submission path and catch the breach in the first 200 messages.

EvilMail Team
July 21, 2026
11 min read
Catching Compromised Mailboxes: Outbound Spam Detection with rspamd and Volume Baselines
Contents
The blind spot: authenticated submission skips the spam enginePut rspamd on the submission path without breaking legit sendersThe three signals that actually flag a compromiseRate limiting by authenticated user, not by IPTurning your maillog into a per-user volume baselineWiring alerts and automatic quarantineChecklist: outbound spam containment

03:14 — a SASL login lands from an ASN this mailbox has never touched, two continents from where the user actually lives. 03:20 — the account has pushed 4,000 messages through port 587, every one authenticated cleanly, every one addressed to disposable and dictionary-generated domains. 06:00 — your sending IP is on the CBL, Gmail is deferring everything with a 421, and your support queue is filling with legitimate customers asking why their mail is stuck. Your inbound rspamd saw none of it, because authenticated submission is trusted by design. That gap is what this article closes.

Outbound spam from a compromised mailbox is not a content-filtering problem. Your Bayes classifier, your fuzzy hashes, your RBL lookups — all of that runs on the inbound smtpd and never touches a message a valid user submits. It is a detection problem, and you solve it by putting rspamd on the submission path with three signal families and a per-user volume baseline built from your own maillog.

The blind spot: authenticated submission skips the spam engine

Almost every Postfix + rspamd deploy in the wild milters only the inbound service. The submission (587) and smtps (465) services authenticate the sender, decide they are one of yours, and hand the message straight to the queue. This is deliberate and, in isolation, correct — you do not want to bounce your own paying customer because their newsletter tripped BAYES_SPAM. But the moment credentials leak — reused password, phishing, an infostealer on the user's laptop — that trust becomes a liability, because your IP and domain reputation are shared across every mailbox on the platform. One compromised account is not one account's problem. It is a blast radius over everyone you send for.

The submission path with an outbound choke point traditional deploy: authenticated mail bypasses filtering SASL user :587 queue compromised acct valid SASL :587 rspamd proxy worker ratelimit: user selector content score: BAYES / reputation recipient fan-out clean → queue → internet trip 2-of-3 → quarantine + alert + disable SASL smart host

Put rspamd on the submission path without breaking legit senders

Point the submission and smtps services at rspamd's proxy worker milter, and tag the daemon name so rspamd knows this traffic came from an authenticated user. In master.cf:

submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_security_level=encrypt
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
  -o smtpd_milters=inet:localhost:11332
  -o milter_macro_daemon_name=ORIGINATING

Mirror the same -o smtpd_milters line onto the smtps (465) service. The {auth_authen} macro carries the SASL username into rspamd automatically over the milter protocol — that username is the key to everything that follows.

Now tell rspamd that authenticated mail is a distinct policy. The instinct to reject an authenticated spammer outright is wrong: a single false positive means a real customer's message is gone. Raise the reject threshold out of reach and lean on add header, soft reject, and quarantine, so a legitimate burst is delayed, not lost. In /etc/rspamd/local.d/settings.conf:

hcl
settings {
  authenticated {
    authenticated = true;
    apply {
      actions {
        "reject"      = 999.0;
        "add header"  = 8.0;
        "soft reject" = 12.0;
      }
    }
  }
}

Overriding only the action thresholds keeps every symbol — SPF, DKIM, Bayes, fuzzy, reputation — scoring exactly as before; you are changing the consequences, not the scan. Soft reject is the sweet spot for a compromised account: the sending client gets a 451 4.7.1 try again later, so a burst clips flat instead of flooding out, and you buy minutes to react without permanently bouncing anyone.

The three signals that actually flag a compromise

No single metric is trustworthy on outbound. A transactional account legitimately blasts 500 messages an hour; a lawyer legitimately emails 40 strangers a day. You want 2 of 3 signal families to fire before you act.

  • Rate-limit breaches keyed on the authenticated user. The volume ceiling, covered below in depth. This is the highest-leverage control.
  • Content-score inversion. A mailbox whose 30-day history is pure ham suddenly lighting up BAYES_SPAM, FUZZY_DENIED, MIME_BAD, MANY_INVISIBLE_PARTS, FROM_NEQ_ENVFROM, or R_SUSPICIOUS_URL. Per-user Bayes and rspamd's reputation module make the inversion sharp — the delta from the user's own baseline matters more than the absolute score.
  • Recipient-pattern anomalies. RCPT fan-out, distinct-domain count spiking past 30/hour, dictionary-style invalid recipients producing a double-bounce storm, and daytime users suddenly sending at 03:00.

Wire the composite as an rspamd rule that requires at least two families. A force_actions clause then quarantines only on the combined condition, which keeps false positives near zero.

Rate limiting by authenticated user, not by IP

This is the one control most providers get wrong. IP-keyed limits are useless here: your submission service sits behind one or a handful of IPs, users share carrier-grade NAT, and a corporate customer's whole office egresses from a single address. Key on the SASL username. In /etc/rspamd/local.d/ratelimit.conf:

hcl
rates {
  authuser {
    selector = 'user';
    bucket = [
      { burst = 40;  rate = "100 / 1h"; },
      { burst = 20;  rate = "600 / 1d"; }
    ];
  }
}
whitelisted_rcpts = "postmaster,mailer-daemon";

These are token buckets: burst is the instantaneous allowance, rate is the sustained refill. A user can fire 40 messages back-to-back, then settles to 100/hour with a hard 600/day ceiling. Legit bulk and transactional accounts get their own selector with a higher bucket — whitelist by username, never by IP. When the bucket empties, rspamd emits RATELIMIT and the authenticated settings policy turns that into a soft reject. The attacker's 4,000-message run clips at the bucket ceiling instead of escaping.

Turning your maillog into a per-user volume baseline

The ratelimit is your guardrail. It will not catch the *slow burn* — the patient attacker who sends 45 messages an hour, safely under the 100 ceiling, for eight hours straight. That is 360 spam messages from an account whose real median is 4/hour, and the static limit never fired once. The baseline catches it.

Parse Postfix maillog on sasl_username=, keep a rolling 7-day median per user, and alert when a user crosses max(50, 5 × their own median). Top authenticated senders in the last hour is a one-liner:

bash
grep "$(date '+%b %e %H')" /var/log/mail.log \
  | grep -oP 'sasl_username=\K[^,]+' \
  | sort | uniq -c | sort -rn | head

Persist the counters in Redis so the anomaly logic stays cheap: INCR outq:<user>:<yyyymmddHH> on each authenticated send, EXPIRE the key at 8 days, and a small responder job compares the current hour against the rolling median of the trailing week. The relative threshold is what makes this work — a 5× jump on a quiet mailbox trips at 20 messages, long before any absolute floor.

Per-user volume baseline vs compromise spike messages/hour for one authuser over ~3 days 400 50 0 absolute floor = 50 5 × rolling median normal band (2–8/hr) SASL login, new ASN 03:14 soft-reject clips at ceiling

Wiring alerts and automatic quarantine

Detection is worthless if it stays inside rspamd. Emit it. A force_actions rule quarantines on the composite (rate breach plus content inversion), and the built-in Prometheus endpoint on the controller feeds Grafana. In /etc/rspamd/local.d/force_actions.conf:

hcl
rules {
  compromise_quarantine {
    action = "soft reject";
    expression = "RATELIMIT & (BAYES_SPAM | FUZZY_DENIED | MANY_INVISIBLE_PARTS)";
    require_action = ["soft reject", "reject"];
  }
}

Alert in Grafana on the rate of rspamd_actions_total{action="soft reject"} — a step change there is a live compromise almost every time. A lightweight responder cron queries rspamd history in Redis for repeat offenders and pages ops. And keep the external feedback loops wired: Google Postmaster Tools, Microsoft SNDS, and ARF/FBL parsing catch reputation damage that started before your internal signals did.

When an account trips, the runbook is fixed and fast:

bash
# 1. kill submission for the account (Dovecot / your auth DB)
#    set the mailbox inactive so SASL auth fails immediately
# 2. rotate the password, invalidate app-specific passwords
# 3. purge the queued spam by sender
mailq | grep -B1 'attacker@yourdomain' | grep '^[A-F0-9]' \
  | awk '{print $1}' | tr -d '*!' | postsuper -d -
# 4. audit attacker persistence — the step everyone forgets

Step four is where breaches survive a password reset. Attackers add a ~/.dovecot.sieve forward rule, a hidden alias, or an OAuth app password so they keep receiving replies and can re-establish access. Check managesieve rules, forwarding aliases, and app-specific credentials before you call it closed.

Checklist: outbound spam containment

  • Submission (587) and smtps (465) both milter to the rspamd proxy worker with daemon_name=ORIGINATING.
  • An authenticated settings policy with reject raised to 999 — soft reject and quarantine, never bounce a first offense.
  • Ratelimit keyed on the user selector with burst plus sustained buckets; bulk senders whitelisted by username, never by IP.
  • A per-user 7-day median baseline job running against sasl_username=, alerting on max(50, 5× median).
  • force_actions quarantine wired to the 2-of-3 composite.
  • Grafana alert on the rspamd_actions_total{action="soft reject"} rate.
  • FBL/ARF, Google Postmaster, and Microsoft SNDS monitored for early reputation loss.

What good looks like: mean time to detect under five minutes and under a few hundred messages, caught by your own metrics — not by Gmail's 421 and a support queue that already knows before you do.

Share
𝕏 TwitterLinkedInReddit
PreviousRspamd + Postfix over Milter: the Proxy Worker Done RightNextSpamAssassin Custom Rules and Meta Scoring: Catch the Pattern, Not the Word

Related Articles

Security

Anti-Abuse Controls That Keep an Alias Service Off the Blocklists

13 min read
Security

Encrypting Dovecot Mailboxes at Rest with mail_crypt: Global vs Per-User Keys

12 min read
Security

Zero-Downtime Let's Encrypt for Postfix and Dovecot: Automated Renewals That Actually Reload

11 min read
EvilMailEvilMail

EvilMail — free temp mail and disposable email service. No registration required.

Product

FeaturesPricingAPITools

Resources

BlogFAQGlossaryRegisterAbout Us

Legal

Privacy PolicyTerms of ServiceTrust & SecurityContact
© 2026 EvilMail. All rights reserved.
Response runbook documented: disable SASL, rotate password, postsuper -d by sender, audit sieve/aliases/app passwords.