← All posts
Engineering

How we poll Reddit every 60 seconds without hitting rate limits.

An engineering note on the polling architecture behind Subportly. Adaptive intervals, sharing polls across customers, and the one trick that saved us from a 4-figure monthly API bill.

Reddit doesn’t have webhooks. Pretending it does is the most expensive thing you can do in this product.

When we started Subportly, the polling architecture took a rough sketch on a whiteboard and an afternoon of code. The naive version worked for a single test account.

It did not work for the hundredth, and the gap between “works for one” and “works for one hundred” is the most interesting engineering problem in the product. Here is how we closed it.

Constraints

Three constraints set the design space.

1. Reddit’s commercial API has rate limits per OAuth client. Roughly 600 requests per minute as of early 2026, with bursting allowed but penalized if sustained. This is generous for a single account. It is not generous for a service polling thousands of accounts in parallel.

2. There are no webhooks. Reddit will not push events to us. Every “new DM” detection has to come from a poll.

3. Customers expect new messages within ~1 minute. The product loses its punch if a DM sits for 5 minutes before showing up in their queue. The whole point is fast triage.

So the design question becomes: how do you build a system that can poll thousands of accounts every minute, across multiple endpoints each, without exhausting the rate-limit budget or burning through an API agreement?

The naive version

Every account, every minute, hit four endpoints (DMs, modmail, mentions, comment replies). Take 100 customers averaging 3 accounts each: 300 accounts × 4 endpoints × 60 polls per hour = 72,000 calls per hour, or 1,200 per minute.

This breaks two constraints. It exceeds rate limits. It also produces a giant API bill, because almost all of those calls return zero new items: paying for empty responses. That is the version that never ships, so the system was designed past it from the start.

The four moves that fixed it

1. Adaptive polling intervals based on account activity

Most accounts are quiet most of the time. A brand account at a 5,000-customer SaaS company gets maybe 2-5 messages a day. Polling that account every 60 seconds is wasting 99% of the calls.

Every poll is instrumented with “did this return anything new?”, because the design leans on a property of how Reddit accounts actually behave: activity is bimodal. Accounts are either busy (multiple messages a day) or quiet (one message a week). The middle is rare.

So we made polling adaptive:

In the worked example above, this alone cuts poll volume by roughly 70%, with no perceptible change in customer-facing latency.

2. Endpoint-specific polling cadences

The four endpoints don’t all need the same cadence.

The four endpoints are split into separate poller pools, each with its own cadence rules, which defers the lower-priority endpoints and takes another meaningful slice off the volume.

3. Shared polling for shared subreddits

This is the trick that mattered most.

When Subportly tracks “every mention of $brand on Reddit,” what it is actually doing is monitoring the subreddits where that brand’s customers hang out. Different customers will often care about the same subreddits: multiple SaaS brands all tracking r/SaaS, multiple e-commerce brands all tracking r/ecommerce.

Polling those subreddits separately per customer would be redundant, so the mention-tracking layer polls each subreddit once per cadence, then fans out matches to whichever customers care about which keywords.

The economics compound: N brands tracking the same subreddit cost one fetch instead of N, so every customer who shares a community with another makes both cheaper to serve.

4. Backoff and circuit breakers

When Reddit’s API has a hiccup, every poll starts failing simultaneously. The naive version of polling retries immediately, which makes the API hiccup worse. The disciplined version backs off.

We use exponential backoff with jitter at the per-account level (one account failing doesn’t pause everyone), and a global circuit breaker that triggers if more than 10% of polls fail in a 30-second window (because that means Reddit, not us, is having a bad time).

The backoff means we don’t dogpile failures. The circuit breaker means we recover gracefully when Reddit recovers. Neither is novel infrastructure. Both are missing from naive implementations.

The architecture

The end result is a polling system that:

The whole thing runs on Fly.io, with persistent state in Postgres and a fast cache for the per-account last-seen-ID tracking that drives polling.

What we did not do

For completeness.

We did not use Reddit’s “stream” features. Reddit’s PRAW library has a stream interface that abstracts polling. Under the hood it polls. We rejected it because we needed control over the cadence to do the adaptive logic.

We did not build a “real-time” claim. The product latency is 60-90 seconds for new messages. We could chase 30 seconds with more aggressive polling, but the marginal value is zero (no human’s eyes are on the inbox at 30-second resolution) and the API cost would double.

We did not try to do this with scrapers. Several “Reddit dashboard” startups went the scraping route. They are mostly dead now. The web-scraping path is unsustainable, against terms of service, and architecturally fragile.

We did not over-engineer the dedup logic. The simple version (track last-seen item ID per account, only return items newer than that ID) handled 99% of cases. The remaining 1% (deleted-then-restored items, edits, weird modmail re-orderings) we handled in the application layer, not in the poller.

What this enables

This is what makes the rest of the product feel real.

Watch the inbox and a new DM appears about a minute after it lands on Reddit. That feels real-time enough to act on. Log in once a day instead and the entire backlog is there, every account, synced, with read state preserved across sessions. None of that is glamorous infrastructure. All of it has to be there.

The polling system is a modest share of the backend by line count and an outsized share of the engineering time behind it. That ratio sounds wrong until you build one.

If you are doing this in-house

Two pieces of advice.

Start with the adaptive intervals from day one. The naive uniform-polling version works fine for the first few accounts and then gets very expensive very fast. Build the adaptive logic before you scale, not after the bill arrives.

Get the rate-limit observability in early. You need to know, in real time, how close you are to your quota. We use Prometheus metrics on every poll and have a Grafana panel that shows our quota burn rate. Without that, you are flying blind.

The rest of the engineering is secondary. The polling architecture is the engineering project. Get it right, and the product on top of it feels like magic. Get it wrong, and no amount of UI polish will save you.


Subportly handles all of this so you don’t have to. One inbox, every account, real-time enough to act on. See how it works.

Keep reading