Mobile App Development for Internal Employee Communication
Corporate messenger — technically more complex than it appears. Real-time messaging, E2E encryption, push when app closed, message history sync across devices, group chats with thousands of participants, read receipts, media exchange. It's not a single CRUD with WebSocket — it's a separate infrastructure.
Transport Layer: WebSocket vs XMPP vs Custom Protocol
Three real options for corporate messenger:
WebSocket + custom protocol. Most common approach. Client maintains persistent WebSocket connection, server pushes events. Downside: you implement reconnect, heartbeat, acknowledgement, message queue on connection loss yourself.
XMPP (Extensible Messaging and Presence Protocol). Mature protocol with rich ecosystem. Servers: Ejabberd, OpenFire, Prosody. Mobile libraries: XMPPFramework (iOS), Smack (Android). XMPP out-of-box provides presence (online status), MUC (group chats), standardized E2E via OMEMO. Downside: harder to customize protocol for non-standard requirements.
Matrix + Element SDK. Open-source, federated, E2E by default via Megolm. Homeserver: Synapse or Dendrite. Client SDKs: matrix-ios-sdk, matrix-android-sdk2. Suitable if you need federation between organizations.
For typical corporate project from scratch — WebSocket + custom backend protocol (NestJS + Redis Pub/Sub + PostgreSQL). Choose XMPP if you need integration with third-party XMPP clients or mature OMEMO implementation.
Real-time Architecture on Client
Key challenge: WebSocket connection must survive app backgrounding. On iOS — connection closes on backgrounding (within ~30 seconds). On Android — killed aggressively by doze and battery optimization.
For iOS: hold URLSessionWebSocketTask at AppDelegate level, but foreground-reconnect via applicationWillEnterForeground. Push via APNs with content-available: 1 for background wake-up — not WebSocket, but lets you receive new message and show notification via UNNotificationServiceExtension.
On Android: WebSocket in Service (foreground service with persistent notification) — only reliable way to keep connection alive across all manufacturers. On some Android OEM (Xiaomi, Huawei, OPPO) foreground service gets killed by system even with autostart enabled. Solution: Firebase FCM as fallback for guaranteed delivery + own WebSocket for real-time.
class MessagingService : Service() {
private lateinit var webSocketClient: OkHttpClient
private var webSocket: WebSocket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(NOTIFICATION_ID, buildNotification())
connectWebSocket()
return START_STICKY // Restart on system kill
}
private fun connectWebSocket() {
val request = Request.Builder()
.url("wss://api.company.com/ws")
.header("Authorization", "Bearer ${tokenManager.token}")
.build()
webSocket = webSocketClient.newWebSocket(request, object : WebSocketListener() {
override fun onMessage(webSocket: WebSocket, text: String) {
val message = Json.decodeFromString<IncomingMessage>(text)
messageRepository.save(message)
notificationManager.showIfNeeded(message)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
scheduleReconnect(delay = 5_000L)
}
})
}
}
E2E Encryption
Signal Protocol — de facto standard for E2E in messengers. Used in Signal, WhatsApp, Skype. For mobile: libsignal-protocol-java (Android), SignalProtocolKit (iOS).
Protocol based on Double Ratchet Algorithm: each message encrypted with new key, compromising one key doesn't reveal others (forward secrecy).
// Encrypt outgoing message
val sessionCipher = SessionCipher(signalStore, recipientAddress)
val encryptedMessage = sessionCipher.encrypt(plaintext.toByteArray())
val payload = Base64.encodeToString(encryptedMessage.serialize(), Base64.NO_WRAP)
For group chats — Sender Key Distribution: one key encrypts for entire group, much more efficient than encrypting per participant.
Critical: keys stored only on device. If user uninstalls or switches device — message history inaccessible. This is trade-off you need to honestly warn customer about. Solution — optional key backup via iCloud Keychain / Google Drive with user permission.
Storage and History Synchronization
Local database — Room (Android) / Core Data (iOS). Messages stored with id, conversationId, senderId, encryptedContent, timestamp, status (sent / delivered / read).
Sync on reinstall: if E2E enabled without key backup — history inaccessible. If backup exists or E2E optional — request history from server paginated (cursor-based pagination).
Delivery and read statuses — separate mechanism. Read receipt: recipient sends ack with messageId via WebSocket. Sender updates status in local database. Offline — read receipts accumulate and send on reconnection as batch.
Media and Files
Media upload — via separate HTTP endpoint, not WebSocket. For images: upload thumbnail first (JPEG, 200px, quality 40), then on demand — original. Video: HLS streaming or progressive download via AVPlayer / ExoPlayer.
For large files — chunked upload with resume capability (S3 Multipart Upload). Progress indicator via okhttp3.MultipartBody with custom RequestBody that reports progress.
Media cached locally via Glide (Android) / Kingfisher (iOS) with cache size limit. Old media deleted by LRU policy.
Push Notifications and Badges
Push when closed — via FCM (Android) / APNs (iOS). On iOS UNNotificationServiceExtension lets you decrypt notification content before display (for E2E messages) and update badge.
For Android — FCM high-priority message with content-available. On Android 13+ need explicit POST_NOTIFICATIONS permission — without it push won't arrive.
Unread counter (badge) on app icon: on iOS UNUserNotificationCenter.setBadgeCount() (iOS 16+) or via push payload badge. On Android — ShortcutBadger library, since there's no native badge API (each launcher has its own mechanism).
Phases and Timeline
| Phase | Content | Timeline |
|---|---|---|
| Design | Protocol architecture, E2E solution, database | 2–3 weeks |
| Backend | WebSocket server, API, storage | parallel with client |
| iOS + Android | Real-time, UI, media, push | 10–14 weeks |
| E2E | Signal Protocol or OMEMO | +3–4 weeks |
| Testing | Load, real-time, edge cases | 2–3 weeks |
MVP without E2E (basic chats, push, history) — 3–4 months. With E2E and group chats — 5–7 months. Cost estimated individually.







