Most contact forms and chat endpoints don't need Redis-backed, distributed rate limiting on day one. A basic fixed-window limiter keyed by IP, held in memory on the server process, stops the overwhelming majority of casual abuse and spam bots for effectively zero infrastructure cost.
The catch nobody mentions
That in-memory bucket lives on one server instance. The moment your app scales horizontally — multiple serverless instances, multiple containers behind a load balancer — each instance keeps its own separate count. A limiter set to 5 requests per 10 minutes effectively becomes 5 times however many instances are running, since a request can land on any of them.
How to tell when you have crossed the line
If your traffic is low enough that you are running on a single instance, or the endpoint being protected is low-stakes (a contact form, not a login endpoint), in-memory is a reasonable default. The moment you are protecting something sensitive — authentication, payment actions, an AI endpoint with a real per-request cost — or you know you are running multiple instances, move the counter to somewhere shared: Redis (Upstash is a common low-effort choice on serverless), or your database with a short TTL.
What we actually recommend
Ship the in-memory version first if you're moving fast — it's not a security hole, it's a scoped trade-off. But say so explicitly in the code, and treat 'we added a real edge-level limiter' as a task on the list before the endpoint it protects starts costing real money per abused request.
Have a similar problem to solve?
Tell us what you're building and we'll tell you exactly how we'd approach it.
Start Your Project