Skip to content

Technical Stack

The technology powering Yebo.


Overview

┌──────────────────────────────────────────────────────────────┐
│                       PRESENTATION                            │
│  Yebo Chat (Web)  •  Mobile App (Flutter)  •  SMS Fallback   │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                         GATEWAY                               │
│  Cloudflare (CDN, WAF, Workers)  •  Load Balancer            │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                        SERVICES                               │
│  Orchestrator  •  YeboID  •  YeboSafe  •  Products...        │
│  (Cloud Run, Node.js, TypeScript)                            │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                          DATA                                 │
│  PostgreSQL (Neon)  •  Redis  •  Cloud Storage               │
└──────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────┐
│                       EXTERNAL                                │
│  Gemini AI  •  Mobile Money  •  Resend  •  YeboLink          │
└──────────────────────────────────────────────────────────────┘

Core Technologies

Backend

TechnologyPurposeWhy
Node.js 22RuntimeFast, async, huge ecosystem
TypeScriptLanguageType safety, better DX
ExpressFrameworkSimple, flexible, proven
Prisma 7ORMType-safe DB access

Database

TechnologyPurposeWhy
Neon PostgreSQLPrimary DBServerless, scales to zero, branching
RedisCache, SessionsFast, pub/sub, queues
Cloud StorageFilesImages, documents

AI

TechnologyPurposeWhy
Google Gemini 2.5 FlashIntent recognition, generationFast, capable, good pricing
EmbeddingsSemantic searchMatching, recommendations

Frontend

TechnologyPurposeWhy
React 19Web UIComponent model, ecosystem
ViteBuild toolFast, modern
Tailwind CSS 3StylingUtility-first, rapid
React NativeMobile (future)Code sharing

Infrastructure

TechnologyPurposeWhy
Cloud RunContainersServerless, scales to zero
Cloud TasksQueuesAsync job processing
Cloud SchedulerCronScheduled tasks
CloudflareCDN, DNS, WAFPerformance, security
GCP Secret ManagerSecretsSecure credential storage

External Services

ServicePurpose
Yebo Chat AppPrimary user interface (app.hiyebo.com)
M-Pesa (Daraja)Kenya mobile money
MTN MoMoWest Africa mobile money
ResendEmail delivery
YeboLinkSMS (our own product)

Service Architecture

Monorepo Structure

yebo/
├── packages/
│   ├── shared/           # Shared utilities
│   │   ├── types/        # TypeScript types
│   │   ├── utils/        # Common functions
│   │   └── config/       # Shared config
│   │
│   ├── orchestrator/     # Agent brain
│   │   ├── src/
│   │   │   ├── intents/  # Intent handlers
│   │   │   ├── router/   # Product routing
│   │   │   ├── context/  # State management
│   │   │   └── adapters/ # Product adapters
│   │   └── package.json
│   │
│   ├── yeboid/           # Identity service
│   │   ├── api/
│   │   ├── sdk/
│   │   └── package.json
│   │
│   ├── yebosafe/         # Payment service
│   │   ├── api/
│   │   └── package.json
│   │
│   ├── yeboshops/        # Commerce service
│   │   ├── api/
│   │   └── package.json
│   │
│   └── ...               # Other products

├── apps/
│   ├── web/              # Main web app
│   ├── hub/              # YeboID hub
│   └── admin/            # Admin dashboard

├── docs/                 # Documentation
├── scripts/              # Build, deploy scripts
└── package.json          # Workspace root

Service Communication

javascript
// Services communicate via HTTP + events

// Synchronous (HTTP)
const user = await yeboid.getUser(userId);
const wallet = await yebosafe.getWallet(userId);

// Asynchronous (Pub/Sub or webhooks)
eventBus.publish('payment.completed', { orderId, amount });

eventBus.subscribe('payment.completed', async (event) => {
  await yeboshops.updateOrder(event.orderId, 'paid');
  await notify(event.userId, 'Payment received!');
});

Database Design

Per-Service Databases

Each service owns its data:

Neon Projects:
├── yeboid-db      (users, sessions, handles)
├── yebosafe-db    (wallets, transactions, escrows)
├── yeboshops-db   (listings, orders, reviews)
├── yebojobs-db    (jobs, applications, profiles)
├── yebolearn-db   (courses, enrollments, progress)
└── orchestrator-db (context, tasks, flows)

Cross-Service References

Services reference each other by ID:

sql
-- In YeboShops
CREATE TABLE users (
  id UUID PRIMARY KEY,
  yeboid_user_id UUID NOT NULL,  -- Reference to YeboID
  -- product-specific fields
);

-- In YeboSafe
CREATE TABLE wallets (
  id UUID PRIMARY KEY,
  yeboid_user_id UUID NOT NULL,  -- Reference to YeboID
  -- payment-specific fields
);

Data Consistency

javascript
// Eventual consistency via events
async function createOrder(buyerId, listingId) {
  // 1. Create escrow in YeboSafe
  const escrow = await yebosafe.createEscrow(...);
  
  // 2. Create order in YeboShops
  const order = await db.orders.create({
    ...orderData,
    escrowId: escrow.id
  });
  
  // 3. Publish event
  await eventBus.publish('order.created', { orderId: order.id });
  
  return order;
}

AI Architecture

Intent Recognition

javascript
const intentPrompt = `
You are a Yebo intent classifier. Classify the user message into one of these intents:

SELL - User wants to sell something
BUY - User wants to buy something
FIND_JOB - User wants to find a job
APPLY_JOB - User wants to apply to a specific job
SEND_MONEY - User wants to transfer money
CHECK_BALANCE - User wants to check their balance
SEND_INVOICE - User wants to create/send an invoice
LEARN - User wants to learn something
SOURCE - User wants to source products from China
HELP - User needs help
OTHER - None of the above

Also extract any relevant entities (item, price, job title, etc.)

User message: "${message}"

Respond in JSON: { "intent": "...", "entities": {...}, "confidence": 0.0-1.0 }
`;

const response = await gemini.generate(intentPrompt);
const { intent, entities, confidence } = JSON.parse(response);

Context-Aware Responses

javascript
async function generateResponse(intent, context, result) {
  const prompt = `
You are Yebo, an AI assistant for economic activities in Africa.
You help users sell, find jobs, learn, and manage money.

User: ${context.handle}
Current task: ${intent.type}
Result: ${JSON.stringify(result)}

Generate a helpful, concise response in a friendly tone.
Include any relevant actions the user can take next.
`;

  return await gemini.generate(prompt);
}

Listing Generation

javascript
async function generateListing(photos) {
  // 1. Analyze images
  const analysis = await gemini.analyzeImages(photos, {
    prompt: `
      Analyze these product photos and extract:
      - What is the item?
      - What condition is it in?
      - Any brand or model visible?
      - Suggested category
      - Estimated price range (in KES)
    `
  });
  
  // 2. Generate listing
  const listing = await gemini.generate(`
    Create a marketplace listing based on this analysis:
    ${JSON.stringify(analysis)}
    
    Generate:
    - Title (max 60 chars)
    - Description (max 500 chars)
    - Suggested price
    - Category
  `);
  
  return JSON.parse(listing);
}

Security

Authentication

javascript
// JWT token structure
{
  "userId": "uuid",
  "handle": "laslie",
  "verified": true,
  "iat": 1710770000,
  "exp": 1710770900  // 15 min
}

// Token validation (in any service)
function validateToken(token) {
  return jwt.verify(token, process.env.YEBOID_JWT_SECRET);
}

Rate Limiting

javascript
const rateLimits = {
  'auth/otp/send': { points: 3, duration: 3600 },     // 3/hour
  'auth/signin': { points: 5, duration: 900 },        // 5/15min
  'api/*': { points: 100, duration: 60 },             // 100/min
};

const rateLimiter = new RateLimiterRedis({
  storeClient: redis,
  ...rateLimits[endpoint]
});

Input Validation

javascript
// Zod schemas for all inputs
const signupSchema = z.object({
  phone: z.string().regex(/^\+\d{10,15}$/),
  pin: z.string().regex(/^\d{4,6}$/),
  handle: z.string().min(3).max(30).regex(/^[a-z0-9_]+$/)
});

// Validate before processing
const data = signupSchema.parse(req.body);

Deployment

CI/CD Pipeline

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '22'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
      
      - name: Build
        run: npm run build
      
      - name: Deploy to Cloud Run
        uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: yebo-${{ matrix.service }}
          region: europe-west1
          image: gcr.io/${{ secrets.GCP_PROJECT }}/${{ matrix.service }}

Environment Configuration

env
# Production
NODE_ENV=production
DATABASE_URL=postgresql://...
REDIS_URL=redis://...
YEBOID_JWT_SECRET=...
GEMINI_API_KEY=...
MPESA_CONSUMER_KEY=...
MPESA_CONSUMER_SECRET=...

Monitoring

ToolPurpose
Cloud LoggingLogs
Cloud MonitoringMetrics
SentryError tracking
Uptime RobotAvailability

Cost Estimates

Per 1,000 Users/Month

ServiceCost
Cloud Run$20
Neon PostgreSQL$20
Redis$10
Gemini API$15
WhatsApp API$30
Storage$5
Total$100

Cost per user: $0.10/month


Technology Decisions Log

DecisionChosenAlternativesWhy
LanguageTypeScriptPython, GoType safety, JS ecosystem
DatabaseNeonPlanetScale, SupabaseServerless, branching
AIGeminiOpenAI, ClaudeSpeed, pricing, capabilities
HostingCloud RunVercel, RailwayGCP ecosystem, scaling
CacheRedisMemcachedPub/sub, queues

Previous: Phase 4: Scale

One chat. Everything done.