Skip to main content

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

  1. Document Purpose
  2. User Identity & Discovery
  3. Contact Sync
  4. Inbox Model
  5. Messaging Permissions
  6. Call Permissions
  7. 1:1 Messaging Flows
  8. Group Chat Flows
  9. Commerce DM Flows
  10. Offer Session Flows
  11. Shareable Content
  12. Message Interactions
  13. Voice & Video Call Flows
  14. Group Call Flows
  15. Offline & Notification Flows
  16. Privacy & Safety
  17. Shop Inbox & Staff Access
  18. Notification Settings
  19. Edge Cases & Scenarios
  20. Follow System — Users & Shops
  21. Group Join Model
  22. Shop Chat Initiation
  23. Broadcast Channels — Why Not Needed
  24. 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
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