Skip to main content

Command Palette

Search for a command to run...

Building a simple Readwise alternative

Updated
View as Markdown

I’ve had used Readwise long back but did not want another subscription in my life. The combination of keeping my highlight organised and gentle email digests that resurface old insights was something that I really liked.

But I wanted more control: no recurring subscription, full ownership of my data, and the ability to experiment with the extraction logic itself. So I vibe coded a self-hosted alternative. It lives inside my network.

The result is a lightweight application that:

  • Accepts Markdown or plain text uploads

  • Uses OpenAI to intelligently extract highlights

  • Stores everything in a simple SQLite database

  • Provides a clean web UI for browsing and searching

  • Automatically emails me 3 random highlights on a daily or weekly schedule

Why Self-Host This?

Readwise is excellent, but it comes with trade-offs:

  • Monthly cost

  • Data lives in someone else’s system

I wanted something that could live on cheap hardware I already own, stay private, and serve as a playground for trying different prompting strategies and optimization techniques.

Core Features

  • AI-powered extraction — Upload a Markdown file or article and let OpenAI pull out the best quotes with attribution, source, and author.

  • Manual entry — Add highlights directly through the UI when you don’t have a file.

  • Search and pagination — Quickly find old highlights.

  • Smart resurfacing — A cron job picks 3 highlights you haven’t seen recently and emails them.

  • Settings UI — Change delivery time, frequency (daily/weekly), and target email without touching code.

  • Dockerized deployment — One command to run everything, with persistent volumes for the database and uploads.

How It Works

The architecture is deliberately simple:

When you upload a file, the server saves it temporarily, runs it through an extractHighlights() function that calls OpenAI with a strict JSON schema, then stores the results.

The resurfacing logic lives in the same Express process. On startup (and whenever settings change), it reads the schedule from the settings table and registers a cron job. Every run it selects three highlights that are either unreviewed or older than a day, emails them, and updates their last_reviewed and review_count fields.

Tech Stack

  • Backend: Node.js + Express 5 (CommonJS)

  • Database: SQLite (sqlite3)

  • AI: OpenAI SDK with structured outputs (response_format: { type: "json_object" })

  • Email: Nodemailer + node-cron

  • Frontend: Single-file vanilla HTML/CSS/JS (no bundler)

  • Deployment: Docker Compose (with a Tailscale sidecar for secure remote access)

The entire frontend is served statically from public/index.html. It uses modern CSS custom properties for theming (light/dark/system) and debounced search with client-side pagination.

What’s Next?

Some ideas I’m considering:

  • Better chunking and context for long documents

  • Support for PDFs and web clipping

  • A simple “favorite” or tagging system

  • Using the same setup as a testbed for better extraction prompts and evaluation harnesses

  • Adding a small MCP-style interface so AI agents can query my highlights

Conclusion

Building this was a fun reminder that many “premium” personal tools can be recreated with a weekend of focused fun — especially when you combine modern LLMs with simple, reliable infrastructure like SQLite and cron.

If you’re interested in self-hosting your reading highlights or just want to tinker with AI extraction pipelines, the code is straightforward and the Docker setup makes it easy to run. I am using clerk to have separate logins for my family. You can ask any LLM to remove it.

Github Link