YeboLink — Product Requirements Document
Status: LIVE at
api.yebolink.comwith real users and Stripe billing
Last Updated: March 2026
Version: 1.0.0
1. Vision & Problem Statement
Mission
Messaging Infrastructure for Africa — Provide African businesses with a unified, developer-friendly API to send SMS, WhatsApp, Email, and Voice communications at scale.
Problem
African businesses face significant challenges with customer communications:
- Fragmented Providers: Different providers for SMS, WhatsApp, Email — each with different APIs, pricing, and reliability
- Complex Integration: Most global APIs (Twilio, SendGrid) require complex setup and don't optimize for African carriers
- Unpredictable Costs: Per-message pricing with no visibility into costs until the bill arrives
- Poor Deliverability: Global providers often route through suboptimal paths for African networks
- No Local Support: 24/7 support in African time zones is rare
Solution
YeboLink provides:
- Single API for all communication channels (SMS, WhatsApp, Email, Voice)
- Credit-based prepaid model — know your costs upfront
- African-optimized routing — direct connections to MTN, Airtel, Safaricom, etc.
- Dashboard + API — non-developers can send messages too
- Local currency checkout — pay in ZAR, KES, NGN, GHS, etc.
2. Solution Overview
Architecture
┌─────────────────┐ ┌─────────────────────────────────────┐
│ Landing Site │ │ YeboLink API │
│ yebolink.com │ │ api.yebolink.com │
└────────┬────────┘ └──────────────┬──────────────────────┘
│ │
│ ┌────────┴────────┐
│ │ │
┌────────▼────────┐ ┌────────▼─────┐ ┌───────▼───────┐
│ Dashboard │ │ REST API │ │ Webhooks │
│ app.yebolink.com│ │ (API Keys) │ │ (Callbacks) │
└─────────────────┘ └──────────────┘ └───────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌────▼────┐
│ Twilio │ │ Resend │ │ (Future)│
│ SMS │ │ Email │ │WhatsApp │
└─────────┘ └──────────┘ └─────────┘Tech Stack
| Layer | Technology |
|---|---|
| Backend | Node.js + TypeScript + Express |
| Database | PostgreSQL (Neon serverless) |
| Cache/Queue | Google Cloud Tasks |
| SMS Provider | Twilio |
| Email Provider | Resend |
| Payments | Stripe (LIVE) |
| Hosting | Google Cloud Run |
| Dashboard | React + Vite + TailwindCSS |
| Landing | React + Vite + TailwindCSS |
3. Core Features
3.1 SMS Messaging ✅ LIVE
Capabilities:
- Single message sending
- Bulk messaging (up to 10,000+ per batch)
- Alphanumeric sender IDs (e.g., "KESHLESS" instead of phone number)
- Delivery status tracking via webhooks
- Template variable substitution (e.g.,
)
Provider: Twilio (via API Keys)
Credit Cost: ~1 credit per SMS segment (160 chars)
Sender ID Verification:
- AI-powered verification using Gemini
- Blocks impersonation (Google, banks, government)
- Sanitizes to 11 alphanumeric characters (carrier requirement)
3.2 Email Messaging ✅ LIVE
Capabilities:
- HTML + plain text emails
- Custom sender name (from_name)
- Subject line customization
- Delivery tracking
Provider: Resend
Credit Cost: ~0.5 credits per email
3.3 WhatsApp Messaging ⏳ NOT YET IMPLEMENTED
Planned:
- WhatsApp Business API integration
- Template messages (HSM)
- Rich media (images, documents)
- Interactive buttons
Throws: Error: WhatsApp not yet implemented
3.4 Voice Calls ⏳ NOT YET IMPLEMENTED
Planned:
- Text-to-speech calls
- IVR (Interactive Voice Response)
- Call recording
- DTMF input handling
4. User Journeys
4.1 Developer Journey
1. Sign up at app.yebolink.com
↓
2. Verify email (verification link sent)
↓
3. Complete onboarding (company name, industry, use cases)
↓
4. Generate API key in dashboard
↓
5. Read API docs at api.yebolink.com/docs
↓
6. Integrate API into application
↓
7. Buy credits via Stripe checkout
↓
8. Send messages via API
↓
9. Receive delivery reports via webhooks4.2 Business User Journey (Non-Developer)
1. Sign up at app.yebolink.com
↓
2. Buy credits (Stripe checkout with local currency)
↓
3. Import contacts (paste phone numbers)
↓
4. Use Quick Send modal for single messages
↓
5. Use Bulk Send for campaigns
↓
6. View analytics and delivery rates4.3 Admin Journey (CEO Dashboard)
Via CEO Dashboard (/api/dashboard endpoints):
1. View platform-wide metrics
- Total workspaces, messages, revenue
↓
2. Manage individual workspaces
- View details, messages, transactions
- Add credits manually
- Activate/deactivate accounts
↓
3. Monitor system health5. Data Models
5.1 workspaces
The core account/tenant entity. Each business is a "workspace."
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | VARCHAR(255) | Display name / company name |
email | VARCHAR(255) | Login email (unique) |
password_hash | VARCHAR(255) | bcrypt hashed password |
phone | VARCHAR(20) | Contact phone (optional) |
country | VARCHAR(2) | ISO country code (default: 'SZ') |
credits_balance | NUMERIC(10,2) | Current credit balance |
email_verified | BOOLEAN | Email verification status |
is_active | BOOLEAN | Account active status |
sms_sender_name | VARCHAR(11) | Default alphanumeric sender ID |
company_name | VARCHAR(255) | Business name (onboarding) |
industry | VARCHAR(100) | Industry category |
use_cases | TEXT[] | Array: ['sms_otp', 'marketing', 'bulk'] |
monthly_volume | VARCHAR(50) | Expected volume: '<1000', '1000-10000', etc. |
onboarding_completed | BOOLEAN | Onboarding flow completed |
created_at | TIMESTAMP | Creation timestamp |
updated_at | TIMESTAMP | Last update timestamp |
Indexes:
idx_workspaces_emailON emailidx_workspaces_created_atON created_at
5.2 api_keys
API keys for programmatic access.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
name | VARCHAR(100) | Key name (e.g., "Production") |
key_hash | VARCHAR(255) | SHA-256 hash of key (unique) |
key_prefix | VARCHAR(10) | Visible prefix: "ybk_abc..." |
scopes | TEXT[] | Permissions: ['send_messages', 'read_messages'] |
is_active | BOOLEAN | Key enabled status |
last_used_at | TIMESTAMP | Last API call timestamp |
created_at | TIMESTAMP | Creation timestamp |
Key Format: ybk_<32 random chars> (full key shown once at creation)
Indexes:
idx_api_keys_workspaceON workspace_ididx_api_keys_hashON key_hashidx_api_keys_activeON (workspace_id, is_active)
5.3 messages
All sent/received messages across channels.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
conversation_id | UUID | FK → conversations.id (optional) |
api_key_id | UUID | FK → api_keys.id (which key sent it) |
channel | VARCHAR(20) | 'sms', 'whatsapp', 'email', 'voice' |
direction | VARCHAR(10) | 'outbound' or 'inbound' |
sender | VARCHAR(255) | From address/number |
recipient | VARCHAR(255) | To address/number |
content_type | VARCHAR(20) | 'text', 'media', 'template' |
content | JSONB | {text, subject, html, media_urls, from_name} |
provider | VARCHAR(50) | 'twilio', 'resend', etc. |
provider_message_id | VARCHAR(255) | Provider's message ID |
status | VARCHAR(20) | 'queued', 'sent', 'delivered', 'failed', 'read' |
error_message | TEXT | Error details if failed |
credits_used | NUMERIC(10,2) | Credits charged |
metadata | JSONB | Custom metadata |
parent_message_id | UUID | For reply threading |
created_at | TIMESTAMP | When queued |
sent_at | TIMESTAMP | When sent to provider |
delivered_at | TIMESTAMP | When delivered |
read_at | TIMESTAMP | When read (WhatsApp) |
failed_at | TIMESTAMP | When failed |
Indexes:
idx_messages_workspaceON workspace_ididx_messages_channelON (workspace_id, channel)idx_messages_statusON (workspace_id, status)idx_messages_createdON (workspace_id, created_at DESC)idx_messages_recipientON recipientidx_messages_provider_idON provider_message_id
5.4 contacts
Customer contact list per workspace.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
phone | VARCHAR(20) | Phone number |
email | VARCHAR(255) | Email address |
name | VARCHAR(255) | Contact name |
custom_fields | JSONB | Custom key-value pairs |
channel_preferences | JSONB | {preferred: 'sms', opted_out: []} |
tags | TEXT[] | Contact tags |
created_at | TIMESTAMP | Creation timestamp |
updated_at | TIMESTAMP | Last update timestamp |
Constraint: Must have phone OR email (at least one identifier)
Indexes:
idx_contacts_workspaceON workspace_ididx_contacts_phoneON phoneidx_contacts_emailON emailidx_contacts_tagsUSING GIN(tags)idx_contacts_custom_fieldsUSING GIN(custom_fields)
5.5 transactions
Credits ledger — all credit purchases and usage.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
type | VARCHAR(20) | 'purchase', 'usage', 'refund', 'adjustment' |
amount | NUMERIC(10,2) | Credit amount (+/-) |
balance_after | NUMERIC(10,2) | Balance after transaction |
description | TEXT | Human-readable description |
message_id | UUID | FK → messages.id (for usage) |
stripe_payment_id | VARCHAR(255) | Stripe payment intent ID |
stripe_checkout_session_id | VARCHAR(255) | Stripe checkout session ID |
metadata | JSONB | Additional data |
created_at | TIMESTAMP | Transaction timestamp |
Indexes:
idx_transactions_workspaceON workspace_ididx_transactions_typeON (workspace_id, type)idx_transactions_createdON (workspace_id, created_at DESC)idx_transactions_stripe_sessionON stripe_checkout_session_id
5.6 webhooks
Webhook configurations per workspace.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
url | TEXT | Webhook endpoint URL |
secret | VARCHAR(255) | HMAC signing secret |
events | TEXT[] | Subscribed events |
is_active | BOOLEAN | Webhook enabled |
last_triggered_at | TIMESTAMP | Last successful delivery |
failure_count | INTEGER | Consecutive failures |
created_at | TIMESTAMP | Creation timestamp |
Webhook Events:
message.sentmessage.deliveredmessage.failedmessage.received(inbound)
Indexes:
idx_webhooks_workspaceON (workspace_id, is_active)
5.7 webhook_deliveries
Webhook delivery attempts log.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
webhook_id | UUID | FK → webhooks.id (CASCADE) |
event_type | VARCHAR(50) | Event that triggered delivery |
payload | JSONB | Payload sent |
response_status | INTEGER | HTTP status code received |
response_body | TEXT | Response body |
error | TEXT | Error if failed |
delivered_at | TIMESTAMP | Delivery attempt timestamp |
Indexes:
idx_webhook_deliveries_webhookON webhook_ididx_webhook_deliveries_deliveredON delivered_at DESC
5.8 conversations
Message threading/conversations (optional feature).
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
contact_id | UUID | FK → contacts.id |
customer_identifier | VARCHAR(255) | Phone/email identifier |
channels_used | TEXT[] | Channels in this conversation |
status | VARCHAR(20) | 'open', 'closed' |
assigned_to | UUID | Assigned agent |
last_message_at | TIMESTAMP | Last message timestamp |
last_message_preview | TEXT | Preview of last message |
created_at | TIMESTAMP | Creation timestamp |
5.9 channel_configs
Per-workspace channel configuration (future extensibility).
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
channel | VARCHAR(20) | 'sms', 'whatsapp', 'email' |
provider | VARCHAR(50) | Provider name |
config | JSONB | Provider-specific config |
credits_per_unit | NUMERIC(10,4) | Credits per message |
is_active | BOOLEAN | Channel enabled |
created_at | TIMESTAMP | Creation timestamp |
Constraint: UNIQUE(workspace_id, channel)
5.10 verification_tokens
Email verification and password reset tokens.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
token | VARCHAR(255) | Unique token |
type | VARCHAR(20) | 'email_verification', 'password_reset' |
expires_at | TIMESTAMP | Token expiration |
used_at | TIMESTAMP | When used (null if unused) |
created_at | TIMESTAMP | Creation timestamp |
Indexes:
idx_verification_tokens_tokenON tokenidx_verification_tokens_workspaceON workspace_id
5.11 refresh_tokens
JWT refresh tokens for session management.
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
workspace_id | UUID | FK → workspaces.id (CASCADE) |
token | TEXT | Unique refresh token |
expires_at | TIMESTAMP | Token expiration |
created_at | TIMESTAMP | Creation timestamp |
revoked_at | TIMESTAMP | Revocation timestamp |
Indexes:
idx_refresh_tokens_tokenON tokenidx_refresh_tokens_workspace_idON workspace_id
5.12 blog_posts
Blog posts for autoblogger integration (SEO content).
| Field | Type | Description |
|---|---|---|
id | UUID | Primary key |
title | VARCHAR(500) | Post title |
slug | VARCHAR(500) | URL slug (unique) |
content | TEXT | Post content (HTML/Markdown) |
excerpt | TEXT | Short excerpt |
category | VARCHAR(100) | Category |
tags | TEXT[] | Tags array |
status | VARCHAR(20) | 'published', 'draft', 'archived' |
author | VARCHAR(100) | Author name |
featured_image | TEXT | Image URL |
published_at | TIMESTAMP | Publication date |
created_at | TIMESTAMP | Creation timestamp |
updated_at | TIMESTAMP | Last update |
6. API Reference
Base URL
- Production:
https://api.yebolink.com - Documentation:
https://api.yebolink.com/docs(Swagger UI)
Authentication
Two methods supported:
JWT Bearer Token (for dashboard)
Authorization: Bearer <jwt_token>API Key (for programmatic access)
X-API-Key: ybk_your_api_key_here
6.1 Authentication Endpoints
POST /api/v1/auth/signup
Create a new workspace/account.
Request:
{
"email": "john@company.com",
"password": "SecurePass123!",
"company_name": "John's Business",
"phone": "+26878422613",
"country": "SZ"
}Response (201):
{
"success": true,
"data": {
"workspace": {
"id": "uuid",
"name": "John's Business",
"email": "john@company.com",
"credits_balance": 0,
"email_verified": false
},
"token": "jwt_token",
"refreshToken": "refresh_token"
}
}POST /api/v1/auth/login
Login with email/password.
Request:
{
"email": "john@company.com",
"password": "SecurePass123!"
}Response (200):
{
"success": true,
"data": {
"workspace": { ... },
"token": "jwt_token",
"refreshToken": "refresh_token"
}
}POST /api/v1/auth/refresh
Refresh JWT token.
Request:
{
"refreshToken": "current_refresh_token"
}Response:
{
"success": true,
"data": {
"token": "new_jwt_token",
"refreshToken": "new_refresh_token"
}
}POST /api/v1/auth/verify-email
Verify email address.
Request:
{
"token": "verification_token_from_email"
}POST /api/v1/auth/forgot-password
Request password reset.
Request:
{
"email": "john@company.com"
}POST /api/v1/auth/reset-password
Reset password with token.
Request:
{
"token": "reset_token",
"password": "NewSecurePass123!"
}6.2 Messages Endpoints
Authentication: API Key (X-API-Key header) required
POST /api/v1/messages/send
Send a single message.
Request:
{
"to": "+26878422613",
"channel": "sms",
"content": {
"text": "Hello {{name}}, your OTP is {{code}}"
},
"sender": "MYCOMPANY"
}Email variant:
{
"to": "user@email.com",
"channel": "email",
"content": {
"subject": "Welcome!",
"text": "Plain text fallback",
"html": "<h1>Welcome!</h1><p>HTML content</p>",
"from_name": "My Company"
}
}Response (201):
{
"success": true,
"data": {
"message_id": "uuid",
"status": "queued",
"credits_used": 1,
"created_at": "2026-03-19T12:00:00Z"
}
}POST /api/v1/messages/bulk
Send bulk messages.
Request:
{
"recipients": [
{ "to": "+26878422613", "name": "Alice" },
{ "to": "+26876543210", "name": "Bob" }
],
"channel": "sms",
"content": {
"text": "Hi {{name}}, your order is ready!"
}
}Response (201):
{
"success": true,
"data": {
"queued": 2,
"failed": 0,
"message_ids": ["uuid1", "uuid2"]
}
}GET /api/v1/messages
List messages.
Query Parameters:
limit(default: 50, max: 100)offset(default: 0)channel(filter by channel)status(filter by status)
Response:
{
"success": true,
"data": {
"messages": [ ... ],
"total": 1234,
"limit": 50,
"offset": 0
}
}GET /api/v1/messages/:id
Get single message details.
Response:
{
"success": true,
"data": {
"id": "uuid",
"channel": "sms",
"to": "+26878422613",
"content": { "text": "Hello!" },
"status": "delivered",
"credits_used": 1,
"created_at": "...",
"sent_at": "...",
"delivered_at": "..."
}
}6.3 Account Endpoints
Authentication: JWT Bearer token
GET /api/v1/account/balance
Get current credit balance.
Response:
{
"success": true,
"data": {
"credits_balance": 1500.50,
"workspace_id": "uuid"
}
}GET /api/v1/account/profile
Get workspace profile.
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "John's Business",
"email": "john@company.com",
"phone": "+26878422613",
"country": "SZ",
"sms_sender_name": "MYCOMPANY",
"email_verified": true,
"onboarding_completed": true
}
}PUT /api/v1/account/profile
Update workspace profile.
Request:
{
"name": "New Company Name",
"phone": "+26879876543",
"sms_sender_name": "NEWNAME"
}Sender Name Validation:
- Max 11 alphanumeric characters
- Cannot impersonate banks, government, major brands
- AI-verified using Gemini
GET /api/v1/account/stats
Get messaging statistics.
Response:
{
"success": true,
"data": {
"messages": {
"totalMessages": 5432,
"byChannel": [
{ "channel": "sms", "count": 4500 },
{ "channel": "email", "count": 932 }
],
"byStatus": [
{ "status": "delivered", "count": 5100 },
{ "status": "failed", "count": 332 }
]
}
}
}PUT /api/v1/account/onboarding
Complete onboarding flow.
Request:
{
"company_name": "Acme Corp",
"industry": "fintech",
"use_cases": ["sms_otp", "marketing"],
"monthly_volume": "1000-10000"
}6.4 API Keys Endpoints
Authentication: JWT Bearer token
GET /api/v1/api-keys
List all API keys.
Response:
{
"success": true,
"data": {
"api_keys": [
{
"id": "uuid",
"name": "Production",
"key_prefix": "ybk_abc123...",
"scopes": ["send_messages", "read_messages"],
"is_active": true,
"last_used_at": "2026-03-19T12:00:00Z"
}
]
}
}POST /api/v1/api-keys
Create new API key.
Request:
{
"name": "Production API Key",
"scopes": ["send_messages", "read_messages"]
}Response (201):
{
"success": true,
"data": {
"id": "uuid",
"name": "Production API Key",
"key": "ybk_full_key_shown_only_once",
"key_prefix": "ybk_abc...",
"scopes": ["send_messages", "read_messages"]
}
}⚠️ The full key is only returned once at creation!
DELETE /api/v1/api-keys/:id
Revoke/delete an API key.
6.5 Billing Endpoints
Authentication: JWT Bearer token
GET /api/v1/billing/packages
Get available credit packages.
Response:
{
"success": true,
"mode": "live",
"packages": [
{
"credits": 100,
"price": 300,
"name": "Starter Pack",
"displayFormatted": "R55",
"displayCurrency": "ZAR",
"usdFormatted": "$3"
},
{
"credits": 500,
"price": 1000,
"name": "Growth Pack",
...
}
]
}Pricing Tiers:
| Package | Credits | USD Price | ZAR Price |
|---|---|---|---|
| Starter | 100 | $3 | ~R55 |
| Basic | 250 | $6 | ~R111 |
| Growth | 500 | $10 | ~R185 |
| Business | 1,000 | $18 | ~R333 |
| Pro | 2,500 | $40 | ~R740 |
| Enterprise | 5,000 | $75 | ~R1,388 |
POST /api/v1/billing/checkout
Create Stripe checkout session.
Request:
{
"credits": 500
}Response:
{
"success": true,
"url": "https://checkout.stripe.com/pay/cs_live_..."
}POST /api/v1/billing/webhook
Stripe webhook endpoint.
Handles:
checkout.session.completed— Add credits after successful payment
GET /api/v1/billing/transactions
Get transaction history.
Response:
{
"success": true,
"data": {
"transactions": [
{
"id": "uuid",
"type": "purchase",
"amount": 500,
"balance_after": 1500,
"description": "Credit purchase - 500 credits",
"created_at": "..."
},
{
"id": "uuid",
"type": "usage",
"amount": -1,
"balance_after": 1499,
"description": "Message to +268...",
"message_id": "uuid"
}
]
}
}6.6 Contacts Endpoints
Authentication: JWT Bearer token
GET /api/v1/contacts
List contacts.
Query Parameters:
limit,offset— Paginationsearch— Search by name/phone/emailtags— Filter by tags
POST /api/v1/contacts
Create contact.
Request:
{
"phone": "+26878422613",
"email": "user@email.com",
"name": "John Doe",
"tags": ["vip", "customer"],
"custom_fields": {
"company": "Acme Corp",
"plan": "premium"
}
}GET /api/v1/contacts/:id
Get single contact.
PUT /api/v1/contacts/:id
Update contact.
DELETE /api/v1/contacts/:id
Delete contact.
6.7 Webhooks Endpoints
Authentication: JWT Bearer token
GET /api/v1/webhooks
List configured webhooks.
POST /api/v1/webhooks
Create webhook.
Request:
{
"url": "https://your-app.com/webhooks/yebolink",
"events": ["message.sent", "message.delivered", "message.failed"]
}Response:
{
"success": true,
"data": {
"id": "uuid",
"url": "https://your-app.com/webhooks/yebolink",
"secret": "whsec_generated_secret",
"events": ["message.sent", "message.delivered", "message.failed"],
"is_active": true
}
}PUT /api/v1/webhooks/:id
Update webhook.
DELETE /api/v1/webhooks/:id
Delete webhook.
POST /api/v1/webhooks/:id/test
Trigger test webhook.
6.8 Provider Webhooks
POST /api/v1/webhooks/twilio/status
Twilio delivery status callback.
Called by Twilio when message status changes. Updates message status in database.
POST /api/v1/webhooks/twilio/inbound
Twilio inbound SMS webhook.
Receives incoming SMS messages.
6.9 Admin Dashboard Endpoints
Authentication: X-API-Key header with admin dashboard key
Base path: /api/dashboard
GET /api/dashboard/metrics
Platform-wide metrics.
Response:
{
"messagesSent": 1234,
"messagesTrend": "up",
"messagesChange": 15.5,
"newContacts": 56,
"deliveryRate": 98.5,
"activeWorkspaces": 42
}GET /api/dashboard/workspaces
List all workspaces.
GET /api/dashboard/workspaces/:id
Get workspace detail with messages, transactions, API keys.
POST /api/dashboard/workspaces/:id/credits
Add credits to workspace (admin top-up).
Request:
{
"amount": 100,
"description": "Promotional credits"
}POST /api/dashboard/workspaces/:id/deactivate
Deactivate workspace.
POST /api/dashboard/workspaces/:id/activate
Reactivate workspace.
GET /api/dashboard/stats
Aggregate platform statistics.
7. Service Architecture
7.1 Message Processing Flow
1. API Request (/messages/send)
↓
2. Validate input + check credits
↓
3. Create message record (status: queued)
↓
4. Queue to Cloud Tasks
↓
5. Worker picks up task
↓
6. Deduct credits atomically
↓
7. Send via provider (Twilio/Resend)
↓
8. Update message status
↓
9. Trigger webhooks7.2 Provider Abstraction
MessageProcessor (message-processor.service.ts):
- Routes messages to appropriate provider based on channel
- Handles credit deduction
- Updates message status
- Triggers webhooks
Providers:
| Channel | Provider | Service |
|---|---|---|
| SMS | Twilio | TwilioService |
| Resend | EmailService | |
| — | Not implemented | |
| Voice | — | Not implemented |
7.3 Webhook Delivery
WebhookService (webhook.service.ts):
- Fetches active webhooks for workspace
- Signs payload with HMAC-SHA256
- Queues delivery via Cloud Tasks
- Logs delivery attempts in
webhook_deliveries - Implements retry logic (max 3 attempts)
Payload Signature:
X-YeboLink-Signature: sha256=<hmac_of_payload>
X-YeboLink-Timestamp: <unix_timestamp>8. Authentication & API Keys
8.1 JWT Authentication
- Access Token: 1 hour expiry
- Refresh Token: 7 days expiry, stored in database
- Signing: HS256 with
JWT_SECRET
Token Payload:
{
"workspaceId": "uuid",
"email": "user@company.com",
"iat": 1234567890,
"exp": 1234571490
}8.2 API Key Authentication
- Format:
ybk_<32 random alphanumeric chars> - Storage: SHA-256 hash in database
- Validation: Hash incoming key, compare with stored hash
- Scopes:
send_messages,read_messages
Key Generation:
const key = `ybk_${crypto.randomBytes(32).toString('hex')}`;
const hash = crypto.createHash('sha256').update(key).digest('hex');9. Billing & Credits System
9.1 How Credits Work
- Purchase: User buys credits via Stripe → credits added to
credits_balance - Usage: Each message deducts credits based on channel/type
- Ledger: Every credit change logged in
transactionstable - Atomic: Credit deduction uses PostgreSQL stored procedure for atomicity
9.2 Credit Costs
| Channel | Cost per Message |
|---|---|
| SMS (local) | ~1 credit |
| SMS (international) | ~2-5 credits |
| ~0.5 credits | |
| ~1-2 credits (planned) |
9.3 Payment Flow
1. User selects credit package
↓
2. POST /billing/checkout with credits amount
↓
3. Server creates Stripe Checkout Session
↓
4. User redirected to Stripe
↓
5. User completes payment
↓
6. Stripe sends webhook (checkout.session.completed)
↓
7. Server adds credits to workspace
↓
8. User redirected to success page9.4 Local Currency Support
Supported currencies:
- South Africa (ZAR) — Stripe supported
- Nigeria (NGN) — Stripe supported
- Kenya (KES) — Stripe supported
- Ghana (GHS) — Stripe supported
- Egypt (EGP) — Stripe supported
- Eswatini (SZL) — Falls back to USD
Currency conversion handled in currencies.ts:
- Display price in local currency
- Charge in Stripe-supported currency
- Show USD equivalent if local currency not supported by Stripe
10. Webhooks
10.1 Webhook Events
| Event | Trigger |
|---|---|
message.sent | Message sent to provider |
message.delivered | Delivery confirmed |
message.failed | Delivery failed |
message.received | Inbound message received |
10.2 Webhook Payload
{
"event": "message.delivered",
"timestamp": "2026-03-19T12:00:00Z",
"data": {
"message_id": "uuid",
"channel": "sms",
"recipient": "+26878422613",
"status": "delivered",
"provider_message_id": "SMxxxx"
}
}10.3 Webhook Signature Verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const expectedSig = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return signature === `sha256=${expectedSig}`;
}11. Rate Limiting & Throttling
11.1 Rate Limits
| Endpoint | Limit |
|---|---|
| General API | 100 requests/minute |
| Auth endpoints | 10 requests/minute |
| Message sending | 100 messages/minute |
| Bulk sending | 10 requests/minute |
11.2 Implementation
Using Redis-based rate limiting (via rateLimiter.ts):
const generalRateLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100,
keyGenerator: (req) => req.ip || req.headers['x-forwarded-for'],
message: { success: false, error: 'Too many requests' }
});11.3 Rate Limit Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 123456789012. Analytics & Dashboard
12.1 Dashboard Features
Main Dashboard:
- Credit balance display
- Quick send modal (SMS, WhatsApp, Email)
- Bulk send modal
- Import contacts
- Recent messages list
- Stats overview (total sent, delivery rate, contacts)
Messages Page:
- Full message history
- Filter by channel, status
- Search by recipient
Contacts Page:
- Contact list with search
- Import (paste numbers)
- Edit/delete contacts
- Tags management
Analytics Page:
- Messages by channel
- Delivery rates over time
- Usage trends
Credits Page:
- Current balance
- Credit packages with local pricing
- Custom amount purchase
- Transaction history
Settings Page:
- Profile management
- SMS sender name configuration
- API keys management
- Webhooks configuration
13. Technical Stack
Backend
- Runtime: Node.js 18+
- Framework: Express.js
- Language: TypeScript
- Database: PostgreSQL (Neon serverless)
- Queue: Google Cloud Tasks
- Auth: JWT + API Keys
Frontend (Dashboard)
- Framework: React 18
- Build: Vite
- Styling: TailwindCSS
- State: React Query (TanStack Query)
- Icons: Lucide React
Infrastructure
- Hosting: Google Cloud Run
- Database: Neon (serverless Postgres)
- Payments: Stripe
- SMS: Twilio
- Email: Resend
- AI: Google Gemini (sender verification)
Deployment
- Backend:
./deploy.shscript → Cloud Run - Dashboard: Cloudflare Pages
- Landing: Cloudflare Pages
14. Gaps & Needed Features
14.1 Critical Gaps (Must Have)
| Gap | Priority | Notes |
|---|---|---|
| WhatsApp integration | HIGH | Code throws "not yet implemented" |
| Voice calls | MEDIUM | Not started |
| Inbound SMS handling | HIGH | Webhook route exists but not tested |
| Email delivery tracking | MEDIUM | No open/click tracking |
| 2FA / MFA | MEDIUM | No two-factor auth on accounts |
14.2 Important Improvements
| Feature | Priority | Notes |
|---|---|---|
| Message templates | HIGH | Pre-approved templates for compliance |
| Scheduled messages | HIGH | Send at specific time |
| Contact groups/segments | MEDIUM | Group contacts for bulk sends |
| Campaign analytics | MEDIUM | Track campaign performance |
| A/B testing | LOW | Test different message variants |
| Dedicated short codes | HIGH | For high-volume SMS |
| Number lookup/validation | MEDIUM | Verify phone numbers before sending |
| DLR (Delivery Reports) UI | MEDIUM | Better delivery status visualization |
14.3 Admin/Ops Improvements
| Feature | Priority | Notes |
|---|---|---|
| Admin panel | HIGH | Separate admin UI (not just API) |
| Workspace impersonation | MEDIUM | Admin can act as workspace |
| Audit logs | MEDIUM | Track all admin actions |
| Usage alerts | HIGH | Alert when credits low |
| Provider failover | HIGH | Automatic fallback providers |
| Multi-region routing | LOW | Route via nearest provider |
14.4 Compliance & Security
| Feature | Priority | Notes |
|---|---|---|
| GDPR compliance tools | MEDIUM | Data export, deletion |
| Opt-out management | HIGH | Track unsubscribes |
| Content moderation | MEDIUM | Block spam/abuse |
| Sender ID registration | HIGH | Carrier-level registration |
| SOC 2 certification | LOW | Enterprise requirement |
15. Roadmap
Q2 2026
- [ ] WhatsApp Business API integration
- [ ] Message templates system
- [ ] Scheduled sending
- [ ] Admin panel UI
Q3 2026
- [ ] Voice calling (TTS, IVR)
- [ ] Contact segments
- [ ] Campaign builder
- [ ] Provider failover
Q4 2026
- [ ] Multi-tenant teams
- [ ] BYOC (Bring Your Own Carrier)
- [ ] Advanced analytics
- [ ] API v2
Appendix A: Environment Variables
# Core
NODE_ENV=production
PORT=3000
APP_URL=https://api.yebolink.com
FRONTEND_URL=https://app.yebolink.com
# Database
DATABASE_URL=postgresql://...
# Auth
JWT_SECRET=<secret>
API_KEY_SECRET=<secret>
# Twilio
TWILIO_ACCOUNT_SID=<sid>
TWILIO_AUTH_TOKEN=<token>
TWILIO_API_KEY_SID=<key>
TWILIO_API_KEY_SECRET=<secret>
TWILIO_PHONE_NUMBER=+1234567890
# Email
RESEND_API_KEY=<key>
FROM_EMAIL=noreply@yebolink.com
FROM_NAME=YeboLink
# Stripe
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# AI (Sender Verification)
GEMINI_API_KEY=<key>
# Cloud Tasks
GCLOUD_PROJECT=yebolink
CLOUD_TASKS_LOCATION=us-central1Appendix B: API Error Codes
| HTTP Code | Error | Description |
|---|---|---|
| 400 | validation_error | Invalid request body |
| 401 | unauthorized | Missing/invalid auth |
| 402 | insufficient_credits | Not enough credits |
| 403 | forbidden | No permission |
| 404 | not_found | Resource not found |
| 409 | conflict | Duplicate resource |
| 429 | rate_limited | Too many requests |
| 500 | internal_error | Server error |
Appendix C: Webhook Event Types
type WebhookEvent =
| 'message.sent'
| 'message.delivered'
| 'message.failed'
| 'message.received'
| 'credits.low' // Planned
| 'credits.depleted' // PlannedDocument generated from codebase analysis. Last verified: March 2026.