Build Inoreader

YESreplaces $10.38/mosaves $124.56/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort weekendthe files for this build are in the project pack

RuntimeNode 22 with SQLiteFetchingConditional requests with ETag and If-Modified-SinceParsingA library handling RSS, Atom and JSON FeedIdentityA stable item key derived per feedRulesOrdered rules over title, content, author and feed

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the fetcher and the reader.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. 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

  3. 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

  4. 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

  5. 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.

VariableNeededExampleWhere the value comes from
DB_PATHrequired./data/reader.dbFeeds, items and rules. One file, and the whole backup.
DEFAULT_FETCH_MINUTESrequired30Base polling interval. Conditional requests make this cheap, but it is still someone's server.
MAX_FETCH_MINUTESrequired720Longest interval after repeated failures or a consistently unchanged feed.
ALERT_BATCH_MINUTESrequired60How often alerts are delivered. Batching is what prevents notification fatigue.
ALERT_MAX_PER_BATCHrequired20Cap per delivery. Exceeding it reports a count rather than sending everything.
QUIET_FEED_DAYSrequired120How long without new items before a feed is reported as quiet rather than working.

The build, in order

  1. Fetching without being rude

    Feeds update, and an unchanged feed costs almost nothing.

    1. ETag and If-Modified-Since from the last response. Handle 304 by doing nothing.

      Files src/fetch.js

    2. Exponential, respecting a Retry-After header when one is sent.

      Files src/backoff.js

    3. Status, 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 passes
    watch 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.
  2. 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.

    1. Identifier, then link, then title and date. Record which strategy each feed used.

      Files src/identity.js

    2. A content hash so an edit updates the item rather than inserting a second one.

      Files src/items.js

    3. One 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 passes
    watch 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.
  3. Reading at volume

    Three hundred feeds is navigable rather than a wall of unread.

    1. Three hundred feeds and a couple of hundred thousand items. Every performance claim depends on it.

      Files scripts/generate.js

    2. Incremental or indexed. The sidebar renders on every navigation.

      Files src/counts.js

    3. Across titles and content, updated on insert.

      Files src/search.js

    done when · tick each as it passes
    watch 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.
  4. Rules that tame the volume

    The things you care about surface and the noise disappears without unsubscribing.

    1. In priority order, recording the acting rule on the item.

      Files src/rules.js

    2. Out of unread, still findable by search. Deleting is a different, rarer intention.

      Files src/rules.js

    3. A 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 passes
    watch 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.
  5. Alerts worth being interrupted by

    You are notified about the few things that matter and not about everything else.

    1. One message per period with a maximum. Report when the cap was applied.

      Files src/alerts.js

    2. Titles, feeds and links. Enough to judge without opening anything.

      Files src/alerts.js

    3. One 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 passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more rss & research.