Case Study · 2026

Orbis — contacts that update themselves.

A private contacts app for iPhone. One profile you own, a short code you share instead of your number, and every change synced to exactly the people you chose. iOS app, Firebase backend and website, all built solo.

Role
Solo developer
Stack
SwiftUI, Firebase, TypeScript
Status
Live on App Store
Link

The problem.

Phone numbers change. People move house, switch jobs, get a new email. Every time that happens the update goes out as a group text, or it doesn't go out at all and half your contacts are quietly wrong. The address book on your phone is a snapshot that starts going stale the moment you save it.

The apps that try to fix this mostly do it by uploading your entire contacts list to their servers and monetising the graph. I wanted the opposite: a contacts app where you own your profile, choose exactly who sees it, and every connection stays current without anyone doing anything.

What it does.

You create a profile once: name, phone, email, address, birthday, the things people actually need from you. Then:

Plus the small things that make an app feel finished: six colour themes with matching app icons, QR codes for in-person swaps, push notifications for requests, haptics, and account deletion that actually deletes.

How it's built.

iOS client (SwiftUI)

The app is native SwiftUI, around 12,000 lines across 32 files. It talks to Firebase directly for authentication, Firestore reads and image storage, and calls Cloud Functions for anything that touches more than one user's data. Deep links (universal links for share codes, invite tokens, and a cold-start path for links that arrive before the UI exists) all run through a single router, so every entry point ends up in the same place.

Backend (Firebase, with Cloud Functions in TypeScript)

Firestore holds users, connection requests and invite tokens. The important design rule is that clients can't write their own connection graph. Sending, accepting, declining and removing connections all go through callable Cloud Functions, so the server is the only thing that can make two users' documents agree. Firestore security rules back that up: a user can update their own profile but cannot touch connectionIds or change their code.

There are sixteen functions in total: eleven callables, three public HTTP endpoints (the personalised invite page reads its name and avatar from two of them, the website's feedback form posts to the third), and two Firestore triggers that send push notifications when a request is created or accepted.

Making it hard to abuse

The website

orbiscontacts.com is a static site on Cloudflare. It hosts the Apple App Site Association file that makes universal links work, the fallback pages for share and invite links, the legal documents that App Store Connect points at, and a feedback form wired to one of the Cloud Functions. Getting link previews right, the card you see when someone pastes an Orbis link into iMessage or LinkedIn, took more iterations than I'd like to admit.

"The app is the visible part. Most of the engineering is in deciding what a client is allowed to do, and making sure the server is the only thing that can do the rest."

Deferred deep linking, without a third-party SDK.

The hard part of invite links is the gap between "tap link" and "install app". iOS doesn't pass the URL through the App Store, and Firebase Dynamic Links, the usual answer, was shut down in 2025. The commercial alternatives all want an SDK in the app and a line on the privacy label, which runs against the whole point of Orbis.

So the invite page copies the token to the clipboard when the visitor taps the install button, and the app checks the pasteboard on first launch. iOS shows its paste prompt, the user says yes, and they're connected. The page also shows the code in plain text as a manual fallback. It's low-tech, but it's honest about what it's doing and it puts no one else's code in the binary.

What I'd do differently.

What it taught me.

Orbis is the first thing I've built that has a backend other people depend on. Market Brief has a server, but if it goes down you see stale numbers. If Orbis gets a rule wrong, someone's phone number ends up with the wrong person. That changes how you work: security rules get reviewed before features, the "delete my account" path gets tested as carefully as sign-up, and every callable starts with the question "what happens if this is called by something that isn't my app?"

It's also the project where I learnt that shipping is a loop, not an event. Four updates since launch, each driven by watching real people use it and noticing where they got stuck.