Skip to content

Commit

Permalink
feat: add whatsapp twilio provider with templates (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
LakshayaT authored May 2, 2024
1 parent 78de757 commit a646684
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 11 deletions.
213 changes: 211 additions & 2 deletions apps/api/OsmoX.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"info": {
"_postman_id": "87c05d1d-5ce6-47a5-894a-86c0c520ecc4",
"_postman_id": "c33b0616-f8e2-44f4-b23f-8156d111adb1",
"name": "OsmoX",
"description": "OsmoX API helps creating new notifications, fetching existing notifications as well as perform authorization related tasks.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "27217372"
"_exporter_id": "24502738"
},
"item": [
{
Expand Down Expand Up @@ -971,6 +971,215 @@
],
"description": "Collection of requests pertaining to creating notifications for the Twilio WhatsApp channel type."
},
{
"name": "Twilio WhatsApp (Business) Notifications",
"item": [
{
"name": "Send Twilio WhatsApp (Business) Notification - Success",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response is valid JSON\", function () {",
" pm.response.to.be.json;",
"});",
"pm.test(\"Response has valid 'status' and 'data' properties\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property(\"status\", \"success\");",
" pm.expect(jsonData).to.have.property(\"data\").to.be.an(\"object\");",
"});",
"pm.test(\"Response contains a valid 'notification' object\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.data).to.have.property(\"notification\").to.be.an(\"object\");",
"});",
"pm.test(\"Response 'channelType' is 7\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.data.notification).to.have.property(\"channelType\", 7);",
"});",
"pm.test(\"Response 'deliveryStatus' is 1\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.data.notification).to.have.property(\"deliveryStatus\", 1);",
"});",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{apikey}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n // Set your respective providerId\n \"providerId\": 7,\n \"data\": {\n \"contentSid\": \"HXxxxxxxxxxxxxxxxxxxxx\",\n \"from\": \"MGxxxxxxxxxxxxxxxxxxx\",\n \"contentVariables\": {\n \"1\": \"Name\",\n \"2\": \"2\",\n \"3\": \"29-04-2024\",\n \"4\": \"03-05-2024\",\n \"5\": \"Sunday, 05-05-2024\"\n },\n \"to\": \"+91xxxxxxxxxxx\"\n }\n}"
},
"url": {
"raw": "localhost:3000/notifications",
"host": [
"localhost"
],
"port": "3000",
"path": [
"notifications"
]
},
"description": "Allows successfully creating new notification for the Twilio WhatsApp channel type."
},
"response": []
},
{
"name": "Send Twilio WhatsApp (Business) Notification - Invalid Provider ID",
"event": [
{
"listen": "test",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{apikey}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n // Set your respective providerId\n \"providerId\": 77,\n \"data\": {\n \"contentSid\": \"HXxxxxxxxxxxxxxxxxxxxx\",\n \"from\": \"MGxxxxxxxxxxxxxxxxxxx\",\n \"contentVariables\": {\n \"1\": \"Name\",\n \"2\": \"2\",\n \"3\": \"29-04-2024\",\n \"4\": \"03-05-2024\",\n \"5\": \"Sunday, 05-05-2024\"\n },\n \"to\": \"+91xxxxxxxxxxx\"\n }\n}"
},
"url": {
"raw": "localhost:3000/notifications",
"host": [
"localhost"
],
"port": "3000",
"path": [
"notifications"
]
},
"description": "Allows successfully creating new notification for the Twilio WhatsApp channel type."
},
"response": []
},
{
"name": "Send Twilio WhatsApp (Business) Notification - Missing To Field",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"Response is valid JSON\", function () {",
" pm.response.to.be.json;",
"});",
"pm.test(\"Response for Invalid Data (Missing 'to' Value)\", function () {",
" pm.expect(pm.response.code).to.equal(400);",
" pm.expect(pm.response.json().status).to.equal(\"fail\");",
" pm.expect(pm.response.json().data[0]).to.equal(\"to should not be empty\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer {{apikey}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n // Set your respective providerId\n \"providerId\": 7,\n \"data\": {\n \"contentSid\": \"HXxxxxxxxxxxxxxxxxxxxx\",\n \"from\": \"MGxxxxxxxxxxxxxxxxxxx\",\n \"contentVariables\": {\n \"1\": \"Name\",\n \"2\": \"2\",\n \"3\": \"29-04-2024\",\n \"4\": \"03-05-2024\",\n \"5\": \"Sunday, 05-05-2024\"\n }\n }\n}"
},
"url": {
"raw": "localhost:3000/notifications",
"host": [
"localhost"
],
"port": "3000",
"path": [
"notifications"
]
},
"description": "Allows representing failure in creating new notification for the Twilio WhatsApp channel type when missing the `to` field."
},
"response": []
},
{
"name": "Send Twilio WhatsApp (Business) Notification - Invalid API Key",
"event": [
{
"listen": "test",
"script": {
"exec": [
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Authorization",
"value": "Bearer bad-api-key",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n // Set your respective providerId\n \"providerId\": 7,\n \"data\": {\n \"contentSid\": \"HXxxxxxxxxxxxxxxxxxxxx\",\n \"from\": \"MGxxxxxxxxxxxxxxxxxxx\",\n \"contentVariables\": {\n \"1\": \"Name\",\n \"2\": \"2\",\n \"3\": \"29-04-2024\",\n \"4\": \"03-05-2024\",\n \"5\": \"Sunday, 05-05-2024\"\n },\n \"to\": \"+91xxxxxxxxxxx\"\n }\n}"
},
"url": {
"raw": "localhost:3000/notifications",
"host": [
"localhost"
],
"port": "3000",
"path": [
"notifications"
]
},
"description": "Allows representing failure in creating new notification for the Twilio WhatsApp channel type when passing invalid API key."
},
"response": []
}
],
"description": "Collection of requests pertaining to creating notifications for the Twilio WhatsApp channel type using templates."
},
{
"name": "Twilio SMS Notifications",
"item": [
Expand Down
53 changes: 53 additions & 0 deletions apps/api/docs/channels/wa-Twilio-Business.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## Twilio WhatsApp (Business)

Using the WhatsApp Business Platform with Twilio, you can send and receive messages to WhatsApp users using the same Twilio Messaging APIs you already know and enjoy. Dive into the Twilio SDKs and helper libraries, see the quickstart and API reference docs, read through guides on templates and Twilio phone numbers, and find the sample code you'll need.

This particular provider can be used to send WhatsApp messages generated using Content Template Builder.

### Values to Update in Database

When using Twilio to send WhatsApp messages via their API/Client, you need to provide certain variables that hold the Twilio configuration details. Here are the values you need to update in table `notify_providers`:

Create a new entry in table `notify_providers` and set the fields - `name`, `application_id`, `user_id`

- Set field `channel_type` = 7 (for Twilio WhatsApp (Business))
- Set field `is_enabled` = 1 (to enable the newly created provider)

Then set the following configurations in the `configuration` field:

| Key | Description |
| --------------------- | ---------------------- |
| TWILIO_WA_ACCOUNT_SID | Twilio SMS account SID |
| TWILIO_WA_AUTH_TOKEN | Twilio SMS auth token |

```jsonc
// Sample json to set in configuration field
{
"TWILIO_WA_ACCOUNT_SID": "ACXXXXXXXXXXXXXXXXX",
"TWILIO_WA_AUTH_TOKEN": "someauthtoken",
}
```

### Sample Request Body

Here's a sample request body:

```jsonc
{
// Set your respective providerId. channelType associated with providerId should be 7 (Twilio WhatsApp (Business))
"providerId": 7,
"data": {
"contentSid": "HXXXXXXXXX",
"from": "MGXXXXXXXX",
"contentVariables": {
"1": "Name",
"2": "52", // Numbers should also be strings
},
"to": "+919004812051",
},
}
```

References

- [Twilio - Send templates created with the content template builder](https://www.twilio.com/docs/content/send-templates-created-with-the-content-template-builder)
18 changes: 10 additions & 8 deletions apps/api/docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Welcome to the usage guide for OsmoX, a powerful notification management system
- [1. Overview](#1-overview)
- [2. Pushing Data to the Database](#2-pushing-data-to-the-database)
- [3. Using the OsmoX API](#3-using-the-osmox-api)
- [Authorization Header](#authorization-header)
- [4. Tracking Notification Status](#4-tracking-notification-status)
- [5. Available Channel Types](#5-available-channel-types)
- [6. Delivery Status Information](#6-delivery-status-information)
Expand Down Expand Up @@ -93,14 +94,15 @@ OsmoX updates the `delivery_status` and `result` columns to provide information

OsmoX supports multiple channel types, allowing you to choose the most suitable one for your notifications. Currently, the available channel types are:

| **Channel Type** | **Value** | **Document** |
| :----------------------------------: | :-------: | :----------------------------------------------: |
| SMTP - Simple Mail Transfer Protocol | 1 | [SMTP](channels/smtp.md) |
| Mailgun | 2 | [Mailgun](channels/mailgun.md) |
| WhatsApp - 360Dialog | 3 | [WhatsApp - 360Dialog](channels/wa-360Dialog.md) |
| WhatsApp - Twilio | 4 | [WhatsApp - Twilio](channels/wa-Twilio.md) |
| SMS - Twilio | 5 | [SMS - Twilio](channels/sms-Twilio.md) |
| SMS - Plivo | 6 | [SMS - Plivo](channels/sms-Plivo.md) |
| **Channel Type** | **Value** | **Document** |
| :----------------------------------: | :-------: | :------------------------------------------------------------: |
| SMTP - Simple Mail Transfer Protocol | 1 | [SMTP](channels/smtp.md) |
| Mailgun | 2 | [Mailgun](channels/mailgun.md) |
| WhatsApp - 360Dialog | 3 | [WhatsApp - 360Dialog](channels/wa-360Dialog.md) |
| WhatsApp - Twilio | 4 | [WhatsApp - Twilio](channels/wa-Twilio.md) |
| SMS - Twilio | 5 | [SMS - Twilio](channels/sms-Twilio.md) |
| SMS - Plivo | 6 | [SMS - Plivo](channels/sms-Plivo.md) |
| WhatsApp - Twilio (Business) | 7 | [WhatsApp - Twilio (Business)](channels/wa-Twilio-Business.md) |

## 6. Delivery Status Information

Expand Down
1 change: 1 addition & 0 deletions apps/api/src/common/constants/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export const ChannelType = {
WA_TWILIO: 4,
SMS_TWILIO: 5,
SMS_PLIVO: 6,
WA_TWILIO_BUSINESS: 7,
};
7 changes: 7 additions & 0 deletions apps/api/src/common/decorators/is-data-valid.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { CreateNotificationDto } from 'src/modules/notifications/dtos/create-not
import { WaTwilioDataDto } from 'src/modules/notifications/dtos/providers/waTwilio-data.dto';
import { SmsTwilioDataDto } from 'src/modules/notifications/dtos/providers/smsTwilio-data.dto';
import { SmsPlivoDataDto } from 'src/modules/notifications/dtos/providers/plivo-data.dto';
import { WaTwilioBusinessDataDto } from 'src/modules/notifications/dtos/providers/waTwilioBusiness-data.dto';
import { Injectable } from '@nestjs/common';
import { ProvidersService } from 'src/modules/providers/providers.service';

Expand Down Expand Up @@ -81,6 +82,12 @@ export class IsDataValidConstraint implements ValidatorConstraintInterface {
await validateAndThrowError(smsPlivoData);
return true;

case ChannelType.WA_TWILIO_BUSINESS:
const waTwilioBusinessData = new WaTwilioBusinessDataDto();
Object.assign(waTwilioBusinessData, value);
await validateAndThrowError(waTwilioBusinessData);
return true;

default:
return false;
}
Expand Down
12 changes: 11 additions & 1 deletion apps/api/src/database/migrations/1692870736646-seeddata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class SeedData1692870736646 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`INSERT INTO notify_users (username,password,role) VALUES (?, ?, ?)`, [
'Admin',
// Admin123
'$2b$10$iUUsgPtfqu./C2fnnb80EOlNxc3q73woJd2.Ns0D66xHh0iX4E1vq',
1,
]);
Expand Down Expand Up @@ -75,6 +76,15 @@ export class SeedData1692870736646 implements MigrationInterface {
],
);

await queryRunner.query(
`INSERT INTO notify_master_providers (name,provider_type,configuration) VALUES (?, ?, ?)`,
[
'WA_TWILIO_BUSINESS',
3,
'{"TWILIO_WA_ACCOUNT_SID":{"label":"TWILIO WA ACCOUNT SID","id":"TWILIO_WA_ACCOUNT_SID","pattern":"^AC\\w{3,}$","type":"string"},"TWILIO_WA_AUTH_TOKEN":{"label":"TWILIO WA AUTH TOKEN","id":"TWILIO_WA_AUTH_TOKEN","pattern":"^[a-zA-Z0-9-_]{16,512}$","type":"string"}}',
],
);

// Add foreign key for channel_type -> master_id
// Adding this after seeding data in master_providers so FK constraint does not fail
await queryRunner.createForeignKey(
Expand All @@ -92,7 +102,7 @@ export class SeedData1692870736646 implements MigrationInterface {
await queryRunner.dropForeignKey('notify_notifications', 'channel_type');
await queryRunner.query(`
DELETE FROM notify_master_providers
WHERE id IN (1, 2, 3, 4, 5, 6);
WHERE id IN (1, 2, 3, 4, 5, 6, 7);
`);
await queryRunner.query(`
DELETE FROM notify_server_api_keys
Expand Down
Loading

0 comments on commit a646684

Please sign in to comment.