Anonymous notes, shared instantly. Write a note, get a shareable link ! No account required. Optionally protect it with a password or set it to auto-expire.
🔗 Live demo: https://pasquin.mkgg.dev
A pasquinade is an anonymous note posted in a public place which is named after "Pasquino," a statue in Renaissance Rome where people stuck up anonymous verses. That's exactly what this app is: anonymous notes, posted to a public link.
![]() |
![]() |
![]() |
| The editor | Share dialog with QR code | Reading a shared note |
- Rich text editor — headings, lists, task lists, code blocks, highlighting, text color, links (built on TipTap).
- Anonymous ownership — no sign-up. Each note returns a one-time edit key that proves ownership; the server only ever stores its hash.
- Shareable links — view-only by default, or an editable link that carries the key in the URL fragment (never sent to the server).
- Password protection — reader-facing passwords, hashed with BCrypt. Protected notes hide their title and content until unlocked.
- Auto-expiry — 1 hour to 1 month, implemented server-side with DynamoDB TTL (the note deletes itself).
- My Notes — a per-browser sidebar (search, rename, delete, copy link) backed by
localStorage. - Google sign-in (optional) — syncs the note list across browsers and devices.
- Premium image uploads — a one-time purchase (via Polar as merchant of record, confirmed by signed webhook) unlocks embedding images in notes. Uploads are validated by magic bytes, capped per image and per user, and served from the CDN.
- Sharing extras — one-click copy, email, and a generated QR code for handing a note to a phone.
- Abuse reporting & contact — public forms with validation, rate limiting, and a spam honeypot. Submissions are emailed to the operator via SES.
- Moderation tooling — an operator-only takedown endpoint removes a reported note everywhere at once: metadata, content, embedded images, and CloudFront edge caches.
- Dark / light theme, responsive down to mobile.
Static frontend served globally from CloudFront + S3; a containerized Spring Boot API on App Runner persists note metadata to DynamoDB and note bodies to S3. Premium images live in the content bucket behind a dedicated CloudFront images/* behavior, contact/report submissions are relayed by SES, and purchases go through Polar (checkout API out, signed order.paid webhooks in). Storage cleanup is event-driven: removing a note (TTL expiry or delete) emits a DynamoDB Streams event, and a Lambda sweeps the orphaned S3 body, its images, and their CDN caches. An AWS Budgets alarm emails the operator before costs can creep. Everything is defined in Terraform.
flowchart LR
Push([git push main]) --> GHA[GitHub Actions]
GHA -->|OIDC · no stored keys| Role[(AWS deploy role)]
GHA -->|build + push image| ECR[ECR] -.->|auto-deploy| API[App Runner]
GHA -->|sync + invalidate| CDN[S3 + CloudFront]
Push to main and the changed app deploys itself. GitHub authenticates to AWS via OIDC — there are no long-lived AWS credentials stored anywhere.
| Layer | Tech |
|---|---|
| Frontend | Astro 5, React 19, TipTap 3, Tailwind CSS 4, shadcn/ui, TypeScript |
| Backend | Spring Boot 4, Java 25, AWS SDK v2, BCrypt, Bucket4j (rate limiting) |
| Data | DynamoDB (on-demand, TTL), S3 |
| Billing | Polar (merchant of record) · Standard-Webhooks signature verification |
| Infra | Terraform · App Runner, CloudFront, ECR, DynamoDB (+ Streams), S3, Lambda, SES, IAM, Budgets |
| CI/CD | GitHub Actions with OIDC federation |
| Quality | Spotless (Google Java Format), astro check, JUnit |
There are no user accounts, yet notes are still editable and deletable only by their creator:
- On save, the server generates a random 128-bit edit key, returns it once, and stores only its SHA-256 hash.
- Updates and deletes require the key in an
X-Edit-Keyheader; a wrong key gets a403. - The browser keeps a
slug → keymap inlocalStorage, that map is the "My Notes" list. - Editable share links put the key in the URL fragment (
/n/{slug}#key), which browsers never transmit to a server, so the key stays private even in transit.
Backend (runs on :8080 with an in-memory store — no AWS needed):
cd backend
./mvnw spring-boot:runFrontend (runs on :1234, proxies to the local backend):
cd frontend
npm install
npm run devRun the backend test suite:
cd backend
./mvnw verify # unit tests + Spotless format checkInfrastructure is fully codified in infra/ (Terraform). See infra/README.md for the step-by-step deploy runbook and cost notes. In short: Terraform provisions everything, and GitHub Actions handles ongoing deploys.
pasquin/
├─ frontend/ # Astro + React app
├─ backend/ # Spring Boot API
├─ infra/ # Terraform (AWS)
└─ .github/ # CI/CD workflows
This project is open source and available under the MIT License.



