Aaron Perkel

projects / lejer

Lejer

Shared bills for the people you live with

Lejer screenshot
Status
Live
Role
Designed, built, and run it solo
Timeline
Oct 2026 – present
Stack
Next.js 16, TypeScript, PostgreSQL (Neon) via postgres.js, Vercel (Blob, Cron, Queues), Resend + React Email

Highlights

  • Postgres row-level security per household; the app’s database role can’t bypass it.
  • Email sign-in codes stored only as SHA-256 hashes, compared in constant time, behind layered rate limits.
  • ~790 verification checks across 14 suites, run against a Neon branch.

Overview

Lejer is a free bill-splitting app for people who share a lease. Someone posts each house bill with its statement; everyone sees their share, gets a reminder before it’s due, and checks off when they’ve paid — whether one person pays everything or each bill has its own owner who fronts it. It grew out of a PHP dashboard my roommates and I used, and two households moved onto it at launch.

How it works

Tenant isolation

Postgres row-level security per household. Each request runs in a transaction that sets the household with set_config(…, true), and every policy reads it. The app connects as a role without BYPASSRLS — the migration refuses to run otherwise — and the owner role is confined to four listed call sites.

Auth

Email sign-in codes: six digits from a CSPRNG, stored only as a SHA-256 hash, 10-minute expiry, dead after five wrong guesses, and compared in constant time. Rate limits per email, per (hashed) IP, and a global daily cap. Sessions are a signed JWT in an httpOnly cookie, 30 days with sliding renewal.

Reminders

An hourly Vercel Cron tick sends each household’s reminders at its own local send hour; a Vercel Queues topic handles delayed wake-ups. 14 React Email templates.

Files

Statements live in a private Blob store and are served only through an authenticated route; documents upload straight from the browser.

Migrations and import

18 hand-written SQL migrations with a small runner. Both legacy households were imported from the old app’s database in a single transaction at launch.

Verification

14 verification suites (~790 checks) run against a Neon dev branch: RLS isolation, identity, bills, cron, the import, and HTTP.

Admin

The operator console reads only through SECURITY DEFINER functions that never return dollar amounts.

What I’d do next

  • Venmo and Zelle pay links.
  • Run the verification suites in CI on every pull request.

Links

← All projects