Business Card Management App
A business card app for companies, priced at roughly half what comparable products charge by letting each customer choose how much recognition accuracy they are paying for.
- Period
- Aug 2025 – Jun 2026
- Team
- Mostly solo, with a contractor on part of the build under my review
- Role
- Lead developer — product design, most of the implementation, and review of contracted work
- Scale
- iOS and Android release · company-level permission model · bulk re-recognition tooling
- Stack
- TypeScript · NestJS · TypeORM · PostgreSQL · Next.js · React Native · Google Cloud Run · Cloud SQL · Cloudflare R2 · Cloud Build · fastlane
Problem
Business cards accumulate with the person who received them. Someone meets a supplier, the card goes in a drawer, and the relationship it represents is invisible to everyone else in the company. The information is not secret — it is just not anywhere the organisation can reach.
Products already existed to solve this. They were expensive enough that companies bought them for the sales team and nobody else, which reproduces the original problem at a smaller scale: the cards that get digitised are the ones belonging to people whose employer decided they were worth a licence.
The opening was price. If the product cost little enough to give everyone, the collection would belong to the company, not to the sales department.
Approach
What happens to a card after it is read is the part that matters. Cards land in a shared collection, not a personal one, so a card carries the people who hold it: scan a supplier and you can see which colleagues already know them. Each person also chooses which of their own cards the rest of the company can see, and cards can be exchanged directly by QR code, skipping paper altogether. The effect is that contacts stop belonging only to whoever collected them — someone about to approach a company can discover that a colleague two departments away has been dealing with them for years. That is the whole point, and it is what the rest of the design has to be cheap enough to allow.
Recognition accuracy is the expensive part. Card scanning lives or dies on optical character recognition, the good models cost meaningfully more than the adequate ones, and Japanese makes this worse — kanji have near-identical forms, and a model that resolves them reliably is not the cheap one.
The usual responses are to absorb the cost and charge accordingly, or to ship the cheap model and let accuracy be a complaint. I did neither. The recognition model is a setting the customer chooses. At around five Australian dollars per user per month the app runs the cheaper model, and the customer accepts that visually similar characters will occasionally come back wrong. Paying more moves them to a model that gets those right. The trade-off between cost and certainty did not disappear — it moved to the person who is actually in a position to judge which one they want.
Sharing works the same way. Whether a card belongs to the person who collected it or to the company is not a question with one right answer, so it is a setting: an organisation-wide default for whether cards are private or shared, and permission roles by department and job title on top of it for the cases the default does not cover.
There is no offline mode. Cards are usually collected away from a desk, which argues for one, but photographing now and scanning later covers the same need without a synchronisation model to maintain.
Getting it onto the stores was its own problem. This is software for companies, and an app that does nothing until your employer has an account sits awkwardly against review guidelines that ask what a member of the public gets from installing it. Making it invite-based, with invitation links anyone can pass on, gave that question a real answer rather than a rehearsed one — the app works for whoever is invited, not only for whoever is on a corporate plan. The cost is discoverability: nobody finds this in a store listing and starts using it that afternoon. Every user arrives because somebody sent them a link, which is fine for how it is actually sold and would be fatal for a product that needed to be found.
The deployment setup came with the product line and needed almost no rework: Cloud Run over Cloud SQL, with Cloud Build installing, building, running the production migrations and rolling out on every push to main, so a schema change and the code that depends on it arrive together instead of in a sequence somebody has to remember. What was new here was the mobile half. Store submission is the step most likely to be done by hand and therefore most likely to be done differently each time, so it goes through fastlane.
Trade-offs
Letting customers choose the model has a cost that only shows up later: everything already recognised was recognised by whichever model was selected at the time.
A customer who moves up a tier has a library processed by the cheaper model. A model reaching general availability from preview means every card processed under the old version is now behind. Neither situation is acceptable if the answer is that the customer should re-scan cards they no longer physically hold.
So the decision to expose the choice carried an obligation to build re-recognition. Individual cards can be run again, and there is tooling to reprocess in bulk with a dry run first, because a batch job that silently rewrites a company's contact data is not something to run hopefully. It is deliberately not automatic on a plan change: reprocessing an entire library is expensive, and making it a free consequence of upgrading would let someone upgrade for a single month, reprocess everything and drop back down. So it runs under conditions, not as a reflex.
That infrastructure exists because of the pricing model, and it is the part of the work I would point to if asked what a product decision costs an engineer.
Multi-tenancy is what makes the price possible at all, and it is not free either. One deployment serves every customer, so the marginal cost of another company is close to nothing — the only way five dollars a head covers anything. That arrangement is safe or it is worthless, and the way it usually stops being safe is a query somebody forgot to scope.
So isolation is not left to the application. Every tenant-scoped table has row-level security enabled in Postgres, with a policy matching the row's company_id against a session variable the connection sets before it does anything else — a tenancy layer I built for the product line rather than for this app in particular. A query that forgets its filter returns nothing; it cannot return another company's rows, because the database will not serve them. The cost is that every connection has to establish its tenant context first, and a mistake there fails closed but silently: an empty result set with nothing to explain it.
Storage went the same way, and cost the same kind of thing. Our other products keep files in Cloud Storage alongside everything else, which is the obvious choice when files are incidental. Here they are the product: every card is an image, kept indefinitely, and re-read whenever recognition runs again. At that shape the storage bill stops being a rounding error, so images went to Cloudflare R2 instead, where the pricing suits exactly this pattern. Running across two providers is not free — a second account, a second set of credentials, a second thing that can be down — and I took that on because R2 speaks the S3 API, so the storage layer needed almost no change to move. A product that only works if it is cheap cannot carry an infrastructure bill that grows with how much its customers use it.
My scope
I designed the product and built most of it, with a contractor taking whole features under my review.
What went to the contractor was chosen rather than left over. Handing work across costs something fixed — the specification has to be transferred completely, the context explained, the result reviewed — and below a certain size that overhead is larger than what it buys. So what I delegated was whole features with clean edges: big enough to be worth the handover, and independent enough that the specification could actually be complete. What stayed with me was the work where explaining the constraints would have taken longer than satisfying them.
Reviewing rather than writing is a real substitution and it only works if the review is good. I learned to program at 42 Tokyo, a school whose entire method is reading and critiquing other people's code, so this is the part of the job I am most practised at. It was backed up anyway: tests, a staging environment and manual passes meant a review miss would not reach a customer unassisted.
It was built with AI assistance throughout — conventional development with a fast collaborator, and nothing more structured than that.
Outcome
The app reached the iOS and Android stores in November 2025 as AiiPo 名刺, and has been in maintenance and incremental development since. Fewer than a hundred companies use it — what a self-funded product with no advertising behind it looks like a year in.
That number carries less weight than it appears to, because the product is not really trying to be a revenue line. It is a way into a conversation: companies that adopt it are companies we can then approach about contract development, which is where the margin actually is. Knowing that is what makes undercutting the market a coherent decision rather than a reckless one.
The customers who move tend to arrive with the same complaint about what they were using. Cheap plans are built around personal accounts and will not share a collection across a company; the plans that will share cost several times more. An organisation that digitises a stack of cards and then consults them occasionally is priced for neither. That gap is the one this sits in.
The tier experiment did not land the way I expected. Most customers choose the cheapest plan, for the same reason they came in the first place — they are not heavy users, and adequate recognition is adequate. So the upper tiers earn little. What they do instead is remove a category of complaint: someone unhappy with recognition has somewhere to go that is neither a support ticket nor a refund. The choice turned out to be worth more as a release valve than as an upsell. That is not what I was aiming at, but it is a result I would take.