Private Chat & Calls Phase 2
NexGate Messaging — Product Requirements & Feature Flows
NexGate / QBIT SPARK | Version 1.0 What the messaging platform does — rules, flows, permissions, scenarios
Table of Contents
- Document Purpose
- User Identity & Discovery
- Contact Sync
- Inbox Model
- Messaging Permissions
- Call Permissions
- 1:1 Messaging Flows
- Group Chat Flows
- Commerce DM Flows
- Offer Session Flows
- Shareable Content
- Message Interactions
- Voice & Video Call Flows
- Group Call Flows
- Offline & Notification Flows
- Privacy & Safety
- Shop Inbox & Staff Access
- Notification Settings
- Edge Cases & Scenarios
- Follow System — Users & Shops
- Group Join Model
- Shop Chat Initiation
- Broadcast Channels — Why Not Needed
- Commerce Attach — 1:1 vs Group
1. Document Purpose
This document defines WHAT NexGate messaging does — the rules, user flows, permissions, and scenarios for every messaging feature.
It is the product reference document. It does not describe HOW features are built technically. For technical architecture see:
- Doc 3: Phase 2 Production Architecture
- Doc 5: Private Chat & Calls Deep Dive
Audience: Product, mobile developers, QA, design.
2. User Identity & Discovery
Identity on NexGate
Every NexGate user has:
@username unique, chosen at registration
shown publicly on VP Feed
used for messaging: @kibuti
Display name full name shown in conversations
can differ from username
Phone number used for registration + auth
NOT shown publicly by default
discoverable only with user permission
NexGate ID internal UUID
never shown to users
How Users Find Each Other
Discovery mechanism Requires contact sync?
──────────────────────────────────────────────────────
VP Feed follow ❌ No
VP Shop (buyer/seller) ❌ No
Username search (@kibuti) ❌ No
QR code scan ❌ No
Shareable profile link ❌ No
Group chat membership ❌ No
Phone number search ❌ No (type manually)
Contact sync (phonebook) ✅ Yes (optional)
NexGate has multiple discovery paths. Contact sync is one option — not the only one.
Discoverability Settings
Two independent settings:
"Sync my contacts"
Find NexGate users in my phonebook
Default: OFF (user must opt in)
"Let others find me via phone number"
If someone has my number → can find me
Default: ON (can turn off)
These are INDEPENDENT:
I can sync contacts without being findable
I can be findable without syncing contacts
3. Contact Sync
Rules
✅ Optional — never mandatory
✅ Can be enabled/disabled anytime
✅ Can delete synced data anytime from settings
✅ Only finds users who have discoverability ON
❌ Not required to use messaging
❌ Not required for commerce DMs
❌ Contacts data never shared with third parties
❌ Non-NexGate contacts never stored on server
Privacy-Preserving Implementation
How it works when user accepts:
Step 1: Phone numbers hashed ON DEVICE
sha256(+255712345678) → hash
Actual numbers never leave the device
Step 2: Hashes uploaded to NexGate server
Not real phone numbers
Server cannot reverse hashes
Step 3: Server matches hashes
Against registered users who have
"Let others find me via phone number" = ON
Returns: which hashes are NexGate users
Step 4: App shows matched users
"3 of your contacts are on NexGate"
[Connect] buttons shown
Non-NexGate contacts:
Their hashes deleted from server immediately
Never stored
Never tracked
First Launch Prompt
Shown once at first launch after onboarding:
┌──────────────────────────────────────────────┐
│ 📱 Pata Marafiki Wako NexGate │
│ │
│ Ruhusu NexGate kutumia contacts zako │
│ kupata marafiki wanaotumia NexGate tayari │
│ │
│ ✓ Contacts zako hazitashirikiwa na mtu │
│ ✓ Unaweza kufuta ruhusa wakati wowote │
│ ✓ Nambari za simu zinabadilishwa kuwa │
│ msimbo kabla ya kupakiwa │
│ │
│ [Ruhusu Contacts] [Sasa Hivi Sio] │
└──────────────────────────────────────────────┘
If declined:
App works fully ✅
Remind once after 7 days
Never prompt again after second decline
Always accessible in Settings
Sync Scenarios
Scenario 1 — User accepts sync:
Hashes uploaded
Matched users shown
User can message/follow matched users
Sync runs again when contacts change
Scenario 2 — User declines sync:
App works fully
Can still find people via username/QR/VP Feed
No contacts uploaded
Scenario 3 — User accepts then disables:
Settings → Privacy → Contact Sync → OFF
All hash data deleted from server
Matched contacts remain as connections
(connections not removed — only future sync stops)
Scenario 4 — User wants to delete contact data:
Settings → Privacy → Delete Contacts Data
All hashes deleted from server immediately
Confirmation shown: "Contacts data imefutwa"
Scenario 5 — Contact not on NexGate:
Their hash deleted from server
User sees option: "Invite Juma to NexGate"
Tap → share NexGate invite link via SMS/WhatsApp
4. Inbox Model
Two Separate Inboxes
Every NexGate user has TWO completely separate inboxes:
Personal Inbox:
1:1 DMs with other users (personal)
Group chats (personal)
Friend conversations
Private — only the account owner sees this
NEVER accessible to shop staff
Shop Inbox (one per shop owned):
All customer conversations for that shop
Commerce DMs with buyers
Shared with authorized shop staff
Completely separate from personal inbox
Customer sees shop identity — not personal name
Inbox Tabs
User with 2 shops sees:
┌─────────────────────────────────────────────┐
│ 💬 Inbox │
│ │
│ [Personal ●3] [TechStore ●12] [ClothingHub]│
│ │
│ Personal tab: │
│ Regular DMs + group chats │
│ Private — owner only │
│ │
│ TechStore tab: │
│ Customer commerce conversations │
│ Shared with assigned staff │
│ │
│ ClothingHub tab: │
│ Separate shop conversations │
│ Different staff assigned │
└─────────────────────────────────────────────┘
Staff member (Amina assigned to TechStore):
┌─────────────────────────────────────────────┐
│ [Personal] [TechStore ●12] │
│ │
│ ClothingHub: NOT visible (no access) │
│ Owner personal: NEVER visible │
└─────────────────────────────────────────────┘
Conversation Types
Type Owner Participants
────────────────────────────────────────────────────
DM USER 2 users
GROUP USER 3-500 users
COMMERCE SHOP 1 shop + 1 buyer
BROADCAST USER or SHOP 1 sender + N followers
5. Messaging Permissions
Who Can Message Who
Relationship Can message? How
────────────────────────────────────────────────────────────
Saved contacts (phone) ✅ Always Direct
Mutual followers (VP Feed) ✅ Always Direct
Commerce relationship ✅ Always Direct (shop inbox)
Group chat members ✅ Always Via group only
Strangers (no relationship) ⚠️ Request Message request first
Blocked users ❌ Never Blocked
Message Request System
Stranger (no relationship) wants to message you:
They can send ONE message request:
┌─────────────────────────────────────────┐
│ 📩 Maombi ya Ujumbe │
│ │
│ Juma Mwangi anataka kukutumia ujumbe │
│ @juma_mwangi · 245 wafuasi │
│ │
│ "Habari, nilikuona kwenye VP Shop..." │
│ │
│ [Kubali] [Kataa] [Zuia] │
└─────────────────────────────────────────┘
If Kubali (Accept):
Full conversation opens
Juma can now message freely
Can call after conversation established
If Kataa (Decline):
Juma cannot send another request
One attempt only per account
Juma sees: "Ujumbe haukukubaliwa"
If Zuia (Block):
Juma blocked immediately
Cannot contact again in any way
Juma sees: nothing (as if you don't exist)
If Ignored (no response):
Request stays in request folder
Juma cannot send more messages
No call attempt allowed
Request expires after 30 days
Message Request Limits
Per account per day:
Max 10 message requests sent
Prevents spam
Per account to same person:
1 request only (ever)
If declined → cannot request again
Request folder:
Separate from main inbox
User checks it voluntarily
No notification for low-priority requesters
(unless they have mutual connections)
6. Call Permissions
Permission Tiers
Tier Voice call Video call
────────────────────────────────────────────────────────
Saved contacts ✅ ✅
Mutual followers ✅ ✅
Commerce relationship ✅ (shop) ✅ (shop)
Active conversation partner ✅ ✅
(message request accepted)
Group chat member ✅ (group) ✅ (group)
Strangers ❌ ❌
Blocked users ❌ ❌
Commerce Call Rules
Buyer can call:
The SHOP JID — not the owner personally
techstore@shops.nexgate.com
Any available staff answers as TechStore
Seller (shop) can call:
Buyers they have active commerce DM with
Call goes from shop JID to buyer
Buyer sees "TechStore" calling — not a personal name
Protecting personal contact:
Owner's personal phone/JID never revealed
Commerce calls always through shop identity
Staff cannot reveal personal number to customers
Anti-Harassment Call Rules
Call rate limiting:
Max 3 unanswered calls to same person in 24 hours
4th attempt → blocked for 24 hours automatically
Resets daily
Silence unknown callers:
Setting: "Pumzisha simu kutoka kwa wasio contacts"
Calls from non-contacts ring silently
Notification appears after — not during
First call warning:
First EVER call from this person:
┌───────────────────────────────────────┐
│ 📞 Simu ya kwanza kutoka │
│ Juma Mwangi (@juma_mwangi) │
│ │
│ [Jibu] [Kataa] [Zuia] │
└───────────────────────────────────────┘
Quick block during call:
Report button visible during call
One tap → ends call + reports + blocks
Privacy Settings for Calls
Who can call me:
○ Mutual followers + contacts (default)
○ My contacts only
○ Nobody
User can change anytime in Settings → Privacy → Calls
Call Permission Check Flow
Kibuti tries to call Juma:
│
Spring Boot permission check:
Is Juma blocked by Kibuti? ❌ → reject immediately
Is Kibuti blocked by Juma? ❌ → reject immediately
Are they saved contacts? ✅ → allow
Are they mutual followers? ✅ → allow
Active commerce relationship? ✅ → allow
Active accepted conversation? ✅ → allow
Does Juma's privacy setting allow? ✅ → allow
None of above → ❌ reject
│
If rejected:
App shows: "Huwezi kupiga simu mtu huyu"
No Jingle stanza sent
No TURN credentials issued
If allowed:
TURN credentials generated
Jingle session-initiate sent via Ejabberd
Juma's device rings
7. 1:1 Messaging Flows
Send Text Message
Happy path:
User types message
Taps send
Message shows: pending ⏳
Server receives → single tick ✓
Recipient receives → double tick ✓✓
Recipient reads → blue tick ✓✓
Offline path:
User taps send
Single tick ✓ (server received)
Recipient offline → queued
FCM push fires to recipient device
Recipient comes online → message delivered
Double tick ✓✓ appears
Send Voice Note
User holds mic button:
"Kibuti anarekodia..." shown to recipient (if online)
Recording in progress
Timer shown: 0:01, 0:02...
User releases:
Voice note sent
Waveform shown in thread
Duration shown: 0:15
User swipes up while holding:
Lock mode — records without holding
Release sends
User swipes left while holding:
Cancel recording — nothing sent
Limits:
Basic users: max 2 minutes
Pro users: max 15 minutes
File size: max 16MB
Send Media
Images:
Max 10 images per message
Formats: JPG, PNG, WebP, HEIC
Max 16MB per image
Auto-compressed for EA networks
Videos:
Max 1 video per message
Max 2 minutes (short video)
Max 64MB
Auto-transcoded to HLS by File Thunder
Files/Documents:
Max 100MB per file
Formats: PDF, DOCX, XLSX, ZIP, etc
ClamAV scanned before delivery
Available for 30 days then archived
8. Group Chat Flows
Create Group
User creates group:
Tap new group
Add members (min 2, max 500)
Set group name (required)
Set group photo (optional)
Tap create
Group created:
Members notified: "Kibuti amekuongeza kwenye kikundi"
All members see welcome message
Creator is automatically OWNER
Group Roles & Permissions
Role Add members Remove Send msg Delete msg Change info
──────────────────────────────────────────────────────────────────
OWNER ✅ ✅ ✅ Any msg ✅ ✅
ADMIN ✅ ✅ ✅ Any msg ✅ ✅
MEMBER ❌ ❌ ✅ Own only ✅ ❌
Group Size Limits
Free group: max 50 members
Pro group: max 500 members
At 500 members:
Consider using a Public Group instead
Public group + announcement mode = large audience
Group = two way community discussion
Leave / Remove
Member leaves:
"Juma ameacha kikundi" shown in thread
No longer receives messages
Admin removes member:
"Amina ameondolewa na Kibuti" shown
Member notified: "Umeondolewa"
Deleted group (owner only):
All members removed
All messages deleted (for everyone)
Cannot be undone
9. Commerce DM Flows
Flow 1 — Buyer Initiates from Product Page
Step 1: Buyer on product page
Sees: [Chat na Muuzaji] button
Tap button
Step 2: Conversation opens
New DM opens OR existing conversation if already had one
Conversation type: COMMERCE
Conversation owner: SHOP (not personal)
Product card auto-appears:
┌──────────────────────────────────────┐
│ 📦 Samsung A15 │
│ TZS 450,000 │
│ Inapatikana: Vipande 12 │
│ TechStore │
│ [Jibu] [Angalia Bidhaa] │
└──────────────────────────────────────┘
NOTE: Price in card is frozen at this moment
Public price changes later → does NOT affect this card
Step 3: Negotiation
Buyer and seller exchange text messages
Normal conversation — no restrictions on text
Step 4: Agreement reached
Seller taps attach (+) → From My Shop
Selects product
Sets custom price for THIS buyer ONLY
Public product price: unchanged ✅
Sends price offer card
Step 5: Buyer receives offer
┌──────────────────────────────────────┐
│ 💰 Bei Maalum Kwako │
│ Samsung A15 │
│ ~~TZS 450,000~~ │
│ TZS 400,000 (-TZS 50,000) │
│ Inaisha: dakika 28 │
│ Idadi: [─ 1 +] │
│ [Kataa] [Endelea Kulipa →] │
└──────────────────────────────────────┘
Step 6: Buyer taps "Endelea Kulipa"
Redirected to checkout flow (OUTSIDE inbox)
Checkout at custom offer price
Quantity confirmed in checkout
Step 7: Order placed
Confirmation message appears in same thread:
┌──────────────────────────────────────┐
│ ✅ Agizo Limethibitishwa │
│ Ord #ORD-XYZ-789 │
│ Samsung A15 × 1 │
│ TZS 400,000 imelipwa │
│ [Fuatilia Agizo] │
└──────────────────────────────────────┘
Flow 2 — Seller Attaches from Inside Any Chat
Step 1: Seller inside any DM conversation
Taps attach (+) button
Menu appears:
📷 Picha
🎵 Sauti
📄 Faili
🏪 Kutoka Dukani ← this one
📅 Tukio
👥 Ununuzi wa Pamoja
Step 2: Seller taps "Kutoka Dukani"
Their shop product list opens
Seller browses and selects product
Sets custom price (optional)
Sets quantity limit (optional)
Adds note (optional): "Bei hii ni leo tu"
Taps Send
Step 3: Buyer receives offer card
Same offer card UI as Flow 1
Same checkout redirect
Same order confirmation in thread
Commerce DM Rules
✅ Buyer can initiate from any product page
✅ Seller can attach from inside any conversation
✅ Custom price is private to this buyer only
✅ Public product price never changes
✅ Checkout always happens outside inbox
✅ Order confirmation appears in thread
✅ Multiple offers allowed in one conversation
❌ Buyer cannot request specific price (only negotiate via text)
❌ Offer cannot be edited after sending (send new one instead)
❌ Offer cannot be forwarded to other conversations
❌ Order confirmation cannot be deleted or edited
10. Offer Session Flows
Offer Lifecycle
PENDING → offer sent, waiting for buyer response
│
├──▶ DECLINED buyer tapped "Kataa"
│ seller notified
│ seller can send new offer
│
├──▶ EXPIRED timer ran out
│ both parties notified in thread
│ seller can send new offer
│
▼
ACCEPTED → buyer tapped "Endelea Kulipa"
│ buyer enters checkout
▼
CHECKOUT → buyer in payment flow
│
├──▶ ABANDONED buyer left checkout without paying
│ offer returns to PENDING? NO
│ offer marked ABANDONED
│ seller must send new offer
│
▼
COMPLETED → order placed successfully
│ cannot be reversed here (order system handles)
▼
CANCELLED → order cancelled (handled by order system)
offer marked CANCELLED
Multiple Offers — Same Conversation
Seller can send multiple offers:
Each is independent session
Previous offers remain in thread with their status
Thread shows history:
┌──────────────────────────────────────┐
│ 💰 Bei Maalum: TZS 430,000 │
│ ❌ Ilikataliwa │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 💰 Bei Maalum: TZS 410,000 │
│ ⏰ Imeisha muda │
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ 💰 Bei Maalum: TZS 400,000 │
│ ✅ Imekubaliwa │
└──────────────────────────────────────┘
Offer Expiry Scenarios
Scenario 1 — Expires while buyer is reading:
Offer card shows live countdown timer
Timer hits 0:00
Card updates: "Imeisha muda"
Proceed button disabled automatically
Buyer sees: [Omba Bei Mpya] button
Scenario 2 — Expires while buyer in checkout:
Buyer was on checkout page when offer expired
Checkout validates offer at payment time
If expired: payment rejected
Buyer returned to conversation
"Bei yako imeisha muda. Omba bei mpya"
Scenario 3 — Seller sends same price again:
Allowed — new offer session created
New 30-minute timer
Both offers visible in thread (old + new)
Scenario 4 — Buyer tries to use expired offer link:
Deep link from notification opens expired offer
Shows: "Bei hii imeisha muda"
[Rudi kwa Mazungumzo] button
Price Security Rules
✅ Offer price stored server-side only
✅ Checkout validates price from server (not client)
✅ Client cannot manipulate price
✅ Offer is single-use (cannot complete twice)
✅ Offer belongs to specific buyer (others cannot use)
❌ Seller cannot change price after offer sent
(must send new offer)
❌ Buyer cannot change price
❌ Public product price never affected
11. Shareable Content
What Can Be Shared Into Any DM or Group
Content Who can share Restrictions
────────────────────────────────────────────────────────────
Text message Anyone None
Voice note Anyone Max 2 min (basic)
Image/Video Anyone Size limits
Product card Anyone Shows public price
Custom price offer Seller only Private — NOT forwardable
Event card Anyone None
Bei ya pamoja card Anyone None
VP Feed post Anyone None
VP Live stream Anyone None
Audio Space Anyone None
File/Document Anyone Max 100MB
Forwarding Rules
Forward chain tracking:
chain 1: "↪ Imetumwa kutoka Juma Mwangi"
chain 2-4: "↪ Imetumwa"
chain 5+: "↪ Imetumwa mara nyingi"
(misinformation warning — different icon)
Multi-forward limit:
Max 5 conversations per forward action
Prevents spam broadcasting
Cannot forward:
Custom price offers (private deal)
Order confirmations (private record)
Payment confirmations (private record)
System messages
12. Message Interactions
Editing Messages
Rules:
✅ Only original sender can edit
✅ Text messages only (not media captions yet)
✅ Within 15 minutes of sending
✅ Shows "Imehaririwa" label after edit
✅ Original send time stays the same
✅ Works in group chats (sender edits their own)
❌ Commerce cards cannot be edited (immutable)
❌ System messages cannot be edited
❌ Voice notes cannot be edited
❌ After 15 minutes: edit option disappears
UI:
Long press message → [Hariri] option (within 15 min)
After edit: message updates in place
"Imehaririwa" label appears below message
Position in thread unchanged (no jump to bottom)
Deleting Messages
Delete for Me:
✅ Any message, any time, no limit
✅ Only removed from your view
✅ Recipient still sees it
✅ Works on any message type
Delete for Everyone:
✅ Only original sender
✅ Within 15 minutes of sending
✅ Removed from all screens
✅ Shows: "Ujumbe huu umefutwa"
✅ Works in group chats
❌ Commerce cards: NOT allowed
❌ System messages: NOT allowed
❌ After 15 minutes: option disappears
Nothing is ever permanently deleted:
Soft delete only
For legal compliance + dispute resolution
Admin can view deleted messages for support cases
Users cannot recover deleted messages
Reactions
Rules:
✅ Any message can be reacted to
✅ One reaction per user per message
✅ Change reaction: send new emoji (replaces old)
✅ Remove reaction: tap same emoji again
✅ Works on commerce cards (reactions don't modify content)
❌ System messages: no reactions
Available emojis at launch:
❤️ 👍 😂 😮 😢 🙏
Expand to full emoji keyboard: Phase 3
Group reaction display:
1 type: "👍 3"
2 types: "👍 3 ❤️ 2"
3+ types: "👍 3 ❤️ 2 +2 zaidi"
Tap reaction to see who reacted:
Bottom sheet opens
List of names per emoji
Replying to Messages
Rules:
✅ Anyone in conversation can reply to any message
✅ Reply shows quoted original above new message
✅ Tap quote → scrolls to original
✅ Works in group chats
✅ Works on any message type
❌ Cannot reply to deleted messages
(shows: "Ujumbe umefutwa")
UI:
Swipe right on message → reply mode
OR long press → [Jibu] option
Quote appears in input field
Send normally
Forwarding
Rules:
✅ Anyone can forward allowed content
✅ Max 5 conversations per forward action
✅ Forward chain tracked and shown
✅ Original sender name shown (chain 1 only)
✅ Media: references original file (no re-upload)
❌ Custom price offers: not forwardable
❌ Order/payment records: not forwardable
❌ System messages: not forwardable
UI:
Long press message → [Tuma] option
Conversation picker opens
Select up to 5 conversations
Tap send
13. Voice & Video Call Flows
Initiating a Call
Kibuti taps call button on Juma's profile or conversation:
│
Permission check:
Relationship exists? ✅
Juma's privacy allows? ✅
Kibuti not blocked? ✅
│
TURN credentials generated (server)
│
Juma's phone rings:
┌──────────────────────────────────────┐
│ │
│ 📞 Simu Inayoingia │
│ │
│ [Kibuti Mwangi] │
│ @kibuti │
│ │
│ [❌ Kataa] [✅ Jibu] │
│ │
└──────────────────────────────────────┘
Works even if:
App is closed (FCM HIGH wakes it)
Screen is locked (full screen notification)
App is in background
During a Call — Controls
Voice call controls:
🔇 Mute/unmute microphone
🔊 Speaker on/off
📷 Enable camera (upgrade to video)
❌ End call
Video call controls:
🔇 Mute/unmute microphone
📷 Camera on/off
🔄 Switch camera (front/rear)
🖥️ Share screen
🔊 Speaker on/off
❌ End call
Switching Audio ↔ Video
Voice → Video:
Kibuti taps camera button during voice call
Juma sees: "Kibuti anataka kuongeza video"
Auto-accepted (based on Juma's settings)
OR Juma taps Accept
Video starts — same call session continues
Audio uninterrupted during upgrade
Video → Audio (manual):
Kibuti taps camera OFF
Video stops immediately for both
Audio continues
No renegotiation needed
Video → Audio (automatic):
Network degrades below video threshold
Video disabled automatically
Banner shown: "Video imezimwa — mtandao dhaifu"
Audio continues
Video resumes when network improves
Screen Sharing
Start screen share:
Tap screen share icon during video call
System permission dialog appears (Android/iOS)
"Ruhusu NexGate kunasa skrini yako?"
User accepts
Screen share starts
Other party sees your screen
During screen share:
Your camera: small PiP (picture in picture)
Their view: your screen (large) + your face (small)
Your view: normal call view + "Unaonyesha skrini" banner
Stop screen share:
Tap stop button
Returns to normal video call
Call Quality Indicators
Signal bars shown during call:
████ Excellent (WiFi / 4G strong)
███░ Good (4G)
██░░ Fair (3G) — may show quality banner
█░░░ Poor (2G) — video disabled, audio only
░░░░ Very poor — "Mtandao dhaifu sana" banner
Quality banner examples:
"Ubora wa sauti umepungua kwa sababu ya mtandao"
"Video imezimwa — data ndogo"
"Unaunganika tena..."
Call End Scenarios
Normal end:
One party taps end
Other party sees call ended
Duration shown in conversation: "Simu ya dakika 4:32"
Declined:
Juma taps Kataa
Kibuti sees: "Simu ilikataliwa"
Missed call notification NOT sent (was declined)
No answer (timeout 45 seconds):
Kibuti sees: "Hakujibu"
Juma sees: "Simu iliyokosekana kutoka Kibuti" notification
Network failure:
Both lose connection
App attempts reconnect (10 seconds)
If reconnect fails:
Call marked as FAILED
"Simu ilikatizwa" shown to both
Kibuti can redial
14. Group Call Flows
Starting a Group Call
From a group chat:
Tap call icon in group header
Choose: Voice only OR Video
All group members receive incoming call notification
Members who join → enter call
Members who don't → miss it (missed call shown)
From a 1:1 conversation:
Not supported directly
Must create group first
OR use "Add person" button during active 1:1 call
Add person to active 1:1 call:
During call → tap "Ongeza Mtu"
Pick from allowed contacts
They receive group call invitation
They join → 1:1 becomes group call
Group Call Limits
Voice only group call:
Max 8 participants (comfortable for 3G)
Up to 12 possible but discouraged on EA networks
Video group call:
Max 4 video feeds shown simultaneously
5th person onwards: audio only tile shown
Active speaker highlighted (larger tile)
Tap any tile to pin/feature them
Layout options:
Grid view: all tiles equal size
Speaker view: active speaker large, others small
Auto: switches based on who is talking
Group Call Permission Rules
Within a group chat:
Any member can start group call ✅
Any member can join ✅
No extra permission check needed
(group membership = call permission)
Adding someone outside the group:
Same call permission check as 1:1
Must have relationship (contact/follower/commerce)
If no relationship → cannot add
15. Offline & Notification Flows
Message Notification Levels
Level When FCM SMS after
──────────────────────────────────────────────────────────────
CRITICAL Order placed/paid/failed ✅ 0 min
Delivery update
Payment confirmation
IMPORTANT Commerce DM from buyer ✅ 10 min
Custom price offer received
Bei ya pamoja threshold reached
Call missed (commerce context)
NORMAL Regular DM ✅ Never
Group message
Reaction
Follow notification
Offline Message Delivery
User offline — message sent to them:
│
├── FCM HIGH priority push fired immediately
│ (all message levels)
│
├── CRITICAL: Textfy SMS sent simultaneously
│
├── IMPORTANT: Textfy SMS after 10 min
│ (if FCM not acknowledged)
│
└── Message queued in RabbitMQ
User comes back online:
│
├── WS connects → presence registered
├── Queued messages drained (priority order)
│ CRITICAL first → IMPORTANT → NORMAL
│
└── Catch-up banner shown:
"Umekosa: maagizo 2, ujumbe 8"
[Angalia Maagizo] [Ona Ujumbe]
Textfy SMS Templates
Order placed (to seller):
"NexGate: Agizo jipya kutoka [Buyer]!
TZS [amount]. Kagua: nexgate.app/orders/[id]"
Payment received:
"NexGate: Malipo ya TZS [amount]
yamepokelewa kutoka [Buyer].
nexgate.app/wallet"
Commerce DM (to seller):
"NexGate: [Buyer] anakuuliza kuhusu
[Product]. Jibu: nexgate.app/chat/[id]"
Bei ya pamoja:
"NexGate: Watu [n]/[target] wamejiunga!
nexgate.app/group-buy/[id]"
All SMS:
Swahili first ✅
Deep link included ✅
Shop name shown (not staff name) ✅
16. Privacy & Safety
Privacy Settings Matrix
Setting Options Default
────────────────────────────────────────────────────────────────
Who can message me Everyone / Followers Followers
+ Contacts / Contacts + Contacts
/ Nobody
Who can call me Followers+Contacts / Contacts
Contacts / Nobody only
Last seen Everyone / Contacts / Contacts
Nobody
Profile picture Everyone / Contacts / Everyone
Nobody
Be found by phone number On / Off On
Contact sync On / Off Off
Read receipts (blue ticks) On / Off On
(off = others see delivered but not read)
Block System
User A blocks User B:
User B cannot:
❌ See User A's profile
❌ Send messages to User A
❌ Call User A
❌ See User A in search
❌ See User A's VP Feed posts
❌ See User A's online status
User B sees:
Profile: "Mtumiaji huyu hayupo"
Messages: appear sent but never delivered
User A can:
✅ Unblock anytime from Settings
✅ See User B's profile still (A blocked B — not reverse)
Group chats:
If in same group: messages visible but
cannot DM or call each other directly
Admin can remove either from group
Report System
Report options:
Spam
Harassment / Vitisho
Inappropriate content
Fake account
Scam / Udanganyifu
Other
Report flow:
Long press message → [Ripoti]
OR profile → [...] → [Ripoti]
Choose reason
Optional: add description
Submit
After report:
User not notified they were reported
Report goes to NexGate moderation queue
Automatic temporary restrictions may apply
for high-volume reporters
Block on report:
"Ripoti na Zuia" option available
Blocks immediately + sends report
Safety for Women — Specific Features
This is important for EA platform trust:
Silence unknown callers:
ON by default for new accounts
Calls from non-contacts ring silently
User sees missed call — no disruption
Call rate limiting:
3 unanswered calls per day to same person
Automatic block after 4th attempt
Quick block during call:
One tap visible during any call
Ends + reports + blocks in single action
Message request system:
Strangers cannot freely DM
Must send request first
User controls who enters their inbox
Default privacy settings:
New accounts: strict defaults
Users open up if they choose
Better to protect by default
17. Shop Inbox & Staff Access
The Golden Rule — No Staff Name Ever
This is the most important rule in this section:
Customer NEVER sees staff name
In ANY communication channel:
❌ Not in DM replies
❌ Not in commerce messages
❌ Not in offer cards
❌ Not in order updates
❌ Not in system messages
❌ Not in call screen ("TechStore calling" not "Amina calling")
❌ Not in read receipts
❌ Not in typing indicators ("TechStore anaandika..." not "Amina...")
❌ Not in voice/video calls
❌ Not in notifications
Customer always sees:
✅ Shop name only: "TechStore"
✅ Shop avatar only
✅ Shop JID only
Why this rule exists:
Customer relationship is with THE SHOP
Not with individual staff members
Staff privacy protected
Staff turnover invisible to customer
Brand consistency always maintained
Cannot be broken by any staff action
System enforces this — not just a guideline
How Others Handle Staff Access
Facebook Pages:
Staff have OWN Facebook accounts
Owner assigns role via Page Settings
Staff switches to page context
Posts/replies as page — not personally
✅ No password sharing
WhatsApp Business API:
Connected to CRM (Zendesk, Freshdesk)
Each agent logs into CRM with own account
CRM sends via WhatsApp API as business
Customer sees business name only
✅ No password sharing
Shopify:
Owner invites staff via email
Staff creates own Shopify login
Access scoped to their role
Customer always sees store name
✅ No password sharing
NexGate follows same pattern:
Staff have own NexGate accounts
Owner invites staff to shop
Staff accesses shop context
Customer sees shop name only
✅ No password sharing ever
Shop Tiers
Basic shop (free):
Owner manages inbox alone
No staff assignment
Standard inbox features
Shop name shown to customers ✅
Pro shop (paid):
Staff roles unlocked
Multiple staff share shop inbox
Advanced analytics
Full audit logs
Priority support
Staff management dashboard
Shop name shown to customers ✅ (same rule)
Staff Roles & Permissions
Role Inbox Send msg Products Analytics Settings Staff mgmt
──────────────────────────────────────────────────────────────────────────────
OWNER ✅ ✅ ✅ ✅ ✅ ✅
MANAGER ✅ ✅ ✅ ✅ ❌ ❌
SUPPORT_AGENT ✅ ✅ ❌ ❌ ❌ ❌
READ_ONLY 👁️ only ❌ ❌ ❌ ❌ ❌
Notes:
OWNER: full control — only they can invite/remove staff
only they can change shop tier
only they can delete shop
MANAGER: day-to-day shop management
can send price offers
can view product catalog
cannot change settings or manage staff
SUPPORT_AGENT: inbox only
can reply to customers as shop
can send price offers
cannot see/edit products
READ_ONLY: view conversations only
cannot reply
useful for supervisors/auditors
Staff Invitation Flow
Step 1 — Owner sends invitation:
Shop Settings → Staff → Alika Mfanyakazi
┌──────────────────────────────────────────┐
│ ➕ Alika Mfanyakazi │
│ │
│ Barua pepe au nambari ya simu: │
│ [amina@gmail.com ] │
│ │
│ Jukumu: │
│ ○ Msimamizi (Manager) │
│ ● Wakala wa Msaada (Support Agent) │
│ ○ Soma Tu (Read Only) │
│ │
│ [Tuma Mwaliko] │
└──────────────────────────────────────────┘
System:
Generate secure invitation token
Token expires: 48 hours
Send to Amina via SMS + email:
"Kibuti amekualika kuwa mfanyakazi
wa TechStore kwenye NexGate.
Bonyeza hapa: nexgate.app/invite/TOKEN
Mwaliko unaisha baada ya masaa 48."
Step 2 — Staff receives invitation:
Case A — Amina already has NexGate account:
Taps invitation link
Logs in with OWN credentials
Sees invitation screen:
┌──────────────────────────────────────────┐
│ 🏪 Mwaliko wa TechStore │
│ │
│ Kibuti Mwangi anakualika kujiunga na │
│ TechStore kama: │
│ Wakala wa Msaada │
│ │
│ Utaweza: │
│ ✓ Kujibu ujumbe wa wateja │
│ ✓ Kutuma ofa za bei │
│ ✓ Kuona mazungumzo yote ya duka │
│ │
│ Hutaweza: │
│ ✗ Kuona mazungumzo ya kibinafsi ya owner│
│ ✗ Kubadilisha mipangilio ya duka │
│ │
│ [Kubali] [Kataa] │
└──────────────────────────────────────────┘
Taps Kubali → linked to TechStore immediately
Case B — Amina has no NexGate account:
Taps invitation link
Registration page opens
Registers with own phone number + PIN
Invitation auto-accepted after registration
Linked to TechStore as Support Agent
Case C — Token expired (>48 hours):
"Mwaliko huu umeisha muda"
Owner must send new invitation
Step 3 — Staff accesses shop:
Amina logs into NexGate normally:
Her own phone number
Her own PIN
Her own account entirely
Her inbox shows tabs:
[Personal ●3] [TechStore ●12]
Personal tab:
Her own DMs and groups
Completely private
TechStore cannot see this
TechStore tab:
All TechStore customer conversations
Shared with all TechStore staff
She replies as "TechStore" ✅
Step 4 — Staff replies to customer:
Amina opens TechStore conversation
Context indicator clearly shown:
┌──────────────────────────────────────────┐
│ 🏪 Unajibu kama: TechStore │
│ (Wakala wa Msaada) │
│──────────────────────────────────────────│
│ Customer: "Je, Samsung A15 ipo?" │
│ │
│ [Andika ujumbe kama TechStore...] │
│ │
│ [📷] [🎤] [📎] [➤ Tuma] │
└──────────────────────────────────────────┘
Customer receives:
TechStore: "Ndio, Samsung A15 ipo!"
No "Amina" anywhere ✅
No mention of staff ✅
Just TechStore brand ✅
What Customer Sees vs What Owner Sees
Customer view of conversation:
10:30 [Customer]: "Je, A15 ipo?"
10:32 [TechStore]: "Ndio, ipo! Bei TZS 450,000"
10:35 [Customer]: "Naweza kupata punguzo?"
10:36 [TechStore]: "Nitaona ninachoweza kufanya"
10:40 [TechStore]: 💰 Bei Maalum: TZS 400,000
[Offer card]
Customer never knows:
Who replied at 10:32
Who replied at 10:36
Whether same person replied
How many staff exist
Whether it's the owner or staff
Owner/Manager audit view:
10:30 Customer: "Je, A15 ipo?"
10:32 Amina (Support Agent): "Ndio, ipo! Bei TZS 450,000"
10:35 Customer: "Naweza kupata punguzo?"
10:36 Amina (Support Agent): "Nitaona ninachoweza kufanya"
10:40 John (Manager): Bei Maalum TZS 400,000 imetumwa
Staff Identity Rules — Complete List
Staff name NEVER appears in:
❌ DM message content ("TechStore" always)
❌ Offer cards (shop name only)
❌ Order confirmation messages
❌ Typing indicator ("TechStore anaandika...")
❌ Read receipts (customer sees shop read it)
❌ Call screen ("TechStore" calling / answering)
❌ Missed call notification ("Simu kutoka TechStore")
❌ Voice note sender name
❌ Reaction attribution (customer sees shop reacted)
❌ System messages
Staff identity ONLY appears in:
✅ Internal audit log (owner + manager view)
✅ Staff management dashboard
✅ Internal analytics (who handled most conversations)
✅ Staff's own inbox context indicator
"Unajibu kama: TechStore (Wakala wa Msaada)"
This rule is enforced by the system:
Staff JID: techstore@shops.nexgate.com/amina
Customer sees: techstore@shops.nexgate.com
Resource (/amina) stripped before sending to customer
Cannot be overridden by any staff action
Typing Indicator — Shop Name Not Staff
Amina is typing reply:
Customer sees: "TechStore anaandika..."
NOT: "Amina anaandika..."
NOT: "Mfanyakazi anaandika..."
Two staff typing simultaneously:
Customer sees: "TechStore anaandika..."
(same — no way to know it's two people)
This is correct behavior
Ejabberd handles this via shop JID:
Composing stanza from: techstore@shops.nexgate.com
Resource stripped before routing to customer
Voice/Video Call — Staff Anonymity
Customer calls TechStore:
Whoever answers (any available staff)
Call screen shows to customer:
"TechStore" (shop name)
Shop avatar
NOT staff name
Staff receives call:
Their screen shows:
Customer name (from conversation)
"Simu kwa TechStore" indicator
They answer as TechStore
Staff initiates call to customer:
Customer's incoming call screen shows:
"TechStore" calling
NOT "Amina" calling
NOT a personal number
Call log in customer's conversation:
"Simu na TechStore — dakika 4:32"
NOT "Simu na Amina"
Owner Managing Staff
Staff management dashboard (owner only):
┌──────────────────────────────────────────────────────┐
│ 👥 Wafanyakazi wa TechStore │
│ │
│ Amina Hassan │
│ Support Agent · Imeunganishwa: Jan 15, 2026 │
│ Imetumika mara ya mwisho: Leo 14:30 │
│ Mazungumzo 47 wiki hii │
│ [Badilisha Jukumu] [Simamisha] [Ondoa] │
│ │
│ John Doe │
│ Manager · Imeunganishwa: Feb 1, 2026 │
│ Imetumika mara ya mwisho: Jana 09:15 │
│ Mazungumzo 23 wiki hii │
│ [Badilisha Jukumu] [Simamisha] [Ondoa] │
│ │
│ [+ Alika Mfanyakazi Mpya] │
└──────────────────────────────────────────────────────┘
Owner actions:
✅ Invite new staff
✅ Change role (Manager ↔ Support Agent ↔ Read Only)
✅ Suspend temporarily (keeps access frozen, not removed)
✅ Remove permanently (instant access revocation)
✅ View activity stats per staff
✅ View full audit log
Remove staff — what happens:
Access revoked IMMEDIATELY
TechStore tab disappears from their inbox
All pending conversations stay in shop inbox
(conversations not lost — just staff can't see them)
No notification sent to customers
Historical messages remain attributed in audit log
Multi-Shop Staff
Amina works for two shops:
Shop A: TechStore (Support Agent)
Shop B: ClothingHub (Manager)
— Different owner (different business)
Her inbox shows:
[Personal] [TechStore] [ClothingHub]
Rules:
TechStore conversations: visible ✅
ClothingHub conversations: visible ✅
Each shop completely isolated from other ✅
TechStore cannot see ClothingHub data ✅
ClothingHub cannot see TechStore data ✅
Her personal inbox: only she sees ✅
Security Rules
✅ Each staff has own NexGate account
✅ Owner never shares password
✅ Invitation token secure + expires 48 hours
✅ Role-based access strictly enforced
✅ Customer always sees shop name (system enforced)
✅ Audit log tracks every action
✅ Owner can revoke access instantly
✅ Staff cannot access unassigned shops
✅ Staff cannot access owner personal inbox
✅ Staff cannot change their own role
✅ Staff cannot invite other staff (Manager+ only)
✅ Removed staff cannot export conversation history
❌ No shared passwords ever
❌ No staff name to customers ever
❌ No personal contact revealed to customers
❌ No cross-shop data access
❌ No personal inbox access by staff
Database Schema
shop_staff_invitations
─────────────────────────────────────────────
invitation_id UUID
shop_id UUID
invited_by UUID owner userId
invitee_email TEXT
invitee_phone TEXT
role ENUM MANAGER / SUPPORT_AGENT / READ_ONLY
token_hash TEXT hashed secure token
status ENUM PENDING / ACCEPTED / DECLINED / EXPIRED
expires_at TIMESTAMPTZ 48 hours from creation
created_at TIMESTAMPTZ
responded_at TIMESTAMPTZ
shop_staff_members
─────────────────────────────────────────────
id UUID
shop_id UUID
user_id UUID staff NexGate userId
role ENUM OWNER / MANAGER / SUPPORT_AGENT / READ_ONLY
invited_by UUID
joined_at TIMESTAMPTZ
last_active_at TIMESTAMPTZ
status ENUM ACTIVE / SUSPENDED / REMOVED
suspended_at TIMESTAMPTZ
removed_at TIMESTAMPTZ
removed_by UUID
shop_staff_audit_log
─────────────────────────────────────────────
log_id UUID
shop_id UUID
staff_user_id UUID
action ENUM MESSAGE_SENT / OFFER_SENT /
OFFER_CREATED / CALL_ANSWERED /
PRODUCT_VIEWED / MEMBER_ADDED /
MEMBER_REMOVED / ROLE_CHANGED
conversation_id UUID
message_id UUID
timestamp TIMESTAMPTZ
metadata JSONB action-specific details
18. Notification Settings
Per-Conversation Settings
Each conversation has:
Notifications:
○ All messages
○ Mentions only (groups)
○ Muted (until: 8 hours / 1 week / forever)
Media auto-download:
○ WiFi only
○ WiFi + Mobile data
○ Never
Global Notification Settings
Message notifications: On / Off
Call notifications: On / Off
Reaction notifications: On / Off
Group notifications: On / Off
Commerce notifications: On / Off (cannot turn off CRITICAL)
Sound: Default / Custom / Silent
Vibration: On / Off
In-app preview: Show / Hide content
CRITICAL Notifications — Always On
These cannot be turned off by user:
Order placed (seller receives)
Payment confirmed (buyer receives)
Payment failed (buyer receives)
Order cancelled (both receive)
Reason:
Financial events
User could miss critical money information
Platform liability without guaranteed delivery
Textfy SMS fallback ensures they always arrive
19. Edge Cases & Scenarios
Messaging Edge Cases
User deletes their account:
Their messages remain visible in conversations
Name shows: "Mtumiaji aliyefuta akaunti"
Profile picture: default avatar
Cannot be messaged or called
User changes username:
All conversations update automatically
Old username links still work (redirect)
No broken references
Very long message (>4000 chars):
Truncated in thread: first 200 chars + "...Soma zaidi"
Tap to expand full message
Not split into multiple messages
Same message sent twice (duplicate):
Detected by temp_id
Only one stored in DB
User sees single message (not duplicate)
Message sent to blocked user:
Appears sent (single tick)
Never delivered (stays at single tick)
User not informed of block
Commerce Edge Cases
Product goes out of stock while offer pending:
Offer still valid (was frozen at send time)
Checkout validates stock at payment time
If out of stock at payment:
Payment rejected
"Bidhaa hii imeisha"
Buyer returned to conversation
Seller notified to resend offer or cancel
Product price changes while offer pending:
Offer price locked at creation ✅
Public price change does NOT affect offer
Buyer pays the offer price always
Seller deletes product while offer pending:
Offer still valid (snapshot frozen)
Checkout validates product existence
If product deleted at payment:
Payment rejected
"Bidhaa hii haipatikani tena"
Two buyers get offers for last item:
Both offers exist simultaneously
First to complete checkout gets the item
Second buyer's payment rejected:
"Bidhaa hii imeisha"
Standard e-commerce race condition handling
Seller sends offer to wrong person:
Cannot retract offer
Offer expires naturally
Seller can contact NexGate support if urgent
Buyer in checkout when offer expires:
Payment attempt fails
"Bei yako imeisha muda"
Must request new offer from seller
Call Edge Cases
Call drops mid-conversation:
WebRTC detects loss
Auto-reconnect attempted (10 seconds)
If reconnect success: call continues
If reconnect fails: call ended
Both see: "Simu ilikatizwa"
Kibuti can redial immediately
Both call each other simultaneously:
Race condition — both see "Simu inayoingia"
System picks one (first to reach server)
Other cancelled automatically
One call established
Call during active call:
Second call: goes to missed calls
Busy signal not sent (no UX for this)
After first call: notification of missed call
Phone runs out of battery during call:
WebRTC detects disconnect
Same as network drop
Other party sees: "Simu ilikatizwa"
Someone calls during Do Not Disturb (DND):
DND on device: depends on device DND rules
NexGate mute: call goes to missed calls silently
CRITICAL commerce calls: bypass mute (configurable)
Group Chat Edge Cases
Owner leaves group:
Must transfer ownership first
OR system auto-assigns to oldest admin
OR if no admins: oldest member becomes owner
Last person leaves group:
Group archived automatically
Messages preserved for 30 days
Then permanently deleted
Adding member who blocked you:
Cannot add blocked users to groups
System rejects silently
No error shown (privacy)
Member blocked in group:
They stay in group (cannot remove via block)
Cannot DM each other
Can both still see group messages
Admin can remove either from group
20. Follow System — Users & Shops
Two Entity Types — Two Sigils
NexGate has two distinct entity types:
User: @kibuti (a person)
Shop: $techstore (a business)
The sigil makes it immediately clear
what type of entity you are interacting with
@ = social relationship
$ = commercial relationship
Follow Button — Users
On @kibuti profile page:
[+ Follow]
What following a user gives you:
Their VP Feed posts appear in your feed
Their VP Live streams appear in your feed
Their events appear in your feed
You can message them (mutual relationship)
They can message you back
Mutual follow:
Both @kibuti and @juma follow each other
= stronger relationship
= can call each other
= message requests not needed
One-way follow:
@kibuti follows @juma
@juma does NOT follow @kibuti
Kibuti sees Juma's content
Juma does not see Kibuti's content
Kibuti can message Juma (follower relationship)
Juma sees message request (not mutual)
Follow Button — Shops
On $techstore profile page:
[+ Follow Shop]
NOT "Subscribe" because:
Subscribe implies payment (Netflix, Spotify)
EA users might think it costs money
Follow is free and familiar ✅
"Follow Shop" label sets clear expectation
NOT just "Follow" because:
"Follow Shop" makes clear it's a business
Sets expectation: commercial content
Not personal/social content
What following a shop gives you:
Shop products appear in your VP Feed
Shop promotions appear in feed
Shop VP Live streams appear in feed
Shop events appear in feed
FCM notification: "TechStore posted new products"
Commerce DM permission (can initiate chat)
Can be invited to shop customer groups
Can receive Bei ya pamoja from shop
Shop Following Rules
Shops do NOT follow anyone:
No "Following" count on shop profile
Shops are followed — they don't follow
Like a Facebook Page
Like a YouTube channel
Shop profile shows:
Followers: 3,420 ← people following the shop
No "Following" count
User profile shows:
Followers: 1,240
Following: 856
Both counts shown (social graph)
Unfollow Behavior
User unfollows @kibuti:
Kibuti NOT notified ✅ (standard)
Kibuti's content leaves your feed ✅
Can still message Kibuti ✅
(if previously connected)
Relationship weakened but not broken
User unfollows $techstore:
Shop NOT notified ✅
Shop content leaves your feed ✅
Can still initiate commerce DM ✅
(unfollow ≠ block)
Cannot be added to shop groups ✅
(lost follower relationship)
Existing order conversations remain ✅
Follow Count Display
@kibuti profile:
┌────────────────────────────────┐
│ @kibuti │
│ Kibuti Mwangi │
│ │
│ 1,240 856 │
│ Followers Following │
│ │
│ [+ Follow] [Message] │
└────────────────────────────────┘
$techstore profile:
┌────────────────────────────────┐
│ $techstore │
│ TechStore │
│ Electronics · Dar es Salaam │
│ │
│ 3,420 │
│ Followers │
│ │
│ [+ Follow Shop] [Message] │
└────────────────────────────────┘
21. Group Join Model
Two Group Types
PRIVATE GROUP:
Closed — controlled membership
Not discoverable in search
Default when creating a group
Like WhatsApp groups
PUBLIC GROUP:
Open — anyone can join
Discoverable in NexGate search
Explicit choice by creator
Like Telegram public groups
How People Join Groups — The Philosophy
NexGate principle:
Nobody ends up in a group
without choosing to be there
No WhatsApp-style direct add
(added before you know it)
Two mechanisms instead:
1. Consent DM invitation (proactive)
2. Invite link (self-service)
Both require the person to
actively choose to join
Mechanism 1 — Consent DM Invitation
Admin selects people from:
Their contacts ✅
Their followers ✅
Their commerce relationships ✅
NOT random strangers ❌
Each selected person receives a DM:
┌──────────────────────────────────────────┐
│ 📨 Group Invitation │
│ │
│ Kibuti Mwangi invited you to join: │
│ │
│ 🏘️ Business Friends │
│ 47 members · Private Group │
│ "Discussion for Dar founders" │
│ │
│ [Accept & Join] [Decline] │
└──────────────────────────────────────────┘
If Accept: member immediately ✅
If Decline: not added ✅
admin NOT notified (privacy)
If Ignored: auto-declined after 48 hours ✅
Why this model:
✅ Familiar to EA users (like WhatsApp add)
✅ But with consent (unlike WhatsApp)
✅ Commerce groups work (seller invites customers)
✅ Event groups work (organizer invites attendees)
✅ Family/friends groups easy to start
✅ User always in control
Mechanism 2 — Invite Link
Admin generates invite link:
nexgate.app/join/abc-xyz-def
Person taps link → sees group preview:
PRIVATE GROUP link:
┌─────────────────────────────────────────┐
│ 🔒 Private Group │
│ Business Friends │
│ 47 members │
│ Created by Kibuti Mwangi │
│ │
│ This group requires admin approval │
│ │
│ [Request to Join] │
└─────────────────────────────────────────┘
Admin sees request:
Name, username, mutual connections
[Approve] [Decline]
If approved → member ✅
If declined → person not notified (privacy)
PUBLIC GROUP link:
┌─────────────────────────────────────────┐
│ 🌍 Public Group │
│ Dar Tech Community │
│ 1,247 members │
│ Created by @kibuti │
│ "Discussion for Dar tech founders" │
│ │
│ [Join Group] │
└─────────────────────────────────────────┘
Tap [Join Group] → member immediately ✅
No approval needed
Invite Link Settings
Admin controls (Group Settings → Invite Link):
┌──────────────────────────────────────────┐
│ Invite Link │
│ nexgate.app/join/abc-xyz │
│ [Copy] [Share] [Revoke] │
│ │
│ Expiry: │
│ ● Never │
│ ○ 24 hours │
│ ○ 7 days │
│ ○ 30 days │
│ │
│ Max joins: [Unlimited ▾] │
│ Options: 10 / 25 / 50 / 100 / Unlimited │
└──────────────────────────────────────────┘
Revoke link:
Old link immediately dead
"This invite link is no longer valid"
New link auto-generated
Invitation Limits (Anti-Spam)
Per group per day:
Admin can send max 50 consent DM invitations
Prevents mass-invite spam
Per user per day:
User can receive max 10 group invitations
11th → goes to group requests folder
User reviews when ready
Rate limiting:
If 80%+ of your invitations declined
System flags account
Temporary invite restriction applied
Group Admin System
OWNER (1 per group):
Created the group OR ownership transferred
Cannot be removed by anyone
Full control over everything
Can delete the group
Can transfer ownership
ADMIN (multiple):
Appointed by OWNER
Can add/remove members
Can remove any message
Can pin messages
Can change group info
Cannot remove OWNER
Cannot remove other ADMINS
MEMBER (everyone else):
Can send messages
Can react, reply, forward
Can delete own messages only
Cannot manage others
Group Admin Permissions Matrix
Action Owner Admin Member
────────────────────────────────────────────────────
Send messages ✅ ✅ ✅
Delete own messages ✅ ✅ ✅
Delete any message ✅ ✅ ❌
Send consent DM invite ✅ ✅ ❌
Remove members ✅ ✅ ❌
Make someone admin ✅ ❌ ❌
Remove admin ✅ ❌ ❌
Change group name/photo ✅ ✅ ❌
Generate invite link ✅ ✅ ❌
Revoke invite link ✅ ✅ ❌
Pin messages ✅ ✅ ❌
Announcement mode ✅ ❌ ❌
Transfer ownership ✅ ❌ ❌
Delete group ✅ ❌ ❌
Leave group ✅* ✅ ✅
*must transfer ownership first
Group Settings Panel
Admin opens Group Settings:
┌──────────────────────────────────────────┐
│ ⚙️ Group Settings │
│ │
│ Group Name │
│ [Business Friends ] │
│ │
│ Description │
│ [Discussion for Dar founders ] │
│ │
│ Group Type │
│ ● 🔒 Private (invite + approval) │
│ ○ 🌍 Public (anyone can join) │
│ │
│ Who can send messages? │
│ ● All members │
│ ○ Admins only (announcement mode) │
│ │
│ Who can send invitations? │
│ ● Admins only │
│ ○ All members │
│ │
│ Invite Link ──────────────────────── │
│ nexgate.app/join/abc-xyz │
│ [Copy] [Share] [Revoke] │
│ Expiry: [Never ▾] │
│ Max joins: [Unlimited ▾] │
│ │
│ ─────────────────────────────────────── │
│ 🗑️ Delete Group │
└──────────────────────────────────────────┘
NexGate Natural Group Contexts
These groups form naturally without strangers:
VP Events:
Organizer creates event group
Ticket buyers receive consent DM automatically
"Dar Tech Summit invited you to Attendees group"
All attendees = commerce relationship ✅
VP Shop customer group:
Seller creates customer community group
Past buyers receive consent DM invitation
"TechStore invited you to VIP Customers group"
All = previous buyers ✅
VP Feed creator community:
Creator makes PUBLIC group
Shares link on VP Feed
Followers join themselves
All = followers (relationship exists) ✅
Friends/family:
Admin sends consent DM to close contacts
Small group bootstrapped easily
All = contacts/followers ✅
22. Shop Chat Initiation
The Core Principle
Customer relationship = permission to initiate
No relationship = cannot initiate
This applies to shops messaging customers
Same rule as personal messaging
Consistent across all of NexGate
When Shop Can Initiate
ALWAYS ALLOWED (transactional):
Order placed → shop sends confirmation
Order shipped → shop sends update
Order issue → shop contacts buyer
Payment problem → shop contacts buyer
→ Goes directly to customer commerce inbox
→ Customer expects this ✅
→ Cannot be turned off
ALLOWED with relationship:
Customer bought from shop before ✅
Customer sent message to shop before ✅
Customer follows the shop ($techstore) ✅
→ Shop can initiate from order/customer list
→ Goes to existing commerce thread
→ OR new message request if no thread yet
NEVER ALLOWED:
Cold message to random NexGate users ❌
Mass promotional outreach ❌
Message to users who never interacted ❌
→ System blocks this
→ "You can only message customers
who have interacted with your shop"
How Shop Staff Initiates
From existing order:
Shop → Orders → Find Kibuti's order
Tap [Message Customer]
→ Opens existing commerce thread
→ Send message as TechStore ✅
From customer list:
Shop → Customers → Find Kibuti
Tap [Send Message]
→ Opens existing thread OR
→ New message request to Kibuti
→ Kibuti sees: "TechStore wants to send you a message"
From active conversation:
Staff sees conversation in shop inbox
Replies as TechStore
Normal response flow ✅
From VP Feed (not messaging — different):
Shop posts on VP Feed
Followers see it in feed
Interested followers DM the shop
Shop replies ✅
Transactional Messages — Auto System
These fire automatically from Spring Boot
No manual staff action needed:
Order placed:
→ System message in commerce thread:
"Your order ORD-789 has been confirmed
Samsung A15 × 1 — TZS 400,000
[Track Order]"
Order shipped:
→ "Your order has been shipped
Expected: 2-3 days
[Track Delivery]"
Order delivered:
→ "Your order has been delivered!
How was your experience?
[Leave Review]"
Payment failed:
→ "Payment issue with your order
Please update your payment method
[Fix Payment]"
All go directly to commerce inbox ✅
Customer always expects these ✅
Cannot be disabled by customer ✅
Customer Controls
Settings → Privacy → Shop Messages:
Who can send me shop messages?
● Shops I have bought from (default)
○ Shops I follow + bought from
○ Nobody (all go to requests)
Allow shops to send me promotions?
○ Yes
● No (default)
Allow order updates from shops?
● Always (cannot turn off)
(transactional — always needed)
23. Broadcast Channels — Why Not Needed
The Question
Should NexGate have broadcast channels?
Like Telegram channels or WhatsApp channels?
One-way: creator/shop → followers
What VP Feed Already Covers
VP Feed does everything a broadcast channel does:
Public announcements:
Shop posts new product → VP Feed ✅
Creator posts update → VP Feed ✅
All followers see it ✅
Urgent alerts:
FCM HIGH priority notification on post ✅
"TechStore posted: Flash sale today!"
Same urgency as channel message ✅
Exclusive content:
VP Feed close friends feature ✅
Post visible to selected followers only
Covers "exclusive subscriber" use case ✅
Shop promotions:
Shop posts on VP Feed ✅
Product tags + sale tags ✅
Followers see in feed ✅
The question becomes:
What does a broadcast channel add
that VP Feed with notifications doesn't?
Honestly — very little
Why NOT to Build Broadcast Channels
Duplication:
Two places creator manages content
Two places follower checks for updates
"Should I post this to feed or channel?"
Confusing for both creators and followers
WeChat lesson:
WeChat has both feed (Moments)
AND official accounts (channels)
Users find it confusing what goes where
Even WeChat admits this overlap
NexGate should be cleaner
Simpler is better:
One place for content → VP Feed
One place for conversation → Messaging
No hybrid in between
Clean product with clear purpose
Inbox stays clean:
Personal DMs
Group chats
Shop commerce DMs
That's it
No broadcast section cluttering inbox
What Covers Each Use Case Instead
Use case Solution (no broadcast needed)
──────────────────────────────────────────────────────────
Public announcements VP Feed post ✅
Urgent alerts VP Feed + FCM notification ✅
Flash sales VP Feed with sale tag ✅
Exclusive content VP Feed close friends ✅
Private community Group chat ✅
Personal deals 1:1 Commerce DM ✅
Group deals Group + Bei ya pamoja ✅
Customer updates Transactional DMs (auto) ✅
Order updates Commerce thread (auto) ✅
Decision
Broadcast channels: NOT BUILT ✅
VP Feed is NexGate's content distribution layer
Messaging is NexGate's conversation layer
They serve different purposes
They stay separate
No overlap needed
24. Commerce Attach — 1:1 vs Group
The Clean Separation
Custom price offer:
1:1 DMs ONLY
Private negotiation between one seller and one buyer
Makes no sense in a group
(why negotiate privately in front of everyone?)
Bei ya pamoja (group purchase):
Group chats (primary home)
Also shareable in 1:1 DMs
Group power buying
Dynamic pricing
Perfect for group context
Attach Menu — 1:1 DM
Inside a 1:1 conversation:
Tap attach (+)
📷 Image
🎵 Voice Note
📄 File
🏪 From My Shop → custom price offer
(private deal — this buyer only)
📅 Event Card → share any event
👥 Bei ya pamoja → share a group buy
(recipient can join or share further)
Attach Menu — Group Chat
Inside a group conversation:
Tap attach (+)
📷 Image
🎵 Voice Note
📄 File
🏪 From My Shop → product card only
(public price shown)
(NO custom price in group)
📅 Event Card → share any event
👥 Bei ya pamoja → start or share group buy
THIS is group commerce ✅
Why Custom Price NOT in Groups
Custom price = private negotiation
"I'll give you a special deal"
Said to ONE person in private
Makes sense in 1:1 ✅
Custom price in group = awkward
"I'll give everyone TZS 400,000"
In front of 50 group members
Why is this price special?
How is it different from a sale?
It's NOT private anymore
Loses its meaning ❌
Bei ya pamoja = group power
"If we get 10 people together we save"
Collective action in a group
Group chat is the PERFECT home for this ✅
Dynamic pricing makes sense collectively ✅
Bei ya Pamoja in Group — Flow
Someone shares Bei ya pamoja in group:
┌──────────────────────────────────────────┐
│ 👥 Group Purchase │
│ Samsung A15 │
│ │
│ Public price: TZS 450,000 │
│ Group price: TZS 350,000 (10 people) │
│ │
│ Progress: ████████░░ 8 / 10 │
│ 2 more people needed │
│ │
│ Expires: 23 hours 45 minutes │
│ │
│ [Join Group Buy] │
└──────────────────────────────────────────┘
Group members:
See card in group thread ✅
Anyone can tap [Join Group Buy] ✅
Progress updates in real time ✅
When 10 people join → all checkout ✅
Card updates: "Target reached! Proceeding..." ✅
Summary Table
Feature 1:1 DM Group Chat
──────────────────────────────────────────────────
Text message ✅ ✅
Voice note ✅ ✅
Media (image/video) ✅ ✅
File/document ✅ ✅
Product card ✅ (public) ✅ (public)
Custom price offer ✅ seller only ❌ not available
Bei ya pamoja ✅ shareable ✅ primary home
Event card ✅ ✅
Post card ✅ ✅
Stream card ✅ ✅
Summary Updates
NexGate's messaging platform has been refined with five additional decisions:
Follow System: Users are followed with [@Follow], shops are followed with [Follow Shop]. The $ sigil already signals commercial relationship. Shops have followers but do not follow anyone — like a Facebook Page. "Subscribe" is avoided because it implies payment to EA users.
Group Join Model: Nobody enters a group without choosing to. Two mechanisms: consent DM invitation (admin proactively invites their network — each person accepts or declines) and invite link (self-service joining). Private groups require admin approval on link join. Public groups allow instant join. No WhatsApp-style forced adding.
Shop Chat Initiation: Shops can only initiate chat with customers who have an existing relationship (previous buyer, follower, or active conversation). Transactional messages (order updates) fire automatically. Cold outreach to strangers is blocked at system level. Customer controls what types of shop messages they receive.
Broadcast Channels: Not built. VP Feed already covers all content distribution use cases. Adding channels would duplicate VP Feed and confuse users about where to post content. The inbox stays clean: personal DMs, group chats, and shop commerce DMs only.
Commerce Attach Separation: Custom price offers live in 1:1 DMs only — private negotiation has no place in a group context. Group commerce is Bei ya pamoja — dynamic group buying where price drops as more members join. Each feature has one clear home and one clear purpose.
NexGate Messaging — Product Requirements & Feature Flows v1.0 QBIT SPARK | Rules · Flows · Permissions · Scenarios · Edge Cases