All projects
02WEB + MOBILE2026
SubTrack app icon: a green subscription loop crossed by a violet arrow rising to the right

SubTrack

Every subscription on one calendar — with the renewal maths, the multi-currency totals and the reminders all done for you.

Visit subtrack.appiOS · Android
SubTrack calendar for August: $60.46 a month, a trial converting soon banner, renewal dots on the month grid and category cards
SubTrack stats: this month's spend of $60.46 with a step chart and yearly, charged, left-this-month and due-this-week rows
SubTrack welcome screen: "Every subscription, one calendar" with example chips for renewals, gym membership, free-trial alerts and debit orders

Overview

What is SubTrack

SubTrack is a subscription tracker for people who have quietly accumulated more recurring charges than they can name. Streaming, the gym, medical aid, insurance — every subscription lands on one calendar with the renewal maths, the multi-currency totals and the reminders done for you, including free trials that are about to convert. It ships to iOS and Android from one Expo codebase, and it is free.

Role
Founder & engineer — designed and built the product end to end: app, API, reminders and release.
Platform
iOS · Android
Stack
Expo · Next.js · PostgreSQL
Reminders
Email + local notifications
Status
Live, v1.0.5
Web
subtrack.app

Contribution

What I built

  1. 01Designed the product around one calendar: each subscription's price, cycle and renewal day fold into a month view, a spend dashboard and a list ordered by what charges next — trials about to convert included.
  2. 02Built the backend as a versioned REST API on Next.js 16 with a 30-day bearer JWT — the Expo app never calls Server Actions, so the contract stays stable across app releases.
  3. 03Made reminders idempotent: a nightly cron emails renewals and trial conversions through Resend, keyed on a unique (subscription, kind, due date) row, while the app schedules its own local notifications.
  4. 04Handled money honestly: prices in any currency, one USD rate snapshot a day stored in Postgres and rebased per request — every currency pair resolves without a second upstream call.
  5. 05Shipped with confidence: 27 Vitest suites on in-memory PGlite, Jest units and a Maestro end-to-end suite; Google, Apple and email sign-in verified against the providers' JWKS.

How it works

How the data moves

The core paths through the system — a dot travels each hop, and the caption says what happens there.

Adding a subscription

From a form to a renewal on the calendar — and reminders armed on the phone.

YESNOStart
Enter name, price, cycle and renewal day
POST /api/v1/subscriptions with the bearer JWT
Valid and owned by this user?
422 with field errors
Insert the subscription row
Next charge date computed (day 31 clamps to month end)
Local reminders armed · widget snapshot updated
End

Swipe sideways to see the whole chart.

01 / 09new subscription

You add a subscription — name, price, billing cycle, renewal day.

Decisions

Calls that shaped it

A versioned REST API, not Server Actions

Server Actions are a web-only protocol. A bearer-authenticated /api/v1 gives the mobile app a contract that survives app-store release cycles.

Reminders on two independent tracks

Email from a cron that is safe to re-run, local notifications from the phone itself — neither depends on the other, and there is no push infrastructure to keep alive.

One rate snapshot a day, rebased on read

A single daily USD snapshot keeps currency conversion cheap and deterministic; any display currency is derived at request time.

Drizzle migrations, PGlite for tests

Generated migrations plus an in-memory Postgres mean the entire backend suite runs without a database server.

Stack

  • Expo
  • React Native
  • Next.js
  • TypeScript
  • PostgreSQL
  • Drizzle
  • Resend
  • Railway