owned this note
owned this note
Published
Linked with GitHub
> Imagine having a whole abstraction capable of sending email, SMS, or notification to all your users in one place .
>
> Prerequisite : Knowledge of either Javascript, (ExpressJs or Sails Js) and NodeJs .
Abstraction, What does it mean?
[Abstraction](https://en.wikipedia.org/wiki/Abstraction_(computer_science)) according to computing is the process of generalising or hiding concrete details of a functionality to focus attention on more information of greater importance. It can also be explained as hiding away from the low-level functionality of a feature and exposing it via function or API so that it can be consumed.
In programming, whenever you find yourself repeating the same code or logic, it's a good idea to create an abstraction. This is called the "Do not repeat yourself" rule (DRY). This means you take the detailed parts of the code and hide them, making it easier to use and understand. For example, instead of writing the same email-sending code in multiple places, you can create a function that handles it. This way, you only need to call the function whenever you need to send an email. This makes your code cleaner and easier to maintain. repeating logic in multiple places in your codebase, you should consider abstracting the implementation details into functions.
## Firebase Cloud Message Feature
As a case study, a while back at Cudium, I needed to integrate notification into our application so that when a user makes a transaction, they are properly notified about the deposit or withdrawal made on their wallet via email and also via a notification so I decided to integrate with firebase after a few days of research.
One thing stood out during my research, I needed to support two things :
1. Cross-platform notification - Android, Web and iOS. All of which Firebase Cloud Messaging (FCM) support
2. Support unicast, broadcast and also topic base subscription
Since all the libraries I use at work are built on top of [Express JS](https://expressjs.com/), they help avoid bootstrapping Express applications from scratch, Sails JS, I mean, which is another form of abstraction. I decided to write a helper file to support this. Think of it as a utility library function, but in Sails JS, it goes beyond that. Let's take a look at it and develop an interesting approach that will help us create a 3-in-1 abstraction for centralized notifications later.
> While the idea is language agnostic , I will be using Javascript to explain.
Let's start with setting up the configuration for the Firebase cloud message which allows us to import all necessary constants
![temp-1](https://hackmd.io/_uploads/SkRuhS4H0.jpg)
> *All configuration specific to firebase are extracted from the docs at*[*https://firebase.google.com/docs/cloud-messaging*](https://firebase.google.com/docs/cloud-messaging). Ensure you have firebase admin library installed
Extracting the configuration is not the only thing needed to set up a Firebase cloud message notification on the backend, you also need to initialise the Firebase itself to be able to use it like so
![temp-2](https://hackmd.io/_uploads/rJmTnrNSR.jpg)
Moving forward, since working with Sails JS requires using pure JavaScript and not TypeScript, I believe TypeScript is not necessary for every project, depending on your team's goals. Sails allows us to specify the type of data we expect to be passed into our functions or controllers, which makes using TypeScript redundant. The way arguments are specified to function in Sails Js is via property specification in this case for us to be able to set Firebase notification as a helper/util library we need to specify some expectation for the argument that the Firebase cloud message library expects to properly send notification across supported platforms such as device token, data, title, body, priority, time to live, topic, mutableContent, contentAvailable e.t.c. To do this, we need to specify the arguments in a table, which are expressed as a JavaScript object following Sail's JS conventions. You can see how they are defined [here](https://sailsjs.com/documentation/concepts/helpers#?inputs).
| **Input** | **Type** | **Description** | **Defaults To** | **Example** |
| --- | --- | --- | --- | --- |
| `fcmDeviceTokens` | JSON | An array of unique FCM notification tokens for each device | | `['<token_1>']` |
| `data` | JSON | Data to send to the client for the notification | {} | `{"account": "savings", "amount": 900.0}` |
| `title` | string | Push notification title on delivery | `""` | `Debit Alert` |
| `body` | string | Push notification message body on delivery | `""` | `msg: A deposit has been made to your account` |
| `contentAvailable` | boolean | Used when sending messages to iOS | `true` | |
| `mutableContent` | boolean | This applies only on iOS to allow the device to mutate the content before the presentation | `true` | |
| `priority` | string | The delivery priority value for the target audience (allowed values: `high`, `normal`) | `PRIORITY` | |
| `timeToLive` | number | Time in seconds to hold the message if the device is offline | `TIME_TO_LIVE` | |
| `topic` | string | Topic to broadcast to | `""` | |
> Side note: In order to be able to send notification to users, I needed to map every user to assigned device token as shown in the code snippet below but since I am only focused on explaining how to bundle sms, fcm and email message as a unit library, I won't be talking about the mapping user to ensure unique messaging even when users change their devices but this snippet below gives a rough idea of what it is.
![11-temp](https://hackmd.io/_uploads/HkcWU8VBR.jpg)
Now back from our little detour, I will share the code snippet for the FCM notification and then explain what it does. Let's go.
![Snap (2)](https://hackmd.io/_uploads/HJh0aS4BA.jpg)
With the parameters specified above, designing a helper function to abstract the implementation details of all things notification was easy.
First, the implementation starts with a guard clause to ensure all required parameters are provided. If any of `topic`, `fcmDeviceTokens`, `data`, `title`, or `body` are missing, the function returns `false` immediately. Then the `payload` , an object was built including the data and notification content (title and body). Default message options (`defaultMessageOpts`) such as `ttl` (time-to-live) and `priority` are set using the provided values or default constants (`TIME_TO_LIVE` and `PRIORITY`). This is what is passed over to the Firebase notification SDK.
Secondly, the main focus was on figuring out whether the notification was of one of the three modes listed below :
* **Unicast**: Sending a notification to a single device.
* **Multicast**: Sending notifications to multiple devices.
* **Broadcast to Topic**: Sending notifications to all devices subscribed to a specific topic.
To determine this, the following conditions were used :
* **Unicast**: If there's only one device token and no broadcasting topic.
* **Multicast**: If there are multiple device tokens and no broadcasting topic.
* **Broadcast to Topic**: If a topic is provided along with `mutableContent` and `contentAvailable`.
Since sending notifications can fail, I wanted to ensure we retry sending a notification even after a failure. To achieve this, a loop was used to implement a retry mechanism with a backoff strategy, attempting to send the notification up to `MAX_NOTIFICATION_BACKOFF` times if not successful.
For unicast mode, a notification is sent to a single device token. If the response is successful, `notified` is set to true; otherwise, a retry attempt message is logged. For multicast mode, notifications are sent to all device tokens using `sendEachForMulticast`. The number of successfully sent notifications is logged, and if not all are successful, the failed tokens are identified. The list is compacted to remove null values, and retries are made for the failed tokens. For broadcasting to a topic, notifications are sent to devices registered to the specified topic. If the response contains `messageId`, it is marked as successful and `notified` is set to true; otherwise, a retry attempt message is logged.
With this, we have the Firebase utility ready to support all three modes of notification. Now, let's set up the email side of things.
## Email Notification Feature
Many applications need an email support feature, and you can integrate any email service based on your software requirements. For this article, we chose to integrate with Zoho Mail, even though the previous integration was with SendGrid. Therefore, I also had to ensure backward compatibility.
![Snap (3)](https://hackmd.io/_uploads/HJk51UEBC.jpg)
By listing all the supported email providers in the code above, the application ensures reliable email delivery by using multiple transporters: Zoho Mail and SendGrid. The email payload, combined with a flexible template configuration, allows the application to easily integrate and send well-formatted emails.
Let's quickly move to SMS notifications before discussing the centralized notification code.
## Sms Notification Feature
To send notifications, we configure an Axios instance to make HTTP POST requests to the SMS provider. This ensures delivery to users whenever a pin or token is needed, as shown in the code snippet below.
![temp-4](https://hackmd.io/_uploads/SkiieL4SC.jpg)
So with these three utility functions, let's create a centralized notification function that can send notifications via the three channels depending on varying configurations.
## Centralized Notification
To centralize SMS, FCM, and Email notifications into a single utility function, we made some basic assumptions. We need to specify the type of notification to send and the necessary payload. This allows us to create a generalized function that builds on the three notification features discussed earlier.
![image](https://drive.google.com/file/d/1oj6NGkHGYyKjRk9HkjTFQ45cI_3Nj-oU/view?usp=sharing)
The centralized notification library in the snippet above is designed to send emails, SMS, and push notifications to users. It starts by setting up constants for different notification types and allowed notifications. The required inputs include template options, data for the templates, the type of notification to send, and the recipient's user ID.
When the function runs, it first gets the user's email and first name using the provided user ID. For email notifications, if the notification type is 'ALL' or 'EMAIL', it creates the email using the given template and data, sends it, and logs the result. Similarly, for SMS notifications, it checks if the type is 'ALL' or 'SMS', and sends an SMS with the provided data.
For push notifications, if the type is 'ALL' or 'PUSH', it retrieves the appropriate template and data, maps the user's FCM device tokens, and constructs the push notification message. The message is then sent.
The library ensures each notification type is only attempted if the required data and templates are provided, and logs errors if any data is missing. It uses necessary helper functions to handle the sending process, ensuring a robust and flexible notification system. This approach guarantees that notifications are reliably sent through multiple channels, enhancing user communication.
While this may seem like a lengthy process for sending notifications, emails, or SMS to users, it ensures that each component can work independently and also together.
One example of how this can be applied is during the KYC process for a new user, as shown in the snippet below.
![Snap (5)](https://hackmd.io/_uploads/rktfW8VHA.jpg)
A similar use case could be for sending out transaction notifications to users after a debit or credit process.
> While I know this article does not real give you the in depth step by step process of writing this whole logic out . My hope is that it gives you an idea of how you can set up all notification related logic in one library and use it at will.
In summary, I hope this article lays in your heart the importance of abstraction using function and how to build better software with it
Resources:
* [https://sailsjs.com](https://sailsjs.com/)
* [https://en.wikipedia.org/wiki/Abstraction\_(computer\_science)](https://en.wikipedia.org/wiki/Abstraction_(computer_science))
* [https://firebase.google.com/docs/cloud-messaging](https://firebase.google.com/docs/cloud-messaging)
> ***I am Caleb and you can reach me at*** [***Linkedin***](https://www.linkedin.com/in/adewole-caleb) ***or follow me on***[***Twitter***](https://x.com/Soundboax)