USSD Apex is a USSD-based interface for the APEX rentals management system. It enables low-end mobile phone users (Kaduda) to access full property management services without a smartphone or internet connection. A built-in browser simulator allows developers to test and iterate on all USSD flows in real time.
| Goal | Detail |
|---|---|
| Accessibility | Serve feature-phone users via USSD (384123#) |
| Business Process | Mirror a real property management lifecycle end-to-end |
| Role-Based Access | Distinct menus & permissions for each stakeholder |
| Simulation | Local browser simulator for full flow testing |
The system models the full property management lifecycle. Here is how each actor fits into the process:
[New User Dials In]
│
├─ Not registered? ──▶ REGISTRATION FLOW (name → role → PIN)
│ │
│ [Tenant] ──▶ Dial again → browse units → landlord assigns
│ [Landlord] ──▶ Dial again → add property → add units → assign tenants
│
└─ Registered? ──▶ PIN LOGIN ──▶ Role-Based Menu
│
┌────────────────┼──────────────────────┐
[Tenant] [Landlord] [Manager] [Admin]
A Tenant is a paying occupant of a property unit. Tenants self-register via USSD, but must be assigned to a unit by their Landlord before they can pay rent or report issues.
| Feature | Condition |
|---|---|
| Pay Rent | Requires unit assignment |
| Check Balance (rent due) | Always available |
| Report Maintenance Issue | Requires unit assignment |
| View Payment History | Always available |
| Submit Notice to Vacate | Requires unit assignment |
A Landlord owns one or more properties. They register via USSD and then build their property portfolio within the system.
Landlord workflow:
- Register → Login
- Add Property (e.g., "Sunset Apartments")
- Add Units to the property (unit number + monthly rent)
- Assign Tenant to a vacant unit using the tenant's registered phone number
- Monitor Occupancy, Rent Collections, and Pending Issues
| Feature | Description |
|---|---|
| Occupancy Summary | % occupied vs. vacant across all properties |
| Rent Collections | Expected vs. collected rent + yield % |
| Add Property | Name a new property to portfolio |
| Add Unit | Add a unit (number + rent) to a property |
| Assign Tenant | Link a registered tenant to a vacant unit |
| Pending Issues | View open maintenance requests |
A Manager is promoted from an existing user by the Admin (not self-registerable). Managers handle day-to-day operations across all properties.
| Feature | Description |
|---|---|
| View Pending Maintenance | See all open issues with unit and description |
| Resolve Issue | Mark a request resolved by ID |
| View Vacant Units | See all unoccupied units system-wide |
| Approve Vacate Notices | Approve tenant move-outs, freeing the unit |
| View All Tenants | List all tenants with their assigned units |
Admins have full system visibility and manage user roles. Admin accounts are seeded manually (not self-registered).
| Feature | Description |
|---|---|
| System Overview | Total users, properties, units, open issues |
| Users by Role | Breakdown of all user roles |
| Promote to Manager | Upgrade any user to Manager by phone number |
| Pending Payments | View all unprocessed payment records |
| Layer | Technology |
|---|---|
| Runtime | Node.js |
| Framework | Express |
| Language | TypeScript |
| Database | PostgreSQL (via pg) |
| Dev Tools | ts-node-dev, concurrently, wait-on, open-cli |
- Node.js v14+
- PostgreSQL running locally
Create or confirm your .env file at the project root:
PORT=3000
DB_USER=postgres
DB_HOST=localhost
DB_NAME=property_management
DB_PASSWORD=yourpassword
DB_PORT=5432npm installRun the schema SQL against your PostgreSQL instance. This creates all tables and inserts seed data:
psql -U postgres -d property_management -f schema.sqlOr use the helper script:
node init_db.jsnpm run devThis concurrently:
- Starts the Express backend (
ts-node-devwith hot-reload) - Waits for the server to be ready, then opens the USSD simulator in your browser
The browser simulator at http://localhost:3000 lets you select a pre-seeded user or enter a custom phone number.
| Name | Phone | Role |
|---|---|---|
| John Tenant | +254712345678 | Tenant (assigned to A1, has outstanding balance) |
| Jane Tenant | +254711111111 | Tenant (assigned to A2, balance clear) |
| Alice Landlord | +254722222222 | Landlord (owns Sunset Apartments & Sunrise Villas) |
| Bob Manager | +254733333333 | Property Manager |
| Admin Super | +254744444444 | System Admin |
| New User | +254799999999 | Unregistered — triggers registration flow |
- Select New Unregistered User (+254799999999) in the simulator
- Click Dial *384*123#
- Enter your Full Name → Send
- Select 1. Tenant or 2. Landlord → Send
- Enter a 4-digit PIN → Send
- Registration complete — dial again to log in
- Select John Tenant
- Dial → Enter PIN
1234→ Send - Select
1. Pay Rent→ Enter amount → Send - Payment is logged as
pendingin the database
- Select Alice Landlord → Dial → PIN
1234 - Select
4. Add Unit→ choose property → enter unit number → enter rent - Select
5. Assign Tenant→ choose the new unit → enter Jane Tenant's phone number - Jane is now linked to the unit and can pay rent
ussdApex/
├── .env # Environment variables
├── package.json # Scripts and dependencies
├── schema.sql # DB schema + seed data
├── init_db.js # DB initialization helper
├── public/
│ └── index.html # USSD browser simulator
└── src/
├── app.ts # Express entry point
├── database/
│ └── db.ts # PostgreSQL connection pool
├── shared/
│ └── utils/logger.ts # Structured logger
└── modules/
└── ussd/
├── controllers/
│ └── ussd.controller.ts # HTTP handler
├── routes/
│ └── ussd.routes.ts # POST /api/ussd
├── services/
│ └── menu.service.ts # All USSD business logic
└── types/
└── ussd.types.ts # TypeScript interfaces
- Every USSD interaction updates the
ussd_sessionstable with thesession_idand current inputtext - Returning users are identified by
phone_numberand must enter their 4-digit PIN before accessing any menu - Sessions are stateless — the full input chain (
text=step1*step2*step3) is replayed on every request, making the system horizontally scalable - Manager and Admin roles cannot be self-registered — Managers are promoted by Admin; Admins are seeded