Real-time Chat Blog

Read Receipts Pattern for Real-time Chat Apps

3 min readAdam Bavosa on Nov 11, 2016
Try PubNub Today

Free up to 1MM monthly messages. No credit card required.

Subscribe to our newsletter

By submitting this form, you are agreeing to our Terms and Conditions and Privacy Policy.

Good News! We've launched an all new Chat Resource Center. We recommend checking out our new Chat Resource Center, which includes overviews, tutorials, and design patterns for building and deploying m...

Waving hand

Good News! We’ve launched an all new Chat Resource Center.

We recommend checking out our new Chat Resource Center, which includes overviews, tutorials, and design patterns for building and deploying mobile and web chat.

Take me to the Chat Resource Center →

Read receipts signify when a message has been opened and read by another user. Though not a fundamental feature of chat apps, and feared by millennials, the functionality can be extended to any messaging application where proof of delivery is critical.

In this tutorial, we’ll walk you through building a simple 1:1 chat app that uses a basic read receipts pattern with HTML, JavaScript, and PubNub. You can use this same pattern in your own chat app with any of our 75+ SDKs.

Real-time Chat with Read Receipts

real-time read receipt pattern for chat apps

The main components needed are:

  • A unique ID on every chat message that is published.
  • An event that fires on the client machine when a user has read a message.
  • A designated Read Receipts channel in your PubNub connection.

When the client read event fires, we publish a Read Receipt to the Read Receipts channel (“message-receipts”) that contains the ID of the chat message that the user last read, and the user’s ID. This event fires in the sample by hitting the button labeled “I’ve read all the messages.”

The lastSeen property in the Read Receipts holds the most recently read message’s ID. If you want to include the time that the message was read, like in Apple’s iMessages or Facebook Messenger, you can include a time property in your Read Receipts.

In order to update the UI in real time, subscribe to the Read Receipt channel, and move the Last Read text when an update arrives. When the chat is first opened, use PubNub Storage & Playback and ask for the most recent chat messages and Read Receipt.

You may want to implement a restriction that allows only 1 read receipt to be sent per user, per message. Make sure Storage & Playback is turned on in your PubNub Admin Dashboard if you want to use History.

And that’s it! Check out the sample below:

<!DOCTYPE html>
<html>
    <body>
        <div class="readReceiptDemo">
            <input id="readMessages" type="submit" value="I've read all the messages" />
            <br />
            <input id="msgText" type="text" />
            <input id="sendMsg" type="submit" />
            <div id="messagePanel"></div>
        </div>
    </body>
    <style>
        .readReceiptDemo {
            font-size: 16px;
            line-height: 2em;
        }
    </style>
    <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.0.11.js"></script>
    <script>
        var pubnub, myLastMessage, theirLastMessage;
        var userId = PubNub.generateUUID();
        bindUiEvents();
        initPubNub();
        function initPubNub() {
            pubnub = new PubNub({
                "publishKey": "pub-key",
                "subscribeKey": "sub-key",
                "uuid": userId
            });
            //unsubscribe as user leaves the page
            window.onbeforeunload = function () {
                pubnub.unsubscribeAll();
            }; 
            pubnub.hereNow({
                "channels": ["message"]
            }, function (status, response)
            {
                //only pub/sub if there are 2 users present.
                if (response.totalOccupancy > 1) return;
                pubnub.subscribe({
                    "channels": ["message", "message-receipts"],
                    "withPresence": true
                });
                var pnMessageHandlers = {
                    "message": writeMessageHtml,
                    "message-receipts": readMessageHtml
                };
                pubnub.addListener({
                    "message": function (m) {
                        pnMessageHandlers[m.subscribedChannel](m.message);
                    }
                });
                //write messages from the last 5 minutes to the screen
                pubnub.history({
                  "channel": "message",
                  "end": new Date().getTime() * 10000 - 3000000000
                },
                function (status, response) {
                    if (!response.messages) return;
                    response.messages.forEach(function(messageObject) {
                      writeMessageHtml(messageObject.entry);
                    });
                });
            });
        }
        function bindUiEvents () {
            var textbox = document.getElementById("msgText");
            var sendButton = document.getElementById("sendMsg");
            var readButton = document.getElementById("readMessages");
            sendButton.addEventListener("click", function () {
                publishMessage(textbox.value);
                textbox.value = "";
            });
            readButton.addEventListener("click", function () {
                onMessageRead(theirLastMessage, userId);
            });
        }
        function writeMessageHtml (message) {
            //create message element and add it to the page
            var panel = document.getElementById("messagePanel");
            var newMsg = document.createElement("div");
            var who = message.user === userId ? "Me" : "Them";
            who === "Them" ? theirLastMessage = message.id : null;
            newMsg.textContent = who + ": " + message.content;
            newMsg.id = message.id;
            panel.appendChild(newMsg);
        }
        function readMessageHtml (message) {
            //remove existing read UI
            var read = document.getElementById("readNotification");
            read && read.id !== message.lastSeen && message.userId !== userId ? read.remove() : null;
            //update read UI
            read = document.createElement("span");
            read.id = "readNotification";
            read.innerHTML = " ** Last Read **";
            var messageElement = document.getElementById(myLastMessage);
            messageElement && message.userId !== userId ? messageElement.appendChild(read) : null;
        }
        function publishMessage (message) {
            myLastMessage = PubNub.generateUUID();
            pubnub.publish({
                "channel": "message",
                "message": {
                    "content": message,
                    "id": myLastMessage,
                    "user": userId
                }
            });
        }
        function onMessageRead (messageId, userId) {
            pubnub.publish({
                "channel" : "message-receipts",
                "message" : {
                    "lastSeen": messageId,
                    "userId": userId
                }
            });
        }
    </script>
</html>

More from PubNub

Can Empathy Exist in the Metaverse
News May 16, 20221 min read

Can Empathy Exist in the Metaverse

A roundtable discussion led by PubNub’s COO, Casey Clegg, exploring the topics of what it means to be human in a virtual world.

PubNub Staff

PubNub Staff

How to Advance Telehealth and Virtual Care Technologies
News May 2, 20221 min read

How to Advance Telehealth and Virtual Care Technologies

Dr. Joe Kvedar, Chair of the Board for the American Telemedicine Association, joins our COO, Casey Clegg, to discuss why...

PubNub Staff

PubNub Staff

Another Step Towards Data Security: ISO-27001 Implementation
BuildMay 2, 20221 min read

Another Step Towards Data Security: ISO-27001 Implementation

Today, we are glad to announce that we are currently in the process of implementing ISO-27001 security standards.

PubNub Staff

PubNub Staff