Simple In-App Notifications Process
Most modern applications require some sort of in-app notifications, notifications that are sent to active users.
If you haven’t built something like this before, you may wonder how to start. Allow me to introduce you to a good solution to start with.
I have to emphasize that all these data fields and the process steps are highly adjustable. You can add, remove, or change anything according to your needs. This solution is a good start if you don’t have one. My advice is: don’t complicate the process if you don’t have specific needs or requirements. This process is simple and more than enough in most cases.
The Process. Including all functions that should be handled
This process consists of 6 parts:
1- Any event occurring in our system that requires sending a notification to the user(s). This is the main part where message data is created in the database and prepared for sending or retrieval. In this part, we categorize the message and calculate its expiration and deletion date.
2- A user opens the application. This is where the system retrieves any unseen messages for the user.
3- A user sees the message. This is where the frontend informs the backend that the current user has seen the message, so the system will stop sending this message again. You can change this part so that the system continues displaying the seen message until it expires.
4- A user acknowledges the message. “Acked” is a shorthand for “Acknowledged”, which means the user not only saw the message but confirmed it by taking action. This action could be a button click. When a user acknowledges the message, the frontend informs the backend about it.
5- User connection is closed. If the user closes the app or the websocket connection is closed for any reason, the frontend will inform the backend so that the system can delete the connection data from the database.
6- Deletion time occurs. A scheduled job of any type has to run in the backend, searching for any messages where the deletion date is now or today. If any are found, all related data must be deleted from the database.
Database/Cache
A database is mandatory, while a cache is optional. A cache is a nice to have data layer, particularly if you have a large number of users and aim to speed the things up.
The cache becomes almost mandatory only when database transactions are significantly high, impacting the overall system performance.
If you have a cache layer, you must first search for data in the cache. If the data is not found in the cache, then you must search in the database. Additionally, remember to keep the cache updated to reflect the current data.
The ERD (Entity Relation Diagram)
A suggested database design for the notifications
Let me explain the reason of some data fields in message catagory table
msg_expire_after:
Messages are not sent or retrieved by users after a specific period of time. It could be seconds or anything else.
For example, a happy hour where there’s a discount or sale for only one hour. It doesn’t make sense to send a notification to the user after the happy hour is over.
msg_delete_after:
Messages are to be deleted from the database after a period of time. It could be days or anything else.
It’s not a good practice to keep most notifications.
For instance, if you’re sending news or updates to the user, this data is usually useless for the business, so we can delete it. In fact, most notification messages could be deleted.
require_ack:
If the message requires the user to click on a button to acknowledge the message. Some messages are important, so we need the user not only to see it but also to click on a button to confirm their understanding of the message content.
Connection information could be saved in the session table. I assume you implement token-based authentication in your system and keep the token of each session saved in the database. In this case, we can use the same session table and simply add the WebSocket connection data.
If we, and I recommend doing so, maintain only one connection per session, we can achieve that by using something like a shared worker in the frontend. By using a shared worker, if the user opens many tabs from your system, all tabs will use only one connection. Without a shared worker, every tab will require a separate connection. In this case, we will need to maintain multiple connections’ data per session, so we will need a new table.
Additionally:
- You can create message templates that have placeholders for data.
- Add a photo or icon to the message.
- You can also add more options for each category. You may consider aggregating messages for a specific category that doesn’t require immediate user attention and send them as a batch every specific period of time.
WebSocket Security
WSS (WebSockets over SSL/TLS)
Clients have to provide an authentication token before starting a connection.
Every client can have up to a maximum number of connections (max_sessions_number).
You can use WebSockets directly or any wrapper library like Socket.IO, or even go further with pub/sub channels.