Bookmarx
A passive brain for your X bookmarks.


What it is
You save ideas on X and never come back to them. Bookmarx syncs your bookmarks, enriches each one with an AI summary and tags, and lets you chat with everything you saved, get scheduled digests, and turn a thread into a slide deck. A Next.js 16 PWA with offline support and push.
System design
One canonical row per tweet is shared across every user, with per-user metadata kept in a separate join, so enrichment is fetched and cached exactly once and deduplication is free. Search fuses pgvector and Postgres full-text via Reciprocal Rank Fusion, with embeddings computed once at write time so each query costs a single lookup. Auth resolves entirely at the edge from a signed JWT with zero database calls, and per-user, timezone-aware digests are pre-computed as UTC timestamps and fired by pg_cron, which sidesteps Vercel's single-schedule limit.
- Next.js 16
- React 19
- Postgres + Drizzle
- pgvector
- OpenAI
- Gemini 2.5
- pg_cron
- DodoPayments
What I got wrong, then fixed.
01 · the problem
64% of native X articles came back with no cover image. The standard media expansion silently ignores an article's cover, and the article URL I was building was auth-gated.
what I did
Switched hero covers to X's free public syndication endpoint and inline media to FxEmbed, then gave each source strict ownership so they never overwrite each other. The right fix was a sibling endpoint, not a workaround.
02 · the problem
A single 429 from X's bookmarks API failed a user's entire daily sync. At a 1 to 5% transient failure rate, 99%-per-call reliability quietly collapses to about 97% per user, per week.
what I did
A drop-in fetch with full-jitter exponential backoff that retries 429 and 5xx, honours Retry-After, and deliberately does not retry 401 so the token-refresh path still fires.
03 · the problem
Adaptive AI summaries had a 70% retry rate. JSON mode is best-effort, so models kept emitting the wrong number of sections, and one truncated response could cascade through the whole enrichment pipeline.
what I did
Moved to OpenAI structured outputs with a per-length JSON schema that enforces section counts at decode time. The model literally cannot emit a non-compliant shape. 17 of 17 compliant, zero retries.
04 · the problem
Web push 'worked' on iOS but never prompted. Three independent things were wrong, and each looked fine on its own.
what I did
Added the standalone-mode meta tag, gated support detection on actual PWA standalone mode, and moved the permission request to the first await in the handler so no React re-render breaks the user-gesture chain.