{"id":94,"date":"2026-08-14T05:25:52","date_gmt":"2026-08-14T05:25:52","guid":{"rendered":"https:\/\/pengyaochen.com\/?page_id=94"},"modified":"2026-08-14T05:25:52","modified_gmt":"2026-08-14T05:25:52","slug":"openreader","status":"publish","type":"page","link":"https:\/\/pengyaochen.com\/?page_id=94","title":{"rendered":"OpenReader"},"content":{"rendered":"\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/pengyaoc\/openreader\/main\/docs\/screenshots\/article-list.png\" alt=\"OpenReader article list view, showing the sidebar with RSS\/newsletter sources grouped into folders, unread counts, and the main list of articles\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/pengyaoc\/openreader\">github.com\/pengyaoc\/openreader<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>The open-source, self-hosted alternative to Feedly and Inoreader \u2014 without the monthly bill.<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Problem<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Feedly and Inoreader charge a monthly fee for things that are really just config on a server \u2014 regex filtering, folders, full-text extraction, newsletter ingestion, AI summaries. Feedly AI and Inoreader Intelligence go a step further and resell LLM summaries at a premium on top of that. None of it needs to live behind someone else\u2019s paywall, and none of it needs your reading history sitting in a third party\u2019s database.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Solution: OpenReader<\/h2>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/raw.githubusercontent.com\/pengyaoc\/openreader\/main\/docs\/screenshots\/article-reader.png\" alt=\"OpenReader fullscreen article reader showing the article title, byline, prev\/next navigation, and Summarize\/Star controls in the top bar\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">OpenReader is a self-hosted RSS\/Atom and email-newsletter reader that puts every one of those paywalled features back under your own control \u2014 running on hardware you own, with your data in a SQLite file you own, not a third party\u2019s database. It\u2019s small enough to run on a sub-1GB RAM VM \u2014 it was built and is run daily on a $5\/month 1GB instance, sharing that box with a full WordPress install \u2014 so self-hosting it doesn\u2019t mean paying for a bigger server than the subscription it replaces.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Features<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>RSS &amp; Atom feeds<\/strong> \u2014 add any feed by URL. Standard-compliant parsing (RSS 2.0, Atom, RDF), conditional <code>GET<\/code> (ETag\/Last-Modified) so a routine refresh costs almost nothing against well-behaved servers<\/li>\n\n\n\n<li><strong>Email newsletters (IMAP)<\/strong> \u2014 reads a mailbox with an app password, no OAuth, read-only throughout (SEARCH\/FETCH only \u2014 never STORE\/EXPUNGE\/DELETE)<\/li>\n\n\n\n<li><strong>Content filtering<\/strong> \u2014 per-source regex include\/exclude rules matched against title\/summary\/content\/author\/url. Every kept article stores which rule let it in, so filtering is auditable, not a black box<\/li>\n\n\n\n<li><strong>Full article mode<\/strong> \u2014 fetches and extracts the full page the first time you open a truncated feed item, then caches it forever \u2014 lazy and per-article, not a background sweep of the whole feed<\/li>\n\n\n\n<li><strong>Content summarization<\/strong> \u2014 a Summarize button next to Star, faithful to the article\u2019s own text only (no outside knowledge, no speculation), runs on your own Claude subscription rather than API billing<\/li>\n\n\n\n<li>No scheduler, anywhere \u2014 refresh is a button, summarize is a button, nothing runs on a timer<\/li>\n\n\n\n<li>Dark\/light themes, responsive down to phone width, images proxied server-side<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">How It\u2019s Built<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Backend:<\/strong> Python + FastAPI (Starlette route handlers) with a synchronous refresh loop. RSS\/Atom parsing via <code>defusedxml<\/code>. No scheduler or background jobs \u2014 refresh and summarize are both manual triggers, kept deliberately simple for a single-user reader.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Storage:<\/strong> SQLite, a single file you own end to end \u2014 no managed database, no third party ever sees the reading list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Newsletters:<\/strong> IMAP with an app password instead of OAuth \u2014 no consent screen, no token to refresh. All sources sharing a mailbox folder are batched into one SEARCH\/FETCH pass rather than one per newsletter, and a newly-added source backfills at most 7 days of history so one wide-window source can\u2019t force a much larger re-scan on every routine refresh.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Summarization:<\/strong> shells out to the <code>claude<\/code> CLI on-demand, running against the user\u2019s own Claude subscription rather than metered API billing. Off by default via a hard kill switch \u2014 the endpoint is hidden\/404 and <code>claude<\/code> is never invoked while it\u2019s off.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Frontend:<\/strong> React, a 3-pane shell (sidebar, article list, reader) with keyboard-driven navigation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Hardest Part<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Making the whole thing run comfortably on a 1GB VM shared with a live WordPress install. The <code>claude<\/code> CLI&#8217;s own peak RSS during a single summarize call turned out to be far higher than the FastAPI process&#8217;s baseline \u2014 the first real production call OOM-killed the service at a 300MB memory ceiling, which meant sizing <code>systemd<\/code>&#8216;s <code>MemoryMax<\/code> well past what the app itself needed and measuring the actual subprocess footprint rather than guessing. IMAP batching was the other constraint worth getting right: mail providers can silently throttle a datacenter IP that opens many fresh logins in quick succession, so every refresh opens one IMAP connection and reuses it sequentially across all newsletter sources instead of one connection each.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-css-opacity has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">OpenReader is <a href=\"https:\/\/github.com\/pengyaoc\/openreader\">open source on GitHub<\/a>. If you&#8217;re paying for Feedly or Inoreader and have a spare $5\/month VM, you can be reading your own feeds through it in an afternoon.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>github.com\/pengyaoc\/openreader The open-source, self-hosted alternative to Feedly and Inoreader \u2014 without the monthly bill. The Problem Feedly and Inoreader charge a monthly fee for things that are really just config on a server \u2014 regex filtering, folders, full-text extraction, newsletter ingestion, AI summaries. Feedly AI and Inoreader Intelligence go a step further and resell LLM [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-94","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/pages\/94","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pengyaochen.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=94"}],"version-history":[{"count":1,"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/pages\/94\/revisions"}],"predecessor-version":[{"id":95,"href":"https:\/\/pengyaochen.com\/index.php?rest_route=\/wp\/v2\/pages\/94\/revisions\/95"}],"wp:attachment":[{"href":"https:\/\/pengyaochen.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=94"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}