A local replacement for Readwise Reader's send-to-Kindle flow. It reads your
Gmail newsletter-labeled emails, shows them in a localhost dashboard with
their article links extracted, and sends what you pick to your Kindle as clean
EPUBs — full newsletter bodies or the web articles behind digest links.
Everything runs on your machine. Your Gmail token never leaves it: you authenticate with your own Google OAuth credential, and the dashboard only answers requests from localhost. No server, no domain, no forwarding rules.
Requires Node.js ≥ 20 and pnpm.
git clone https://github.com/JoanClaverol/reader-processor.git
cd reader-processor
pnpm install # also builds the TypeScript
pnpm add --global ./ # puts `reader-process` on your PATH (linked to this checkout)-
Config: create
~/.reader-processor/config.tomlfromconfig.toml.exampleand setkindle_email. (Aconfig.tomlin the repo root also works if you prefer to keep everything in the checkout.) -
Amazon: in Manage Your Content and Devices → Preferences → Personal Document Settings, add your Gmail address to the Approved Personal Document E-mail List, and note your
@kindle.comaddress. -
Google OAuth credential (lets the app read/send with your Gmail — it's yours alone, so no third party ever sees your mail):
- Go to Google Cloud Console, create a
project (e.g.
reader-processor). - APIs & Services → Library → enable the Gmail API.
- APIs & Services → OAuth consent screen → External, add yourself as a test user.
- APIs & Services → Credentials → Create credentials → OAuth client ID → Application type Desktop app.
- Download the JSON and save it as
~/.reader-processor/data/credentials.json.
- Go to Google Cloud Console, create a
project (e.g.
-
Authenticate (opens a browser once):
reader-process auth
-
Gmail label: the app lists emails carrying the
newsletterlabel (configurable assource_label). Set up a Gmail filter that applies it to your newsletter subscriptions.
reader-processIt picks the first free port from 8377 up, starts the server, and opens the
dashboard in your browser (set NO_OPEN=1 to skip the auto-open).
Tick the newsletters/links you want, hit send.
Sent items are marked ✓, the source email gets a kindle-sent label in Gmail,
and /log shows the send history (stored in the data/ SQLite db).
The UI is three columns: newsletters, the selected newsletter's items, and a preview of the selected content. Click a newsletter to preview its body; click a link to fetch and preview the article behind it. Tick checkboxes and send in batch. Drag the column dividers to resize (double-click to collapse), and watch send progress in the bar under the header.
State is kept in ~/.reader-processor/ by default (or the repo root if a
config.toml / data/ already exists there; override with
$READER_PROCESSOR_HOME):
config.toml— your settings.data/credentials.json— your Google OAuth client (you created it).data/token.json— your Gmail token (owner-only file permissions).data/reader-processor.db— message/article cache and send log.
None of these are ever committed: data/ and config.toml are gitignored.
The app is TypeScript: a Node/Express backend (server/) and a vanilla TS
frontend (frontend/), built together by pnpm run build (which
pnpm install runs automatically).
server/index.ts— Express JSON API + serves the static frontend. Binds127.0.0.1only and rejects non-localHost/Originheaders, so neither your LAN nor other websites can reach it.server/gmail.ts— Gmail API (googleapis): list/read labeled emails, send EPUB attachments from your own account, manage the sent label.server/extract.ts— parses newsletter HTML: pulls candidate article links (unwrapping tracking redirects, flagging unsubscribe/social/sponsor junk), light-touch body cleanup (tracking pixels, scripts, hidden elements) that keeps the newsletter's own layout.server/fetchArticle.ts— fetches a linked article (http/https only) and extracts readable content (@mozilla/readability).server/epub.ts— EPUB builder (JSZip, book CSS, embedded images).server/store.ts— SQLite (better-sqlite3): message cache + fetched-article cache + send log.frontend/main.ts— the dashboard UI, compiled topublic/main.js.bin/reader-process.js— the CLI launcher (reader-process,reader-process auth).
- Message bodies are cached in SQLite, so only new newsletters hit the Gmail API on page load.
- If an article fails to extract (paywall, JS-only page), the result page shows the error and nothing is sent for that item.
- Junk-link filtering lives in
JUNK_TEXT/JUNK_HOSTSinserver/extract.ts— tune per your newsletters. - To update:
git pull && pnpm install. - To uninstall the global command:
pnpm remove --global reader-processor.