- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Designing a Maintenance Request System That Tenants Actually Use
About Post
A leaking tap is a simple problem. Getting it fixed often isn't. The tenant calls the office, gets told to send a photo on a messaging app, explains the unit number twice, waits, calls again to ask if anyone is coming, and finally someone turns up when nobody is home.
When we built maintenance requests into Kate PMS, I realised the real competitor wasn't another software product. It was the phone call and the chat message. If reporting a problem in the tenant app is slower or less reassuring than calling someone, tenants will call. And then the system is just a database that staff have to fill in by hand.
So the design goal was simple to say and harder to do: make the app the easiest way to get something fixed. Here's how I think about each part of the flow.
1. Reporting: under a minute, photo first
A tenant reporting a problem is usually standing in front of it, slightly annoyed, with a phone in their hand. So the flow starts with what they can do fastest: take a photo.
- Photo first, text optional. A picture of a water stain explains more than a paragraph, and it's easier than typing in a second language.
- Categories with icons (plumbing, electrical, air conditioning, and so on) instead of a long dropdown. They also help route the request on the staff side.
- Never ask for what we already know. The tenant is logged in and linked to their tenancy contract, so the unit is filled in automatically. Every field you remove is a reason not to pick up the phone instead.
- Access details up front. When it's convenient to visit, and whether someone can enter if nobody is home. This is the question that otherwise causes the most back-and-forth later.
The app is React Native with Expo, and two small technical choices matter a lot here. Photos are resized and compressed on the device before upload, because a full-resolution phone photo over a weak signal is a great way to make the submit button feel broken. And submissions are safe to retry, so a tenant tapping "Send" twice on a bad connection doesn't create two requests.
2. Status: a state machine, not a free-text field
The backbone of the whole feature is the request's status. It's tempting to make it a simple string column that anyone can change to anything. That leads to requests that jump from "submitted" straight to "closed" with no record of what happened.
Instead, statuses and their allowed transitions are explicit. In simplified form, the idea looks like this:
enum RequestStatus: string
{
case Submitted = 'submitted';
case Assigned = 'assigned';
case InProgress = 'in_progress';
case Completed = 'completed';
case Closed = 'closed';
public function canMoveTo(self $next): bool
{
return in_array($next, match ($this) {
self::Submitted => [self::Assigned],
self::Assigned => [self::InProgress, self::Submitted],
self::InProgress => [self::Completed],
self::Completed => [self::Closed, self::InProgress], // confirm or reopen
self::Closed => [],
}, true);
}
}
Every transition goes through one method that checks the rule, records who made the change and when, and fires an event. That history becomes the timeline the tenant sees, and it's also what makes it possible to see where requests tend to wait.
3. Assignment: the staff side matters just as much
A tenant app is only as good as what happens behind it. On the staff side, the goal is that nobody needs to pick up the phone to understand a request. Whoever assigns the work should be able to filter and sort the queue quickly, and the person doing the job should see the photos, the unit and the access details in the staff app before they arrive.
Role-based access does a lot of quiet work here. Different staff roles see and do different things: some assign, some update progress, some only view. Getting those boundaries right is less visible than a nice screen, but it's what keeps the data trustworthy.
4. Notifications: tell people what changed, not everything that happened
The number one reason tenants call about a request they've already submitted is uncertainty: "Did anyone see it? Is someone coming?" Push notifications answer that, but only if they're meaningful.
The rule I use: notify the tenant when something changes for them. Assigned, scheduled, completed. Not every internal note. That also means separating two kinds of comments in the data model: internal notes for staff ("waiting for a part from the supplier") and tenant-visible updates. Mixing them is a classic mistake, and an embarrassing one.
Technically, notifications belong in queued jobs, so a slow push service never slows down the staff member pressing the button.
The design rule behind all of this: every time a tenant has to ask "what's happening?", the system has failed to tell them. Status updates aren't a nice extra. They're the feature.
5. Closing the loop: the step most systems forget
In a lot of systems, the technician marks the job "done" and the request disappears. But "done" from the technician's point of view and "fixed" from the tenant's point of view aren't always the same thing.
So in the flow I design, completion isn't the end. The technician marks the work completed, ideally with an "after" photo. The tenant is then asked to confirm it's fixed, or to reopen it if it isn't, which sends it back to in progress with the history intact instead of starting a brand-new request. If the tenant doesn't respond, the request closes automatically after a while, so nothing stays open forever.
That small confirmation step changes the relationship. The tenant has the last word on whether their problem is solved, and staff get a clear signal when a fix didn't hold.
What I'd tell anyone building something similar
- Your competitor is the phone call. Measure every screen against "is this easier than calling?"
- Pre-fill everything you already know about the user.
- Model statuses as a state machine with a history, not a string column.
- Separate internal notes from customer-facing updates in the data model.
- Notify on changes that matter to the person receiving them.
- Let the person who reported the problem confirm it's actually fixed.
None of these ideas are specific to property management. Any app that handles requests (IT support, deliveries, repairs, internal approvals) has the same shape.
If you've built a request or ticketing flow, which part was harder than it looked? For me it was notifications: deciding what not to send took longer than building the sending.

Be first to comment it...