Build Inoreader
YESreplaces $10.38/mosaves $124.56/yrback to the verdict
A reader that fetches feeds on a schedule with conditional requests so unchanged feeds cost almost nothing, identifies items stably so an edited post does not reappear as new, applies keyword and author rules to tag or mute, and sends alerts for the handful of things you actually want to be interrupted for.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the fetcher and the reader.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - have readyfree
Why Malformed feeds, feeds without identifiers and feeds that change identifiers on edit all exist in any real list and none of them appear in a handful of test feeds.
Get it Export from whatever you read now. If you read nothing, collect thirty feeds you would actually subscribe to.
Verify
At least thirty real feeds import - have readyfree
Why Phase 2's identity work needs one feed without item identifiers, one that changes them on edit, and one with repeated titles. Without them the fallback chain is untested.
Get it They are common; look through your own list for feeds whose items reappear as unread, which is the symptom.
Verify
All three are identified and their behaviour recorded - have ready5 to 10 US dollars a month, or free on hardware you own
Why A reader that only fetches while your laptop is open misses things and then fetches a burst, which is both worse for you and ruder to publishers.
Get it A small virtual server or a machine at home that stays on.
Verify
The fetcher runs on a schedule and survives a reboot - API keyfree tiers cover this volume easily
Why Phase 5 only. Everything before it works without any delivery.
Get it A transactional email provider with a verified domain, or a webhook into a chat tool you already use.
Verify
A test alert arrives
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- feeds(id, url, site_url, title, etag, last_modified, last_fetched_at, next_fetch_at, failure_count, interval_minutes) - items(id, feed_id, item_key, title, author, url, published_at, fetched_at, content, content_hash) - reads(item_id, read_at), stars(item_id, starred_at) - rules(id, priority, scope_feed_id, match_field, pattern, action, tag) · action is tag, mute or alert - tags(id, name), item_tags(item_id, tag_id) - fetch_log(feed_id, at, status, bytes, items_new, note) · so a feed that quietly stopped is visible - The item key is unique per feed. Choosing it well in Phase 2 is what stops the reader filling with duplicates.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DB_PATH | required | ./data/reader.db | Feeds, items and rules. One file, and the whole backup. |
DEFAULT_FETCH_MINUTES | required | 30 | Base polling interval. Conditional requests make this cheap, but it is still someone's server. |
MAX_FETCH_MINUTES | required | 720 | Longest interval after repeated failures or a consistently unchanged feed. |
ALERT_BATCH_MINUTES | required | 60 | How often alerts are delivered. Batching is what prevents notification fatigue. |
ALERT_MAX_PER_BATCH | required | 20 | Cap per delivery. Exceeding it reports a count rather than sending everything. |
QUIET_FEED_DAYS | required | 120 | How long without new items before a feed is reported as quiet rather than working. |
The build, in order
Fetching without being rude
Feeds update, and an unchanged feed costs almost nothing.
ETag and If-Modified-Since from the last response. Handle 304 by doing nothing.
Files
src/fetch.jsExponential, respecting a Retry-After header when one is sent.
Files
src/backoff.jsStatus, bytes, new items. A feed that stopped publishing and one that stopped working look identical without this.
Files
src/log.js
done when · tick each as it passeswatch out- Fetching a feed in full every fifteen minutes with no conditional headers wastes the publisher's bandwidth and yours, and enough readers doing it is why some feeds rate limit or block. Send the conditional headers, handle the not-modified response, and confirm you are getting one by looking at the byte counts.
Item identity, which decides everything
An edited post does not reappear as new, and two feeds carrying the same post are two items rather than a mess.
Identifier, then link, then title and date. Record which strategy each feed used.
Files
src/identity.jsA content hash so an edit updates the item rather than inserting a second one.
Files
src/items.jsOne with no identifiers, one that changes them on edit, one with duplicate titles. All three exist.
Files
test/identity.test.js
done when · tick each as it passeswatch out- Some feeds change an item's identifier when the post is edited, which makes every correction appear as a new unread item. Prefer the link when the identifier looks unstable, and record which strategy a feed uses so it stays consistent.
- Some feeds omit identifiers entirely and some reuse them across different posts. Both exist in the wild and both need a fallback, which is why the key is derived rather than taken.
Reading at volume
Three hundred feeds is navigable rather than a wall of unread.
Three hundred feeds and a couple of hundred thousand items. Every performance claim depends on it.
Files
scripts/generate.jsIncremental or indexed. The sidebar renders on every navigation.
Files
src/counts.jsAcross titles and content, updated on insert.
Files
src/search.js
done when · tick each as it passeswatch out- Unread counts computed by counting rows on every render make the sidebar slow once you have a few hundred thousand items, and the sidebar renders constantly. Maintain counts incrementally or index for them, and measure against a realistic archive rather than a fresh one.
Rules that tame the volume
The things you care about surface and the noise disappears without unsubscribing.
In priority order, recording the acting rule on the item.
Files
src/rules.jsOut of unread, still findable by search. Deleting is a different, rarer intention.
Files
src/rules.jsA rule that has never matched is usually a typo, and it is invisible without a report.
Files
src/rules.js
done when · tick each as it passeswatch out- Silently applying a new rule to the whole archive can hide thousands of already-read items or resurface them, and either is disorienting. Apply going forward, and make a retroactive run an explicit action that reports how many items it will affect.
Alerts worth being interrupted by
You are notified about the few things that matter and not about everything else.
One message per period with a maximum. Report when the cap was applied.
Files
src/alerts.jsTitles, feeds and links. Enough to judge without opening anything.
Files
src/alerts.jsOne action from the notification or the rule list. Alert fatigue is the failure mode here.
Files
src/rules.js
done when · tick each as it passeswatch out- An alert rule matching more than expected produces a burst of notifications that trains you to ignore all of them, and the rule you actually cared about is lost with them. Batch, cap per period, and report when a rule was capped so you know to narrow it.
Import, export and keeping it runningproduct builder
You can arrive with your subscriptions and leave with everything.
Import and export with folders. It is how people arrive and how they leave.
Files
src/opml.jsJSON, complete, re-importable. Stars in particular represent real curation.
Files
src/export.jsFailing and quiet are different problems. A quiet feed usually moved.
Files
src/health.js
done when · tick each as it passeswatch out- A feed that returns success but has published nothing for six months looks identical to a working feed in a status check, and it is usually a site that moved its feed URL. Report quiet feeds separately from failing ones, because the fix is different.
That is the whole plan for Inoreader. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Feed handling at hosted scale with per-publisher workarounds.
- Mobile applications with offline sync.
- The integration catalogue.
- Full text extraction for summary-only feeds.
- enterprise-scale feed handling
- mobile apps
- integrations
- article translation
- polished search
- support
- Full text fetch for a small allowlist of sites you read closely
- A daily digest of the highest-priority tags, replacing per-item alerts
- Duplicate detection across feeds, since syndicated posts arrive several times
Need the files? The project pack on the verdict page hands your agent the whole brief · more rss & research.