View : 231

06/05/2026 08:38am

EP.42 Adding Read Receipts Feature in WebSocket Chat

EP.42 Adding Read Receipts Feature in WebSocket Chat

#Chat UX

#Chat Features

#Message Read Status

#WebSocket API

#Real-Time Chat

#Golang

#Go

#WebSocket

#Read Receipts

Why Have Read Receipts in WebSocket Chat?

Read Receipts are an essential feature that helps users understand:

  • Whether the messages they sent have been read by the recipient.
  • Provides senders with confidence that their messages are not being overlooked.
  • Enhances the real-time communication experience.

Examples of applications that use Read Receipts include WhatsApp, Messenger, and LINE which display checkmarks or special icons when messages are read.

Structure of the Read Receipts System in WebSocket Chat

  1. WebSocket Server: Receives and sends "read" status to users in the chat.
  2. Database: Stores the message status (unread/read).
  3. Frontend (Client-Side): Updates the UI when messages are read.

Adding the Read Receipts Feature to the WebSocket Server

1. Upgrade the WebSocket Server to Support Read Receipts

File: websocket_server.go

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "sync"
    "github.com/gorilla/websocket"
)

type ReadReceipt struct {
    MessageID int    `json:"messageID"`
    Reader    string `json:"reader"`
}

type Message struct {
    ID      int    `json:"id"`
    Content string `json:"content"`
    Sender  string `json:"sender"`
    ReadBy  []string `json:"readBy"`
}

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}

var (
    clients   = make(map[*websocket.Conn]bool)
    messages  = make(map[int]*Message)
    broadcast = make(chan ReadReceipt)
    mu        sync.Mutex
)

func handleConnections(w http.ResponseWriter, r *http.Request) {
    conn, _ := upgrader.Upgrade(w, r, nil)
    defer conn.Close()
    clients[conn] = true

    for {
        var receipt ReadReceipt
        err := conn.ReadJSON(&receipt)
        if err != nil {
            delete(clients, conn)
            break
        }
        broadcast <- receipt
    }
}

func handleMessages() {
    for {
        receipt := <-broadcast
        mu.Lock()
        if msg, exists := messages[receipt.MessageID]; exists {
            msg.ReadBy = append(msg.ReadBy, receipt.Reader)
        }
        mu.Unlock()
        
        for client := range clients {
            err := client.WriteJSON(receipt)
            if err != nil {
                client.Close()
                delete(clients, client)
            }
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleConnections)
    go handleMessages()
    fmt.Println("WebSocket Server Running on Port 8080")
    http.ListenAndServe(":8080", nil)
}

2. Adding Read Receipts in the Frontend (Client-Side)

File: client.js

const socket = new WebSocket("ws://localhost:8080/ws");
const messagesContainer = document.getElementById("messages");

socket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.messageID) {
        document.getElementById(`msg-${data.messageID}`).innerText += " ✔ Read";
    }
};

function sendReadReceipt(messageID) {
    socket.send(JSON.stringify({ messageID, reader: "JohnDoe" }));
}

function displayMessage(id, content) {
    const msgElement = document.createElement("p");
    msgElement.id = `msg-${id}`;
    msgElement.innerText = content;
    msgElement.onclick = () => sendReadReceipt(id);
    messagesContainer.appendChild(msgElement);
}

Displaying Read Receipts on the UI

File: index.html

<div id="messages"></div>

3. Testing the System

  1. Run the WebSocket Server

    go run websocket_server.go
  2. Open Multiple Browser Tabs and Send Messages
  3. Click on Messages to Send Read Status

 

Challenge!

Try adding Push Notifications for Read Receipts to notify the sender immediately when their message has been read. This will enhance user engagement and provide instant feedback.


Next EP

In EP.43, we will add a Pinned Messages feature in the WebSocket Chat! 🚀