A little trick and gotcha with Exim ratelimits
In light of recent events, I've been exploring Exim's features for ratelimiting incoming email. In the process I have found something that I feel like documenting. Suppose you're trying to establish what ratelimit settings you actually want (ones that won't cut off your users). The simple way to do this is to create a do-nothing ratelimit ACL stanza that exists just to log the actual rate volumes, something like:
warn ratelimit = 10 / 10m / per_rcpt / $sender_address log_message = SENDER RATE: $sender_rate
This looks like just what you want, except there's a gotcha; this is not actually measuring what you think it is and what you want. The default Exim behavior of ratelimits is that they are 'leaky'. In Exim terminology, this means that if an actual rate exceeds the limit, it doesn't update the rate counters; in effect, the rate counters stick at the point where they started to go over the limit. This behavior is generally what you want for rates that are actually enforced, but it's definitely not what you want if you're only using ratelimits to explore the actual real rates. In this situation what you'd see is a reported 'sender rate' that was almost always only slightly over 10, regardless of what the real sending rate was.
What you want in this situation is for the ratelimit to be 'strict', where it's updated for every action (whether or not the action pushes the rate over the limit). Then it actually tracks the real sending rate. So you actually want the ACL stanza:
warn ratelimit = 10 / 10m / per_rcpt / strict / $sender_address log_message = SENDER RATE: $sender_rate
(If you're doing ratelimiting, you may or may not want to exclude email to internal addresses. Locally we're going to exclude them, because we mostly care about a compromised user here being exploited to spam outside sites and poison our sender reputation. Spamming our own users is annoying but we can deal with it.)