Why I built Markly— and how the review loop finally got fixed
Markly is a PDF annotation and async review platform I designed and built solo. It exists because I kept hitting the same wall myself: feedback that lives everywhere except on the document.
The problem I owned end-to-end
During mid-review presentations and final project submissions, my team would review reports over email and WhatsApp — sending PDFs back and forth, discussing changes in messages that got completely disconnected from the actual document. Someone would say “the second paragraph on page four is too dense” and then everyone would spend a minute finding page four.
I felt it again during my GSoC application days. As a contributor, I wanted a mentor to review my proposal, but there was no simple way to share it and get comments without making them sign up for something or install something. The feedback loop was always messy — and the friction always landed on the person doing me a favour.
The tools that let you draw on a document lock reviewers into an account and a desktop install. The tools that are easy to share don’t let you draw. I wanted the overlap.
Comment threads in Google Docs don’t work for visual documents. Acrobat’s annotation tools do, but they assume everyone in the loop is already a customer. Markly sits in between: real drawing tools on a real PDF, shared by URL.
So I built exactly that
You upload a PDF, mark it up, and share a single link. The reviewer needs no account and no install. They can comment, draw, and even send their markup back with one click — which branches into a copy, so your original is never overwritten. When you’re done, you export a flat PDF with every annotation baked into the bytes.
- Upload — drop a PDF (or a DOCX, converted server-side). Rendered with pdf.js, multi-page, high fidelity.
- Annotate — eight tools: select, rectangle, circle, arrow, line, highlight, freehand, text. Virgil for the hand-drawn feel, plus a font picker.
- Share — one link with VIEW, COMMENT or EDIT access, optionally time-limited. No account on the other end.
- Export — a flat PDF with annotations embedded at the exact positions and fonts you saw on screen.
Who it’s actually for
It started as my own problem, but the shape of it is common to anyone whose review loop currently lives in email attachments named final_v3_REVISED.pdf.
Students & contributors
Share a proposal, report, or thesis draft with a mentor and get inline comments back — without asking them to sign up for anything.
Design & product teams
Mark up mockups and specs where the feedback lives on the artwork, not in a disconnected comment thread.
Legal & contracts
Redline clauses visually and send a time-limited review link instead of a fifth email attachment.
Architects & engineers
Annotate plans and drawings at any zoom — marks stay pinned to document coordinates, not to the screen.
How it’s put together
One Next.js deployable holds the server components, the API routes and the editor. State splits cleanly: Zustand for the synchronous editor store (tools, history, viewport), TanStack Query for anything that touches the server. Storage is pluggable — lib/storage.ts picks local disk or Cloudflare R2 from environment variables alone, so npm run dev works with zero cloud credentials.
The parts that were actually hard
My role was everything — design, frontend, backend, infrastructure. These are the four problems I’m most proud of solving, because none of them had a library I could just drop in.
Login-free review that is still secure
The obvious way to let anyone review a document is to loosen auth. I did the opposite and used two explicit authorization paths instead. Owners are resolved from `markly_session`, an HTTP-only Firebase session cookie verified server-side. Reviewers are resolved from a `?token=` share link that maps to a ShareLink row, checked for expiry and for a ranked access level — VIEW < COMMENT < EDIT — against whatever the route requires. Every document-scoped route goes through one of those two paths, so there is no second, weaker auth system hiding behind the convenience.
Uploads that do not choke a serverless function
Vercel caps serverless request bodies at 4.5 MB, and streaming a 50 MB PDF through a function is wasteful even where it is possible. So PDF bytes never touch an API route: the browser asks for a presigned URL, PUTs the file straight to Cloudflare R2, then posts back the filename. The server calls fileExists() before writing the database row, which is what stops a client from fabricating an orphaned document record.
Exports that match the screen exactly
An annotation that lands in a different place in the exported PDF than it did in the editor is worse than no export at all. lib/fonts.ts is a single dependency-free registry shared by the canvas, the toolbar picker and the export route. Each font declares a CSS stack for the screen plus either a pdf-lib standard font or a file to embed. The export route re-implements the canvas text wrapping against the embedded font’s real metrics — which is why exported text breaks lines in exactly the same places.
Feedback that never overwrites your original
When a reviewer sends their markup back, it branches into a hidden copy of the document and returns as a new link, rather than writing over what you had. Annotations live in the database as geometry and style JSON columns — the source file is never modified, so "the original" always means something.
How I measure impact
It went from a personal frustration to a live, working product — deployed and usable end-to-end, running entirely on free-tier infrastructure. No paid service anywhere in the stack.
But the real measure for me isn’t the uptime or the bill. It’s that Markly solves the exact review loop I struggled with in college and during GSoC, and that anyone facing that same problem can now use it without friction. That was the whole point: remove the friction from the person on the other end of the link.
Try it on your own PDF.
Markly is open source and MIT-licensed. Read the code, self-host it, or just upload something and share a link.