QuillPrint
A hyperlocal print-shop marketplace, built to production standard with a zero-touch print flow. Customers upload documents, compare nearby shops, pay online and collect with a 4-digit number. I built and run all of it: product, backend, web, mobile, infrastructure and operations.
Printing is not hard. Waiting for it is. Before a deadline you queue at the counter, explain page ranges by hand, and learn the price only after the job is printed.
From a file on your phone to paper in your hand
Upload
PDF, Word, PowerPoint or photos, up to 30 MB each, several documents in one order. Files can also arrive over WhatsApp.

Set up each document
Copies, page range, colour for selected pages only, single or double sided, binding and finishing.

Compare nearby shops
Every shop sets its own rates. You see price, services, live Instana state and distance before you choose.

Pay
Online, from the wallet in full or in part, or at the shop where enabled. The full total is shown before you pay.

Collect
Track the order and pick it up with a 4-digit collection number.

At an Instana shop, a paid order prints automatically in about 10 seconds, with zero touches from the shop.
Instana is QuillPrint's own instant-printing service. It is not related to IBM Instana.
Zero-touch by design: who does what after you pay
At an Instana shop, QuillPrint carries a paid order to paper on its own. Shop staff do nothing until the customer arrives.
Paid
CustomerPays online in the app
Confirmed
QuillPrintConfirms the payment
Printing
QuillPrintStarts the print at the shop
Ready
QuillPrintMarks the order ready for pickup
Collected
CustomerShows the 4-digit number
Shop staffHands over the prints
Built to production standard
- Users
- 500+
- Logins per day
- 100+
- Production load test, 18 July 2026
- 36,028 requests, 0 failures
- Throughput in that test
- 120 requests a second, 493 virtual users at peak
- Memory for the whole stack
- about 1.5 GiB
- Backend
- 27 routers, 60+ endpoints
- Every release
- tested on Postgres 16, scanned, with a rollback controller
Decisions I would defend in an interview
Money is append-only
Payment and wallet ledgers only ever gain rows. Wallet rows are locked with SELECT … FOR UPDATE, so a balance cannot be spent twice.
Prices are frozen per order
Each order snapshots the shop's price at checkout and moves through a strict nine-state machine.
Uploads are treated as hostile
Every file passes a MIME check, a ClamAV scan, PDF sanitising, macro rejection and sandboxed conversion before a shop sees it.
Payment gateways are swappable
One abstraction sits over Razorpay, Cashfree and Zoho, and the active gateway can change at runtime.
Tokens rotate, limits are per endpoint
Refresh tokens are single-use and rotate in Redis. Bank details are Fernet-encrypted. Login allows 5 attempts a minute, register 3, upload 10.
Self-hosted, with a way back
The backend is self-hosted on a VM with Docker Compose behind a Cloudflare Tunnel. Releases go out in a safe order, with a rollback controller if one goes wrong.
Built with
- Backend
- Python 3.11, FastAPI, SQLAlchemy 2 async, PostgreSQL 16, Redis 7, Celery, Alembic
- Web
- React 19, Vite 7, Tailwind 4, Zustand, Mapbox GL, pdf.js, PWA
- Mobile
- Flutter, Riverpod, GoRouter, FCM, Sentry
- Payments
- Razorpay, Cashfree, Zoho, RevenueCat
- Infrastructure
- Self-managed VM, Docker Compose, Cloudflare Tunnel and Workers, Vercel, GHCR, Datadog
- CI
- pytest on Postgres 16, SBOM, Trivy, pip-audit, bandit, gitleaks