Save anything. Find everything.
A personal archive for the links you mean to come back to.
Save in a second, let AI tag it, and Ritro brings it back once you've forgotten it.
Open Ritro · Install the extension · Report a bug
Most bookmarks are saved once and never opened again. Ritro is built around the part other tools leave out: getting things back to you.
- It comes back. Every morning, up to five things from your archive arrive in your inbox. When you have a minute, Surprise me pulls out one you've forgotten.
- Saving is instant. Paste a link, click the extension, press Alt+Shift+S, or use the bookmarklet in any browser. Nothing waits on the AI.
- It files itself. Ritro reads each page's title, description and preview image, and a few seconds later Gemini adds tags. Add your own tags or sort saves into collections if you like.
- Find it with the words you remember. Search looks across titles, descriptions and tags at once. Filter by tag or collection.
- Your archive stays yours. Export everything as JSON or delete your account from Settings at any time. No ads, no tracking cookies, and the code is open source.
flowchart LR
A[Website, extension<br/>or bookmarklet] -->|POST /api/bookmarks| B[Next.js API on Vercel]
B -->|store bookmark| C[(Postgres on Supabase)]
B -->|publish bookmarkId| D[Upstash QStash]
D -->|signed callback| E[Tag worker]
E -->|title and description| F[Google Gemini]
F -->|tags| E
E --> C
G[Vercel Cron, daily] --> H[Digest job] -->|up to 5 saves| I[Resend email]
- Save. The API checks the link is public (no internal addresses, including after redirects), reads its metadata, stores the bookmark and replies straight away.
- Tag. It puts the bookmark's ID on a QStash queue. QStash calls a signed worker route, which asks Gemini for 3 to 5 tags and stores them. Failed jobs are retried, and a daily job re-queues anything that slipped through.
- Bring back. A daily job emails each user up to five saves. Surprise me on the dashboard picks one at random.
Rate limiting (Upstash Redis) protects saving, tagging and collections.
| App and API | Next.js 16 (App Router), React 19, TypeScript |
| Styling | Tailwind CSS v4; Source Serif 4, Geist and Geist Mono, self-hosted |
| Database | PostgreSQL on Supabase, via Prisma 7 |
| Auth | Supabase Auth: email and password, Google, GitHub |
| AI | Google Gemini 2.5 Flash |
| Queue and rate limits | Upstash QStash and Redis |
| Resend | |
| Hosting | Vercel, with Vercel Cron, Web Analytics and Speed Insights |
| Extension | Chrome Manifest V3, plain JavaScript, popup only |
Go to ritro.vercel.app and sign up. There's nothing to install.
The Chrome Web Store listing is coming. Until then, load it unpacked:
- Clone this repository and keep the folder somewhere permanent.
- Open
chrome://extensionsand switch on Developer mode (top right). - Click Load unpacked and choose the
extension/folder. - Pin Ritro from the puzzle-piece menu.
Save the current tab from the popup, or press Alt+Shift+S (Option+Shift+S on Mac) without opening it. Change the shortcut at chrome://extensions/shortcuts.
The extension signs in with email and password. If you joined with Google or GitHub, set a password first with Forgot password? on the website.
On the home page, drag Save to Ritro to your bookmarks bar. Click it on any page, in any browser, to save that page.
You need: Node.js 20+, a Supabase project, a Gemini API key and an Upstash Redis database. All have free tiers.
git clone https://github.com/ritrolabs/ritro.git
cd ritro/client
npm install
cp .env.example .env.local # then fill it in, see below
npx prisma generate
npx prisma db push # creates the tables in your Supabase database
npm run dev # http://localhost:3000AI tagging goes through QStash. Run the local QStash server in a second terminal and copy the four QSTASH_* values it prints into .env.local:
npx @upstash/qstash-cli devWithout them, bookmarks still save; they just don't get AI tags.
Then apply the row-level security policies in client/supabase-rls.sql from the Supabase SQL editor.
Every variable is listed with a comment in client/.env.example.
| Variable | What it's for |
|---|---|
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase project and its public key |
SUPABASE_SERVICE_ROLE_KEY |
Server-only admin key: digest, account deletion, admin page |
DATABASE_URL, DIRECT_URL |
Postgres: pooled for the app, direct for the Prisma CLI |
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN |
Rate limiting |
QSTASH_URL, QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY |
Tagging queue, and checking that callbacks really come from QStash |
GEMINI_API_KEY |
AI tags |
NEXT_PUBLIC_APP_URL |
The app's public address (http://localhost:3000 locally) |
CRON_SECRET |
Password Vercel Cron sends to the daily jobs; also signs unsubscribe links |
RESEND_API_KEY |
Sending the daily digest |
ADMIN_EMAILS |
Comma-separated emails allowed on /admin |
Run these from client/:
| Command | |
|---|---|
npm run dev |
Development server |
npm test |
Unit tests (Vitest) |
npm run lint |
ESLint |
npx tsc --noEmit |
Type check |
npm run build |
Production build (runs prisma generate first) |
Ritro deploys to Vercel from main. Set the project's root directory to client/ and add the environment variables above (store keys and tokens as Secret). Use QStash's production keys, not the local ones.
client/vercel.json schedules two daily jobs:
| Path | When (UTC) | Does |
|---|---|---|
/api/digest |
08:00 | Emails each user up to five saves |
/api/jobs/requeue-untagged |
09:00 | Re-queues saves from the last two days that never got tags |
Both require Authorization: Bearer $CRON_SECRET, which Vercel adds automatically.
In Supabase → Authentication → URL configuration, set the Site URL to your domain and allow https://your-domain/** as a redirect URL.
ritro/
├── client/ Next.js app: website and API
│ ├── src/app/ Pages and API routes (App Router)
│ │ ├── api/ bookmarks, collections, account, digest, jobs
│ │ ├── app/ Dashboard and settings
│ │ └── auth/, privacy/, terms/, bookmarklet/, admin/
│ ├── src/components/ UI components
│ ├── src/lib/ Prisma, Supabase, Gemini, queue, rate limits, URL safety
│ ├── prisma/schema.prisma Bookmark, Tag, Collection
│ ├── DESIGN.md The design system
│ └── PRODUCT.md Product principles
├── extension/ Chrome extension (Manifest V3, popup only)
├── docs/ Release checklist, screenshots
├── CHANGELOG.md
└── THIRD_PARTY_NOTICES.md
- Saved links are checked before the server fetches them: public addresses only, every redirect is re-checked, and the connection itself refuses private IPs.
- The tagging worker only accepts requests signed by QStash. The daily jobs require
CRON_SECRET. - Row-level security keeps every table scoped to its owner.
- CI runs lint, type check, tests and a build on every pull request. CodeQL, secret scanning with push protection, and Dependabot security updates are on.
- Found a vulnerability? Please report it privately through Security → Report a vulnerability on GitHub, not in a public issue.
What Ritro stores, and which services process it, is set out in the privacy policy.
- Website, Chrome extension and bookmarklet
- Background AI tagging, collections, search
- Daily digest and Surprise me
- Data export and account deletion
- Chrome Web Store listing
- Google and GitHub sign-in in the extension
- Semantic search (pgvector)
- "You saved something similar" when you save
- Notes and photos, not just links
- Save and search from Telegram
Contributions are welcome.
- Fork the repo and create a branch from
main. - Make your change, then run
npm run lint,npx tsc --noEmit,npm testandnpm run buildinclient/. - Open a pull request using the template. CodeRabbit reviews every PR automatically.
For bigger changes, open an issue first so we can agree on the approach.
Ritro is free software under the GNU Affero General Public License v3.0. You can use, study, change and share it. If you run a modified version as a public service, you must make your source code available to its users.
Bundled fonts and other third-party components keep their own licences; see THIRD_PARTY_NOTICES.md.
Made by Minit Jain. Built with Next.js, Supabase, Prisma, Google Gemini, Upstash, Resend and Vercel.

