Ten iPhone screens from the Lnked app arranged in a tilted grid on near black: monthly reports, a property detail with its unit list, open maintenance requests, a tenant profile, recurring expenses, tenant messages, rent payments and overdue reminders.

Designing one app for two people who don't trust each other.

A property management CRM for independent landlords, designed and built solo and shipped to the App Store.

My role
Solo product designer and developer
Team
Solo. No PM, no engineers, no researcher.
Timeline
2026 · shipped March

The short version

Problem
Independent landlords run 1 to 20 units from a phone, across a spreadsheet, a notes app, a bank app and a text thread per tenant, so the same rent figure lives in four places and no two agree.
Approach
I designed and built the whole product solo: a separate portal per role, routed at login rather than one interface hiding fields by permission, and a tenant money flow that never asks anyone to do arithmetic.
Result
Shipped to the App Store as v1.0 in March 2026, 74 screens across 2 roles, after clearing five separate guideline rejections in a single pass.

Context and stakes

Independent property managers, people running 1 to 20 units rather than institutions, do the job out of a phone while standing in a driveway. The tooling they actually use is a spreadsheet, a notes app, a bank app, and a text thread per tenant.

The cost of that fragmentation is not inefficiency. It is that the same fact lives in four places and no two agree. What a tenant owes this month exists in the manager’s head, a spreadsheet cell, a Venmo request, and a text. When those diverge, the manager finds out during an argument.

The product brief I wrote for myself put it plainly:

You switch between tools to track tenants, leases, and maintenance. You lose context across properties and units. You manage tasks from a phone while on the move.

Constraints

Three, and they shaped more of the product than any preference did.

No team. No researcher, no PM to argue with, no engineer to tell me a thing was expensive. Every tradeoff in this case study is one I made and then had to build.

No users to test against. The product had no install base before v1, so there were no sessions to watch and nobody to interview.

A gatekeeper who could stop the release, and did. Apple rejected v1.0 on five separate guidelines in a single review pass. Four of them were design and product problems, not engineering ones.

I designed this and I built it, using AI assistance for a large share of the implementation. That is what made a 74-screen native app possible for one person in the time available, and it is why the decisions below are design decisions I then had to live with in code rather than hand off.

Research, and the reframe

This is a solo build with no user base, so there were no interviews and no usability testing before v1. What I had was the product brief, the build itself, and eventually the App Store review record. Where a decision below rests on reasoning rather than observation, I say so.

The reframe is that this is not a workflow tool, it is a system of record. Every competing product for this market is built around helping a manager do tasks faster. The actual failure is that nobody agrees what is owed. So the design goal became one place where the number is the number, legible to everyone with a stake in it. Almost everything below falls out of that, up to and including accepting payment methods the app cannot process.

The complication is that the two roles have opposed relationships to the same data. The manager owns and edits everything, and wants control and speed. The tenant sees only their unit, pays money, and wants not to get cheated. They are not colleagues. They are the two sides of a transaction, and the app has to be credible to both at once.

A tenant and a manager looking at the same rent charge want opposite things from the interface. The manager wants to record it in two taps. The tenant wants to understand why it is that number before paying it.

Explorations

One interface, or two

Rejected: one adaptive interface with permission-based hiding. Cheaper to build, and the standard move.

I rejected it because permission-hiding produces interfaces that feel like a fuller product with pieces missing: the tenant gets a nav bar built for someone else’s job. Worse, it makes leaks a matter of getting every conditional right. One missed check and a tenant sees another tenant’s balance. In an app whose core promise is that the number is the number, that failure is fatal to trust.

Chosen: two separate portals, routed at the root. Role resolves once at login and the user lands in a portal built only for them. The routing guard is deliberately blunt, because being in the wrong portal is a bug, not a state to render around. Row-level security enforces the same boundary in the database, so the split is a usability decision layered on a security guarantee rather than a substitute for one.

Login

Role resolved once, at login

  • Manager portal

    8 tabs · full CRUD

    • Reports
    • Properties
    • Tenants
    • Payments (designed twice, once per portal)
    • Maintenance (designed twice, once per portal)
    • Messages (designed twice, once per portal)
    • Reminders
    • Settings
  • Tenant portal

    4 tabs · one job

    • Home
    • Payments (designed twice, once per portal)
    • Maintenance (designed twice, once per portal)
    • Messages (designed twice, once per portal)

Row-level security

The same boundary, enforced again in the database rather than trusted to the interface.

A dot marks a concept that exists in both portals and was therefore designed twice. Three of the manager's eight, three of the tenant's four.

A tenant cannot reach a manager screen because it does not exist in their app, and the boundary is enforced a second time below the interface.

The tradeoff I accepted: every shared concept, payments and messaging and maintenance, needed two designs instead of one. That is real duplicated effort, and it is why the shared component layer had to exist. I would make the same call again. The duplication is bounded and visible; conditional-rendering bugs are unbounded and invisible.

What the manager portal opens on

The first build opened on Properties, the obvious choice since properties are the root object in the data model.

I changed it while using the app myself. A manager with six properties does not open the app to browse a list they have memorised. They open it to answer one of two questions: did the money come in? or what is on fire? A property list answers neither, and charges a tap before any information arrives.

So the portal opens on Reports, which answers the money question above the fold, and the notification bell carries the other. Properties moved one tap away, where browsing belongs. This is reasoning from my own use rather than from observed behaviour, and it is the first thing I would put in front of real managers.

The solution

Manager: eight tabs, three levels deep

Eight tabs is more than iOS convention encourages and more than I wanted. The role genuinely spans eight unrelated jobs, and burying any of them behind a More menu costs a tap on something done daily.

Depth is capped at three levels throughout: list, detail, edit. When a flow threatened a fourth, it became a modal instead.

Property setup discloses progressively, because it is the heaviest form in the app and the first thing a new manager hits. The first screen asks only what makes the record real (name, address, status) with units, leases and tenants added afterward from the property’s own detail screen. Address entry uses autocomplete rather than five free-text fields, because typing a full address on a phone in a driveway is exactly the friction that sends someone back to the spreadsheet.

Tenant: never make them do the math

The tenant portal is the part of this product I would defend hardest. Four tabs, one job, and unusually sharp constraints: a non-technical user, on a phone, about to move real money, who has every reason to be suspicious.

The home screen leads with a single card: the total, the due date, and the status. Nothing competes with it. Underneath sits a breakdown toggle (base rent, monthly fees, late fee if applicable) collapsed by default.

The collapse is the decision worth explaining, because I built it expanded first. Showing the itemisation by default is arguably more transparent. But it means every tenant does arithmetic every month to confirm a number the app already knows, and most months rent is just rent. Leading with the total respects that, and a tenant who does want to check, the month a late fee appears, is one tap away, with the itemisation never hidden behind a support request.

Tenant home screen. A single card leads with the status pill Due, the amount $1,875.00, the due date of Sep 30 2026 and a countdown reading Due in 24 days, above a View breakdown toggle and a full-width Pay Now button.
The default answers what to pay in one glance, without asking the tenant to add anything up.
The same card with the breakdown open, itemising base rent at $1,800.00 and monthly fees at $75.00 above a $1,875.00 total, with Pay Now still directly beneath it.
The toggle keeps the itemisation one tap away rather than behind a support request.

Payment methods, including two the app cannot process

The obvious build is card-only. One integration, shortest path to a working payment. I built four.

Credit card carries a 3% convenience fee. Bank transfer has none. Cash is arranged with the manager. Zelle goes through the tenant’s own banking app.

Pay Rent screen. A summary card gives a total of $1,875.00 and a Card Fee (3%) line adding $56.25 in amber. The payment method list below offers Visa ending 4242 and Mastercard ending 5555, each labelled 3% card fee, then Check, Cash and Zelle with no fee line at all. The button reads Pay $1,931.25.
The 3% sits on the card rows and in the total before the tenant commits, so the methods carrying no fee are the ones carrying no fee line.
Add Payment Method screen with the Bank Account tab selected, reading Connect your bank account for fee-free payments above an account holder name field, with green ticks for No additional fees and Secure bank verification via Stripe.
The fee-free alternative states its own terms on the form that adds it, rather than leaving the tenant to infer them.

Two things are happening there.

The fee is stated on the choice, not after it. A tenant picking a method is deciding how to spend money, so the 3% belongs at the moment of choosing rather than on a confirmation screen after they have committed. It also makes bank transfer visibly the cheaper option, which is the option that costs the manager less: a rare case where the honest disclosure and the business incentive point the same way.

Cash and Zelle are in the app even though the app cannot process them. A large share of independent-landlord rent is paid by check and Zelle. An app that only accepts cards does not convert those tenants to cards. It just stops knowing about their rent, and the manager is back to a spreadsheet for half their units. The core promise dies right there.

So offline methods are first class. The tenant records the payment and it enters the ledger as pending until the manager verifies it. The app gives up processing the money in order to keep being the system of record for it. Being the place where the number lives matters more than being the place the money moves through.

Worth naming as unfinished: check payment is half-built. The schema and the manager-side verification handle it, but a tenant cannot select it yet. Check is a large share of independent-landlord rent, which makes this the most valuable gap in the tenant flow rather than a cosmetic one.

Reports: making the money legible

Reports replaced a stats dashboard that showed counts, total properties and occupied units. Those look like information and answer nothing, because a manager already knows how many properties they have.

The redesign answers one question: did I make money this month, and where did it go? Three summary cards, three tabs, filters by property and date range, and export to PDF or CSV.

The Reports screen as the manager portal opens. Filters reading All Properties and This Month sit above a row of summary cards: Income $61,275.00 total received in green, Expenses $20,245.00 total spent in red, and a third net card scrolled partly out of frame. Below them, tabs for Overview, Income and Expenses with Overview selected, a Monthly Overview table running from Feb 2026 back to Sep 2025 with income, expense and net per month, and a Top Expense Categories list opening on Property Tax at 53% and $3,500. The bottom tab bar shows Reports selected, with Properties, Payments, Maintenance and Messages beside it.
The money question is answered on arrival, which is why this replaced the property list as the app's first screen.
The Expenses screen. A search field reading Search vendor or description sits above summary cards giving This Month $1,655.00 including recurring and This Year $22,020.00, with a third card scrolled partly out of frame, then filter chips for All Properties, All Categories and Recurring. A Recurring group of 3, totalling $1,655.00 a month, lists Landscaping from Green Thumb Landscaping at $275.00 monthly, Insurance from State Farm at $1,200.00 quarterly and Cleaning from Sparkle Clean Services at $180.00 monthly, each carrying an Active pill and a toggle switched on. A February 2026 group totalling $950.00 follows, opening on a $450.00 Maintenance entry from Austin Plumbing Co marked Paid.
Recurring costs are grouped and totalled apart from one-offs, so the second half of the question, where did it go, is answerable without opening a row.

Export exists because of a boundary I set early: this is not accounting software. It does not do double-entry bookkeeping and it does not file taxes, and trying would have made it a much worse product. It holds clean records and hands them to the accountant who does. Export is the seam where the product admits its own edge.

Removing recurring work

Reminders for rent, lease expiry and expenses fire on a schedule, with a snooze sheet rather than a binary dismiss. Snooze matters: “not now” is the honest answer to most reminders, and an interface offering only deal with it or make it disappear trains people to dismiss everything.

Receipt scanning photographs a receipt and extracts vendor, amount, date and a suggested category. Expense entry is the most tedious and most-skipped task in the product, and its failure mode is not a wrong expense, it is no expense record at all and then reports that lie. Extracted values land in an editable form rather than saving silently, so a bad read is a correction rather than a corrupted record.

The system holding both portals together

A shared primitive layer is the structural answer to the duplication cost the routing decision accepted. One token set with full light and dark palettes, and one primary blue across both portals, deliberately: they differ in what they show, not in feeling like different apps. Status is named by meaning rather than hue (occupied, vacant, maintenance, success, warning, error).

Two components carry more weight than their size suggests.

Empty states are the real onboarding. Every list can be empty, and for a new manager most lists are empty at once. Each one names what goes there and offers the action that creates the first one.

The offline banner exists because this app is used in basements and stairwells, where connectivity is a normal condition rather than an error. It also shipped a false-positive bug, appearing when the device was fine, which is worse than having no banner: it teaches people to distrust the one signal meant to tell them whether their data is real.

Shipping it

Five App Store rejections in one pass

v1.0 came back rejected on five separate guidelines, reviewed on an iPad. Four of the five were design and product problems.

GuidelineIssueFix
2.1(a)Crash when adding a propertyDeferred payment SDK init, anchored the action sheet
3.1.1No Restore Purchases controlExplicit button on the paywall and both settings screens
5.1.1(v)No account deletion pathIn-app deletion flow with double confirmation
3.1.2(c)Missing subscription metadataPrice, period, auto-renewal terms, terms and privacy links
2.3.10Google Play references in an iOS binaryRemoved

Two taught me something.

Restore Purchases. The app did restore purchases, automatically, on launch. I had removed the manual button deliberately, reasoning that automatic restoration is strictly better than making someone hunt for a button to get back what they already own.

Apple rejected it anyway, and the guideline is explicit that auto-restore does not satisfy the requirement. The principle I had missed: a user who has lost access needs a control they can press. Automatic recovery that fails is indistinguishable from no recovery at all, because the user is left with no move to make. The button is not primarily a mechanism, it is the visible affordance that recovery is possible. Invisible automatic behaviour is only good UX when it works.

Account deletion was a genuine omission. I had built the entire account lifecycle except its ending. Deletion is easy to skip because no roadmap asks for it and no user requests it until they want to leave, and by then they cannot tell you.

The crash is worth one more note, because a design decision caused it. Apple tested on an iPad, where the app’s modals render as form sheets. Add Property crashed on a missing location permission: the address autocomplete, the convenience feature meant to reduce typing friction, requested location without the usage description declared. A feature added to save a tenant thirty seconds of typing became the crash that blocked the release. The fix declared the permission, made the hook degrade to manual entry when someone denies it, and forced full-screen modals on iPad.

All five were fixed in a single pass and the app shipped.

The third role I decided not to build

There is an obvious third user for a product like this: the owner, the person who holds the property and wants proof the manager is doing the job. It exists in the data model and as a pricing tier. It was never designed, and it did not ship.

That was a decision rather than an omission. A manager and a tenant form a closed loop: one collects rent, the other pays it, and the product is complete with just those two. An owner is a third party to a transaction that already works without them, and everything they would see is read-only, which makes it build cost with no effect on whether the core loop functions. Two roles that genuinely need each other is a product. Three roles where one is a spectator is a roadmap.

Being honest about the current state: because the role exists in the schema, an owner account today is signed out at login rather than shown an explanation. That is a rough edge, not a design.

Pricing

Three tiers, gated on the constraint that actually correlates with getting value from the product: portfolio size. A landlord with one unit uses it free forever, which is intentional. They are the least able to pay and the most likely to recommend it. Paying starts when the product is demonstrably carrying real work.

Locked features stay visible but marked rather than hidden, because a user who cannot see a feature cannot want it.

Results

  • 74 screens shipped solo

    Across two roles and two portals, designed and built by one person.

  • 5 guideline rejections cleared in one pass

    Four of the five were design and product problems rather than engineering ones.

  • 4 tenant payment methods, two of them offline

    The app gives up processing two of them in order to stay the record of all four.

Scope delivered and the App Store review record, as of the v1.0 release in March 2026. These are counts of what shipped, not usage figures.

Live on the App Store since March 2026.

There are no usage figures yet: no installs, no active managers, no payment volume. Naming that is more useful than a number I cannot source, and closing it is the third item below.

See Lnked on the App Store (opens in a new tab)

What I’d do differently

Validate the Reports-first decision. It is the highest-leverage assumption in the manager portal and the cheapest to test. Five managers and a first-run observation would settle it.

Fix the accessibility gaps. The reports cards distinguish income from expense by colour alone, which fails for roughly one man in twelve, and screen-reader labelling and dynamic-type support have not been audited. The colour issue is a defect, not a nice-to-have.

Instrument the payment funnel. The tenant portal has explicit success targets, payment success above 97% and median time-to-pay under 30 seconds, and no analytics to measure them against. Targets you cannot measure are not targets.

What I’d tell someone building this next

Route by role, do not render by permission. Two deliberately separate portals cost real duplicated work and bought a guarantee no conditional could: a tenant cannot see another tenant’s balance, because the screens that show it do not exist in their app.

Support the payment methods people actually use, even the ones you cannot process. Being the system of record is worth more than being the payment processor.

Put the cost on the choice, not the confirmation. Fee disclosure belongs where the decision is made.

Automatic recovery still needs a button. Invisible behaviour is only good UX when it works. When it fails, the user needs something to press, and this cost me a rejection to learn.

Design the ending, not just the onboarding. Account deletion is invisible on every roadmap and required by the platform. The people who need it cannot ask you to build it, because by then they are already gone.