Product Development

Product Map of Vhoye - Building a Loyalty Platform from Scratch

The complete journey of building Vhoye, a loyalty and intelligence solution for hospitality, from MVP to full-scale platform across iOS, Android, and Web. A startup story with all the highs, lows, and "why didn't we think of that earlier" moments.

5 min read
product-development startup mobile-app loyalty-platform hospitality mvp

In December 2020, while working on my final year project, I co-founded Tathyakar Technologies with a simple observation: restaurants and hotels in Nepal were losing customers not because of bad service, but because they had no way to build lasting relationships. Enter Vhoye - our attempt to solve customer loyalty in the hospitality industry. Spoiler alert: it was harder than we thought.

The Problem: Why Loyalty Programs Fail

During our market research in Pokhara and Kathmandu, we discovered that most hospitality businesses had the same problems:

The Problems

Our Vision

The "Aha" Moment

We realized that restaurants needed more than just a loyalty program - they needed a complete customer relationship management system that was simple enough for a busy restaurant owner to use but powerful enough to drive real business results.

Product Architecture: Building for Scale

Vhoye was designed as a multi-platform ecosystem with three main components: customer-facing apps, business dashboard, and analytics engine. Here’s how we structured it:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│   Customer App  │    │  Business Portal │    │ Analytics Engine│
│   (iOS/Android) │    │      (Web)       │    │   (Backend)     │
└─────────────────┘    └──────────────────┘    └─────────────────┘
         │                        │                        │
         └────────────────────────┼────────────────────────┘
                                  │
                    ┌─────────────────────────┐
                    │      Core API           │
                    │   (Node.js + Express)   │
                    └─────────────────────────┘
                                  │
                    ┌─────────────────────────┐
                    │     Database Layer      │
                    │  (PostgreSQL + Redis)   │
                    └─────────────────────────┘

Technology Stack Decisions

Choosing the right tech stack was crucial. We needed to move fast, but also build something scalable. Here’s what we chose and why:

Frontend: React Native + React

React Native allowed us to build iOS and Android apps with a single codebase. React for the web dashboard gave us consistency and faster development. The learning curve was worth the productivity gains.

Backend: Node.js + Express

JavaScript everywhere meant faster development and easier hiring. Express gave us flexibility, and Node.js handled our real-time requirements well. Plus, the npm ecosystem saved us months of development time.

Database: PostgreSQL + Redis

PostgreSQL for relational data (customers, transactions, businesses) and Redis for caching and real-time features. The combination gave us ACID compliance with performance.

Phase 1: MVP Development (Jan - May 2021)

Our first version was embarrassingly simple, but it worked. The core features were:

Customer App Features

Business Portal Features

Core API Design

// User authentication and session management
const authRoutes = {
  'POST /auth/login': authenticateUser,
  'POST /auth/register': registerUser,
  'POST /auth/verify-otp': verifyOTP,
  'DELETE /auth/logout': logoutUser
};

// Customer management
const customerRoutes = {
  'GET /customers': getCustomers,
  'GET /customers/:id': getCustomerById,
  'POST /customers': createCustomer,
  'PUT /customers/:id': updateCustomer,
  'GET /customers/:id/transactions': getCustomerTransactions
};

// Transaction and points management
const transactionRoutes = {
  'POST /transactions': createTransaction,
  'GET /transactions/:id': getTransaction,
  'POST /points/award': awardPoints,
  'POST /points/redeem': redeemPoints,
  'GET /points/balance/:customerId': getPointBalance
};

// Campaign and notification management
const campaignRoutes = {
  'GET /campaigns': getCampaigns,
  'POST /campaigns': createCampaign,
  'PUT /campaigns/:id': updateCampaign,
  'POST /campaigns/:id/send': sendCampaign,
  'GET /campaigns/:id/analytics': getCampaignAnalytics
};

Early Validation Results

What Worked

  • Simple Points System: Customers understood “spend Rs. 100 = 1 point”
  • Birthday Campaigns: Automated birthday offers had 80% open rate
  • Staff Training: 30-minute training sessions improved adoption
  • SMS Backup: SMS notifications for users without app
  • Weekly Reports: Simple PDF reports business owners loved

What Didn't Work

  • Complex Redemption Rules: Tiered rewards confused customers
  • Notification Fatigue: Too many push notifications killed engagement
  • Analytics Overload: Business owners wanted simple metrics, not complex charts
  • Offline Fallback: What happens when internet is down?

Phase 2: User Feedback Integration

We implemented a feedback loop that changed our development process. Every feature request was evaluated against three criteria: user impact, development effort, and business value.

// Feature prioritization matrix we used
const evaluateFeature = (feature) => {
    const userImpact = feature.userVotes * feature.painPointSeverity; // 1-10 scale
    const devEffort = feature.estimatedHours * feature.complexity; // 1-5 scale
    const businessValue = feature.revenueImpact * feature.retentionImpact; // 1-10 scale
    
    const priority = (userImpact * businessValue) / devEffort;
    
    return {
        feature: feature.name,
        priority: priority,
        recommendation: priority > 50 ? 'HIGH' : priority > 20 ? 'MEDIUM' : 'LOW'
    };
};

// Example feature evaluation
const offlineMode = {
    name: 'Offline Mode',
    userVotes: 25,
    painPointSeverity: 8,
    estimatedHours: 120,
    complexity: 4,
    revenueImpact: 6,
    retentionImpact: 9
};

console.log(evaluateFeature(offlineMode));
// Output: { feature: 'Offline Mode', priority: 22.5, recommendation: 'MEDIUM' }

Phase 3: Advanced Analytics Engine

Business owners wanted insights, not just data. We built an analytics engine that provided actionable recommendations based on customer behavior patterns.

// Customer segmentation algorithm
class CustomerSegmentation {
    segmentCustomers(customers, transactions) {
        return customers.map(customer => {
            const customerTransactions = transactions.filter(t => t.customerId === customer.id);
            const metrics = this.calculateMetrics(customerTransactions);
            
            return {
                ...customer,
                segment: this.determineSegment(metrics),
                metrics: metrics,
                recommendations: this.generateRecommendations(metrics)
            };
        });
    }
    
    calculateMetrics(transactions) {
        const totalSpent = transactions.reduce((sum, t) => sum + t.amount, 0);
        const visitFrequency = transactions.length;
        const avgOrderValue = totalSpent / visitFrequency || 0;
        const daysSinceLastVisit = this.daysSince(transactions[0]?.date);
        const preferredTimeSlots = this.analyzeVisitTimes(transactions);
        
        return {
            totalSpent,
            visitFrequency,
            avgOrderValue,
            daysSinceLastVisit,
            preferredTimeSlots,
            lifetimeValue: this.calculateLTV(transactions)
        };
    }
    
    determineSegment(metrics) {
        if (metrics.totalSpent > 10000 && metrics.visitFrequency > 10) {
            return 'VIP';
        } else if (metrics.totalSpent > 5000 && metrics.visitFrequency > 5) {
            return 'LOYAL';
        } else if (metrics.daysSinceLastVisit > 30) {
            return 'AT_RISK';
        } else if (metrics.visitFrequency === 1) {
            return 'NEW';
        } else {
            return 'REGULAR';
        }
    }
    
    generateRecommendations(metrics) {
        const recommendations = [];
        
        if (metrics.segment === 'AT_RISK') {
            recommendations.push({
                type: 'WIN_BACK_CAMPAIGN',
                message: 'Send personalized offer to re-engage',
                expectedImpact: 'High'
            });
        }
        
        if (metrics.segment === 'VIP') {
            recommendations.push({
                type: 'EXCLUSIVE_OFFER',
                message: 'Offer exclusive menu items or early access',
                expectedImpact: 'Medium'
            });
        }
        
        return recommendations;
    }
}

Automated Marketing Campaigns

Manual campaign creation was time-consuming for business owners. We built an automated system that created and sent campaigns based on customer behavior triggers.

Campaign Automation Examples

  • Birthday Campaign: Automatic 20% discount 3 days before birthday
  • Win-back Campaign: Special offer for customers absent 30+ days
  • Upsell Campaign: Dessert recommendations for dinner customers
  • Weather-based: Hot soup promotions on rainy days
  • Time-based: Happy hour notifications to frequent evening visitors

Technical Challenges and Solutions

Real-time Synchronization

Multiple staff members updating customer points simultaneously caused data inconsistencies.

Solution: Implemented optimistic locking with conflict resolution and real-time sync using WebSockets.

Offline Functionality

Internet connectivity issues in some locations meant lost transactions and frustrated users.

Solution: Built offline-first architecture with local SQLite storage and background sync.

Scalability Bottlenecks

As we grew to 50+ businesses, our monolithic architecture started showing strain.

Solution: Migrated to microservices architecture with separate services for authentication, transactions, analytics, and notifications.

Performance Optimizations

Database Query Optimization

-- Before: Slow query that scanned entire transactions table
SELECT c.*, COUNT(t.id) as visit_count, SUM(t.amount) as total_spent
FROM customers c
LEFT JOIN transactions t ON c.id = t.customer_id
WHERE c.business_id = ?
GROUP BY c.id;

-- After: Optimized with proper indexing and materialized views
CREATE MATERIALIZED VIEW customer_metrics AS
SELECT 
    c.id,
    c.business_id,
    c.name,
    c.phone,
    COALESCE(agg.visit_count, 0) as visit_count,
    COALESCE(agg.total_spent, 0) as total_spent,
    COALESCE(agg.avg_order_value, 0) as avg_order_value,
    agg.last_visit_date
FROM customers c
LEFT JOIN (
    SELECT 
        customer_id,
        COUNT(*) as visit_count,
        SUM(amount) as total_spent,
        AVG(amount) as avg_order_value,
        MAX(created_at) as last_visit_date
    FROM transactions
    GROUP BY customer_id
) agg ON c.id = agg.customer_id;

-- Refresh materialized view hourly
SELECT cron.schedule('refresh-customer-metrics', '0 * * * *', 'REFRESH MATERIALIZED VIEW customer_metrics;');

Caching Strategy

// Redis caching for frequently accessed data
class CacheManager {
    constructor(redisClient) {
        this.redis = redisClient;
    }
    
    async getCustomerMetrics(customerId) {
        const cacheKey = `customer:${customerId}:metrics`;
        
        // Try cache first
        const cached = await this.redis.get(cacheKey);
        if (cached) {
            return JSON.parse(cached);
        }
        
        // Calculate metrics from database
        const metrics = await this.calculateCustomerMetrics(customerId);
        
        // Cache for 1 hour
        await this.redis.setex(cacheKey, 3600, JSON.stringify(metrics));
        
        return metrics;
    }
    
    async invalidateCustomerCache(customerId) {
        const pattern = `customer:${customerId}:*`;
        const keys = await this.redis.keys(pattern);
        
        if (keys.length > 0) {
            await this.redis.del(...keys);
        }
    }
}

Business Impact and Metrics

User Growth

Business Metrics

Technical Performance

Key Lessons Learned

Product Development

  1. Start with a pain point, not a solution: We succeeded because we solved a real problem
  2. Simple beats complex: The most successful features were the simplest ones
  3. User feedback is gold: Every major pivot came from user insights
  4. Technical debt is real: Shortcuts in early stages cost us months later

Business Strategy

  1. Market timing matters: COVID actually helped us as businesses needed digital solutions
  2. Customer success drives growth: Happy customers became our best salespeople
  3. Unit economics first: We focused on retention before acquisition
  4. Local context matters: What works globally doesn’t always work locally

Technical Architecture

  1. Plan for scale early: Our initial architecture couldn’t handle growth
  2. Offline-first is crucial: In emerging markets, connectivity is unreliable
  3. Real-time features need careful design: Synchronization is harder than it looks
  4. Monitoring and observability: You can’t fix what you can’t measure

What We’d Do Differently

Technical Decisions

Product Strategy

The End Result

Vhoye successfully served 60+ businesses and 12,000+ customers across Nepal. While we eventually pivoted to other opportunities, the platform proved that well-designed loyalty programs can significantly impact customer retention and business growth in the hospitality industry.

The journey taught us that building a successful product is 30% technical execution and 70% understanding your users. Every feature that succeeded came from a genuine user need, while failures usually came from assumptions we didn’t validate.

Most importantly, we learned that in emerging markets like Nepal, solutions need to be robust, simple, and designed for the local context. What works in Silicon Valley doesn’t always work in Kathmandu, and that’s okay - it just means there’s more room for innovation.

References