Manual rental management in Senegal or Ivory Coast is a nightmare: Excel for rents, WhatsApp for comms, paperwork for leases, physical rounds for collection. Past 30-50 managed rentals, a dedicated SaaS becomes indispensable.
TL;DR
- Stack: Next.js + multi-tenant Postgres + Wave/OM API + WhatsApp.
- Core features: leases, inventories, recurring rents, charges, maintenance tickets.
- Francophone Africa market: ~200K+ owners managing 5-200 rentals in 2026.
Global architecture
`
[Tenant] [Owner / manager]
↓ ↓
[Tenant app] [Manager dashboard]
↓ ↓
[Multi-tenant SaaS backend]
↓
[Postgres + Redis]
↓
┌──────────┴──────────┐
↓ ↓
[Wave / OM] [Brevo + WhatsApp]
`
Step 1 — multi-tenant data model
`prisma
model Organization {
id String @id @default(cuid())
name String
email String
plan Plan @default(STARTER)
properties Property[]
users User[]
}
model Property {
id String @id @default(cuid())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
reference String
address String
city String
district String
type String
surface Int
rentAmount Int
charges Int
deposit Int
isOccupied Boolean
currentLeaseId String?
leases Lease[]
tickets MaintenanceTicket[]
}
model Lease {
id String @id @default(cuid())
organizationId String
propertyId String
property Property @relation(fields: [propertyId], references: [id])
tenantId String
tenant Tenant @relation(fields: [tenantId], references: [id])
startDate DateTime
endDate DateTime
rentAmount Int
charges Int
deposit Int
paymentDueDay Int
status LeaseStatus
inventoryIn Inventory? @relation("InventoryIn")
inventoryOut Inventory? @relation("InventoryOut")
payments RentPayment[]
}
model Tenant {
id String @id @default(cuid())
organizationId String
firstName String
lastName String
email String
phone String
whatsapp String?
cniNumber String
ninea String?
emergencyContact String?
leases Lease[]
}
model RentPayment {
id String @id @default(cuid())
organizationId String
leaseId String
lease Lease @relation(fields: [leaseId], references: [id])
month String
amount Int
charges Int
status PaymentStatus
paidAt DateTime?
paymentMethod String?
reference String?
@@unique([leaseId, month])
}
model MaintenanceTicket {
id String @id @default(cuid())
organizationId String
propertyId String
property Property @relation(fields: [propertyId], references: [id])
category String
description String
status TicketStatus
reportedAt DateTime @default(now())
resolvedAt DateTime?
cost Int?
}
model Inventory {
id String @id @default(cuid())
leaseId String @unique
type String
date DateTime
rooms Json
pdfUrl String?
signedTenantUrl String?
signedOwnerUrl String?
}
`
Step 2 — strict multi-tenancy
All queries must filter by organizationId. Global Prisma plugin:
`ts
import { Prisma, PrismaClient } from '@prisma/client';
export function tenantPrisma(organizationId: string) {
return new PrismaClient().$extends({
query: {
$allModels: {
async $allOperations({ args, model, operation, query }) {
if (['Organization', 'User'].includes(model ?? '')) {
return query(args);
}
if (operation.startsWith('find') || operation === 'count' || operation === 'aggregate') {
Need a professional website?
Kolonell builds websites that attract clients, optimized for the Sénégalese market. Free quote in 2 minutes.
args.where = { ...args.where, organizationId };
}
if (operation === 'create') {
args.data = { ...args.data, organizationId };
}
return query(args);
},
},
},
});
}
`
Without this middleware, cross-tenant data leak = catastrophe.
Step 3 — automated Wave/OM rent collection
Monthly cron (1st of month):
`ts
export async function generateMonthlyPayments() {
const today = new Date();
const monthKey = ${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')};
const activeLeases = await prisma.lease.findMany({
where: { status: 'ACTIVE' },
include: { property: true, tenant: true, organization: true },
});
for (const lease of activeLeases) {
const payment = await prisma.rentPayment.create({
data: {
organizationId: lease.organizationId,
leaseId: lease.id,
month: monthKey,
amount: lease.rentAmount,
charges: lease.charges,
status: 'PENDING',
},
});
const totalAmount = lease.rentAmount + lease.charges;
const paymentLink = await createWavePaymentLink({
amount: totalAmount,
currency: 'XOF',
reference: payment.id,
callbackUrl: https://app.kolonell.com/api/webhooks/rent-payment,
});
await sendWhatsApp(lease.tenant.whatsapp ?? lease.tenant.phone, {
template: 'rent_due',
params: [
lease.tenant.firstName,
monthKey,
totalAmount.toLocaleString(),
paymentLink,
],
});
}
}
`
D+5, D+10, D+15 reminders cron:
`ts
const overdue = await prisma.rentPayment.findMany({
where: {
status: 'PENDING',
month: previousMonth,
createdAt: { lt: new Date(Date.now() - 5*24*60*60*1000) },
},
include: { lease: { include: { tenant: true, property: true } } },
});
for (const p of overdue) {
await escalateOverdue(p);
}
`
Step 4 — digital inventory
`tsx
'use client';
import { useState } from 'react';
export default function InventoryWizard({ leaseId, type }) {
const [rooms, setRooms] = useState([]);
return (
{rooms.map((room, i) => ( ))} Generate PDF + sign (DocuSign / Yousign){type === 'ENTRY' ? 'Move-in' : 'Move-out'} inventory
);
}
function RoomEntry({ room, onUpdate }) {
return (
Condition
Photos
Notes
);
}
`
PDF auto-generated with embedded photos + e-signatures.
Step 5 — SaaS pricing
| Plan | Monthly | Limits |
|---|---|---|
| Starter | 25K XOF | 1-10 rentals |
| Pro | 75K XOF | 11-50 rentals |
| Business | 200K XOF | 51-200 rentals |
| Enterprise | Custom | 200+ rentals |
Market: ~10K-20K eligible real-estate management SMEs in Francophone Africa. Potential market: 5-15 billion XOF annual.
Real case — Cabinet Diallo (Dakar)
Profile: 87 managed rentals, 3 employees.
| Metric | Before SaaS | After 6 months |
|---|---|---|
| Admin tasks/month | ~280h | ~95h |
| Late rents | 18% | 6% |
| Maintenance tickets tracked | 60% | 100% |
| Deposit disputes | 12/year | 2/year |
| Accounting errors | 8/month | 0-1/month |
Savings: ~1.2M XOF/month salary equivalent + zero litigation.
FAQ
Q: Competition on this market?
A: Some international solutions (LIMS, AppFolio) ill-fit for Africa. Local: Diaspora-Conseil, NestImmo (CI). Immature market = opportunity.
Q: GDPR/CDP tenant data compliance?
A: Critical. See GDPR + Law 2008-12 guide →.
Q: Bank integration for auto transfers?
A: Limited in 2026. SGBS, Société Générale Senegal, BNP Paribas Africa offer limited APIs. Wave/OM = much more mature.
Conclusion
Rental management SaaS in Africa is an emerging market in 2026 — clear opportunity for clean builders. 6-12 month MVP technical stack, potential 50-200M XOF/year ARR at 24 months on Senegal+CI alone.
Mohamed Bah
Fondateur, Kolonell
Passionate about digital and entrepreneurship in Africa, Mohamed has been helping Sénégalese businesses with their digital transformation since 2020. Founder of Kolonell, he believes every SME deserves a professional and accessible online présence.

