Building a SaaS product in public is often presented as a marketing strategy: post screenshots, share monthly revenue, publish a roadmap, and turn the audience into customers.
That is one version of it. But for us, building in public has been more valuable as an engineering and product discipline.
When you explain why a workflow exists, publish the assumptions behind it, and invite people to challenge those assumptions, vague product thinking becomes visible. You cannot hide behind a polished interface when users are asking practical questions such as:
What happens when a tenant pays only part of the rent?
Is a maintenance request the same thing as a work order?
Who can view a lease document?
Can one user manage several properties without mixing their records?
What happens when a payment or maintenance status is changed by mistake?
These are not merely interface questions. They expose the quality of the domain model, authorization rules, data history, and product scope.
This article shares lessons from developing MyEstateManager, a SaaS product for organizing landlord and rental-property operations. It is not a reveal of our private production architecture, and the code examples are intentionally illustrative. The goal is to explain the technical and product principles that have shaped the work.
- Start With the Operational Problem, Not the SaaS Category
“Property management software” sounds like a clear category until you try to build it.
It can include marketing vacancies, screening applicants, signing leases, tracking rent, managing expenses, communicating with tenants, assigning vendors, storing documents, producing financial reports, and much more.
Trying to represent the entire category in an MVP produces a wide but shallow application. Every screen exists, but few workflows are complete enough to replace the spreadsheet, inbox, messaging thread, or paper folder already used by the customer.
We learned to define the product through operational jobs instead:
Know which tenant occupies which unit.
Know what rent is due, paid, partially paid, or overdue.
Keep leases, receipts, invoices, and property records connected to the right entities.
Record a maintenance issue and follow it through resolution.
Retrieve the history behind a decision without searching several systems.
This framing changes the roadmap. Features are no longer isolated boxes. They are parts of a workflow with inputs, transitions, permissions, and outputs.
A product page can list capabilities, but the more useful view is how those capabilities connect. That is why our How MyEstateManager works explanation focuses on the operating flow rather than a collection of disconnected screens.
Product lesson
Before creating a feature ticket, write the operational outcome in one sentence:
After this workflow is complete, what should the user know, prove, or do that they could not do reliably before?
If the team cannot answer that question, the feature is probably still a concept rather than a product requirement.
- Model the Domain Before Designing the Dashboard
Dashboards are attractive starting points because they make a product feel real. But a dashboard is a projection of underlying facts. If those facts are poorly modeled, the dashboard becomes a collection of numbers that cannot be trusted.
In a rental system, the obvious entities are not enough. Property, unit, tenant, lease, payment, expense, document, maintenance request, and vendor are related through time.
For example, a tenant does not permanently “belong” to a unit. A lease connects one or more tenants to a unit for a defined period. Payments normally apply to an obligation or accounting period, not simply to a tenant. A document might relate to a property, a lease, a maintenance issue, or several of them.
A simplified relational model might begin like this:
create table workspaces (
id uuid primary key,
name text not null
);
create table properties (
id uuid primary key,
workspace_id uuid not null references workspaces(id),
name text not null
);
create table units (
id uuid primary key,
workspace_id uuid not null references workspaces(id),
property_id uuid not null references properties(id),
label text not null
);
create table leases (
id uuid primary key,
workspace_id uuid not null references workspaces(id),
unit_id uuid not null references units(id),
starts_on date not null,
ends_on date,
status text not null
);
Notice that workspace_id is repeated even where it could theoretically be inferred through another table. That repetition can support explicit isolation checks and simpler query policies, provided consistency is enforced.
The exact schema will vary, but the principle is stable: model real relationships and time boundaries before turning them into cards and charts.
Technical lesson
Use the interface to reveal the domain model, not to invent it. If a relationship cannot be expressed clearly in the data model, adding a dropdown usually postpones the problem rather than solving it.
- Treat Tenant Isolation as an Architectural Boundary
In SaaS terminology, “tenant” often means a customer account or workspace. In property software, “tenant” also means a person renting a unit. That naming collision alone is a reason to use precise language in code.
We prefer terms such as workspace, organization, or account for the SaaS boundary and reserve tenant for the rental-domain entity.
More importantly, workspace isolation must be applied below the interface. Hiding another account’s records in the frontend is not security. Every read and write must be scoped to the authenticated workspace.
An illustrative service method might look like this:
async function getMaintenanceRequest(
requestId: string,
workspaceId: string
) {
return db.maintenanceRequest.findFirst({
where: {
id: requestId,
workspaceId
}
});
}
The important part is not the ORM syntax. It is that the workspace boundary is present in the query itself.
Useful safeguards include:
deriving the workspace from authenticated context rather than accepting it blindly from the client;
including workspace scope in repository or service-layer methods;
checking parent-child ownership when records are connected;
using database constraints or row-level security where appropriate;
testing attempts to access records belonging to another workspace;
scoping cache keys, background jobs, exports, and file paths as carefully as API queries.
Building-in-public lesson
Public product updates tend to celebrate visible progress. Security boundaries rarely produce impressive screenshots, but they are part of the product. Sharing the reasoning behind these invisible decisions helps keep trust work on the roadmap.
- A Workflow Needs States, Transitions, and History
Many early SaaS products store a status field and consider the workflow complete. The difficulty begins when any user or API call can change that status to any other value.
Consider maintenance management. A reported issue might move through:
submitted → under_review → approved → assigned → in_progress → completed → verified → closed
Not every product needs every state, but the allowed transitions should be deliberate. A closed request should not silently jump back to submitted. Completing work may require a resolution note. Closing it may require confirmation or a documented override.
One simple approach is to define transitions centrally:
type MaintenanceStatus =
| "submitted"
| "under_review"
| "approved"
| "assigned"
| "in_progress"
| "completed"
| "verified"
| "closed";
const allowedTransitions: Record = {
submitted: ["under_review"],
under_review: ["approved", "closed"],
approved: ["assigned", "closed"],
assigned: ["in_progress", "approved"],
in_progress: ["completed"],
completed: ["verified", "in_progress"],
verified: ["closed", "in_progress"],
closed: []
};
function canTransition(
from: MaintenanceStatus,
to: MaintenanceStatus
) {
return allowedTransitions[from].includes(to);
}
The state change should also produce a history record containing who changed it, when, why, and—where relevant—which related record was created.
This thinking informed how we explain maintenance management for landlords: the value is not merely recording a problem but retaining the workflow around it.
Product lesson
Status labels should correspond to real decisions. If two labels do not change what the system or user can do next, they may be unnecessary complexity.
- Build an Activity Trail Before Customers Ask for One
Operational software changes meaning over time. A balance can change. A lease can be renewed. An expense can be corrected. A maintenance request can be reopened.
If the system stores only the latest state, the user eventually asks a question it cannot answer: “How did we get here?”
An activity record can capture important changes without requiring a complete event-sourced architecture:
create table activity_events (
id uuid primary key,
workspace_id uuid not null references workspaces(id),
actor_user_id uuid,
entity_type text not null,
entity_id uuid not null,
action text not null,
metadata jsonb not null default '{}'::jsonb,
occurred_at timestamptz not null default now()
);
The difficult decision is what belongs in metadata. Storing every before-and-after object can expose sensitive information or create unnecessary volume. Storing too little makes the event useless.
A better rule is to record the smallest amount of information needed to explain the action safely. For a status change, that could be the previous status, new status, reason, and related assignment identifier.
Technical lesson
Auditability is not just logging. Application logs help engineers diagnose the system; activity history helps authorized users understand the business process. They have different audiences, retention needs, and privacy considerations.
- Permissions Must Follow Actions, Not Page Names
A common early permission model has two roles: admin and user. It works until the product supports assistants, accountants, maintenance coordinators, owners, or external collaborators.
Page-level access is also too coarse. Someone may be allowed to view a property but not its financial records. An accountant may need expense exports but not tenant communications. A maintenance coordinator may update work status without viewing lease documents.
Action-oriented permissions are easier to reason about:
type Permission =
| "property.read"
| "property.manage"
| "lease.read"
| "lease.manage"
| "finance.read"
| "finance.export"
| "maintenance.assign"
| "maintenance.close"
| "document.read"
| "document.manage";
Roles can then become named bundles of permissions. The backend still authorizes the specific action against the workspace and, where necessary, the record.
This is more work than conditionally hiding a navigation item. It also creates a stronger foundation for teams, integrations, and delegated operations.
Building-in-public lesson
When discussing upcoming collaboration features publicly, describe the user responsibilities being supported rather than promising a complicated role system before its rules are understood.
- Document Storage Is a Product Workflow, Not an Upload Button
Rental operations generate leases, identity and screening records, invoices, inspection images, receipts, notices, and maintenance evidence. Adding file upload appears simple until the product must answer:
Who may access this file?
Which property, lease, payment, or request does it support?
Can the file be replaced, and should the old version remain available?
How long should it be retained?
What happens when the related record is deleted?
Is the link private, expiring, and safe to share?
A useful separation is to keep file bytes in object storage while storing authorization and domain metadata in the database.
interface DocumentRecord {
id: string;
workspaceId: string;
storageKey: string;
originalName: string;
mimeType: string;
sizeBytes: number;
category: "lease" | "invoice" | "receipt" | "inspection" | "other";
relatedEntityType: string;
relatedEntityId: string;
uploadedByUserId: string;
uploadedAt: string;
}
The application should authorize access before producing a short-lived download URL. Public, permanent object URLs are rarely appropriate for private rental records.
Our rental document management guide discusses the user-facing workflow. The technical lesson beneath it is that documents should inherit the same workspace, permission, and retention boundaries as the records they support.
- Instrument Decisions, Not Vanity Metrics
Building in public creates pressure to report numbers. Visitors, sign-ups, followers, and feature counts are easy to publish, but they do not necessarily explain whether the product is becoming more useful.
For an operational SaaS, more informative product events might include:
first property created;
first unit connected to a property;
first lease recorded;
first rent record completed;
first maintenance request resolved;
first document retrieved after upload;
return to the product during a later operating cycle.
These events reveal progress through the product’s value path. They can also expose friction. If many accounts create a property but few create a unit, the issue may be onboarding, terminology, missing data, or a broken workflow—not lack of another feature.
An event should have a clear question behind it:
track("maintenance_request_completed", {
workspaceId,
requestId,
resolutionTimeBucket,
completionPath
});
Avoid placing unnecessary personal or sensitive information in analytics. Product instrumentation should follow the same privacy discipline as the application itself.
Product lesson
Do not ask, “What can we measure?” Ask, “Which product decision will change depending on this result?”
- Publish the Learning Around the Product
Building in public should create more than release announcements.
The most useful public material often comes from the questions encountered while building:
What information should a maintenance request contain?
What should landlords track about rent?
How should rental documents be organized?
Where does property-management software end and accounting software begin?
Which workflows deserve automation, and which require human review?
Publishing educational resources forces the team to make its reasoning understandable. It also allows prospective users to benefit before creating an account.
This is why MyEstateManager’s public material includes workflow-focused resources alongside its product features. The articles are not a substitute for the software, and the software is not a substitute for legal, tax, or accounting advice. Each has a different job.
Building-in-public lesson
Teach from the problem space, not just from the product. A changelog says what you shipped. A useful technical or operational article explains why the underlying problem deserves a particular solution.
- Free Access Does Not Remove the Need for Trust
MyEstateManager is currently free for all users through the end of 2026. Free access lowers the cost of trying the product, but it does not lower the trust threshold.
Users still need to understand:
what the product is designed to do;
which information they should store in it;
how access is controlled;
what support and documentation are available;
what the product does not claim to replace;
how pricing or access may evolve later.
This has shaped an important product principle: reduce adoption friction without reducing clarity.
A free period can support learning and feedback, but it should not be used to excuse vague policies, weak onboarding, or unfinished data boundaries. Trust is part of the architecture and the communication around it.
What We Would Do Earlier
If we restarted the product journey, we would prioritize several practices sooner.
Write workflow specifications before screen specifications
Define actors, inputs, allowed transitions, exceptions, permissions, and completion conditions. Then design the interface.
Create a shared language for the domain
Terms such as tenant, account, request, work order, payment, charge, due, and overdue need one documented meaning across engineering, design, content, and support.
Add activity history alongside important mutations
Retrofitting useful history after users depend on a workflow is harder than recording intentional events from the beginning.
Test workspace isolation as a feature
Include cross-workspace access attempts in automated tests for reads, writes, exports, background jobs, and files.
Connect analytics to roadmap decisions
Every tracked event should support a defined product question. Remove events that collect data without informing action.
Publish assumptions, not fabricated certainty
Building in public is useful when it exposes reasoning and invites correction. It becomes noise when every experiment is presented as a universal lesson.
A Practical Build-in-Public Update Template
For founders and product teams building a vertical SaaS, a useful update can be short:
Problem observed: What real workflow or user question triggered the work?
Current assumption: What do you believe is causing the problem?
Product decision: What are you changing, and why?
Technical constraint: Which data, permission, security, or reliability concern shapes the solution?
Evidence sought: What behavior or feedback would validate or challenge the decision?
What remains uncertain: What are you deliberately not claiming yet?
This structure is more useful than announcing that a team “worked hard on an exciting feature.” It gives other builders something they can evaluate and gives users a clear way to respond.
Final Takeaway
The hardest part of vertical SaaS is rarely generating a feature list. It is translating a messy real-world operation into software without losing the relationships, exceptions, responsibilities, and history that make the operation understandable.
Building MyEstateManager has reinforced a few principles:
model workflows before dashboards;
enforce workspace boundaries at every layer;
treat status changes as controlled transitions;
preserve enough history to explain important outcomes;
authorize actions, not just pages;
connect files to domain records and privacy rules;
measure progress through meaningful user outcomes;
teach what you are learning without pretending every assumption is proven.
Building in public does not require publishing private customer data, proprietary code, or impressive-sounding metrics. It requires making the thinking visible: the problem, the constraint, the decision, and what the team still needs to learn.
That is the version of building in public we want to continue practicing with MyEstateManager.













