Skip to content

Commit

Permalink
1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kaushalvivek committed Mar 28, 2021
1 parent 9bfad7f commit 2ce2810
Show file tree
Hide file tree
Showing 22 changed files with 5,766 additions and 2 deletions.
152 changes: 150 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,150 @@
# notification-microservice
A highly scalable microservice to handle WhatsApp, SMS, Email and Firebase notifications.
# Notification Micro-service

![https://img.shields.io/badge/version-v1.0.0-green](https://img.shields.io/badge/version-v1.0.0-green)

## Introduction

This project is a highly scalable micro-service to handle all the notifications (Email/SMS/WhatsApp) that you would require to schedule for contact from your small-business. It uses Amazon's SQS queuing service, SendGrid's email API and Twilio WhatsApp and SMS API to schedule notifications. Support would be added for other service-providers as the project evolves.

## Features

- Simple/Bulk WhatsApp notification through a single API call.
- Simple/Bulk SMS notification through a single API call.
- Simple/Bulk rich text Email through a single API call.
- Rate limiter for streamlining bulk notification requests.

## QuickStart
- [Setup SendGrid](https://sendgrid.com/solutions/email-api/) and generate an API key
- [Setup Twilio](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account), register a number, generate API key and setup SMS and WhatsApp.
- [Setup SQS](https://vivekkaushal.com/node-sqs/)
- [Install Docker](https://docs.docker.com/engine/install/)
- Clone this project
```bash
git clone https://github.com/kaushalvivek/notification-microservice.git
cd notification-microservice
```
- Create environment file
```bash
vim .env
```
Provide all the values as specified in `sample_env.sh`
- Start the micro-service
```
sudo docker-compose up -d
```

## Usage

### Sending Emails
POST request to : `host:PORT/api/v1/notify/email`
Request body:
```json
{
"data":[
{
"content":{
"subject" : "Sample Email's Subject",
"html" : "<p> Sample email's body</p>"
},
"targets":[
"[email protected]",
"[email protected]"
]
}
{
"content":{
"subject" : "Sample Email 2's Subject",
"html" : "<p> Sample email 2's body</p>"
},
"targets":[
"[email protected]"
]
}
]
}

```

### Sending WhatsApp messages
POST request to : `host:PORT/api/v1/notify/whatsapp`
Request body:
```json
{
"data":[
{
"content":{
"body" : "This is a sample message!"
},
"targets":[
{
"countryCode":"+1",
"phone":"123456789"
},
{
"countryCode":"+91",
"phone":"9876543211"
}
]
}
{
"content":{
"body" : "This is another sample message!"
},
"targets":[
{
"countryCode":"+1",
"phone":"123456789"
}
]
}
]
}

```


### Sending SMS
POST request to : `host:PORT/api/v1/notify/sms`
Request body:
```json
{
"data":[
{
"content":{
"body" : "This is a sample message!"
},
"targets":[
{
"countryCode":"+1",
"phone":"123456789"
},
{
"countryCode":"+91",
"phone":"9876543211"
}
]
}
{
"content":{
"body" : "This is another sample message!"
},
"targets":[
{
"countryCode":"+1",
"phone":"123456789"
}
]
}
]
}

```

## Contributing
Feel free to create issues in this repository for contribution. Contributions guidelines would evolve along with the project. Tests for integration would be added soon.

## License

[MIT License](https://raw.githubusercontent.com/kaushalvivek/notification-microservice/main/LICENSE)

Copyright (c) 2021 Vivek Kaushal
22 changes: 22 additions & 0 deletions api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.dockerignore
Dockerfile
.git
*.csv
*.dat
*.iml
*.log
*.out
*.pid
*.seed
*.sublime-*
*.swo
*.swp
*.tgz
*.xml
.DS_Store
.idea
.project
.vscode
npm-debug.log
node_modules
**/.DS_Store
8 changes: 8 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM node:12.13.0-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "package-lock.json*", "./"]
RUN npm install --production
COPY . .
EXPOSE ${PORT}
CMD [ "node", "server.js" ]
15 changes: 15 additions & 0 deletions api/app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const express = require('express')
const app = express()
const BodyParser = require('body-parser')
const notificationRoutes = require('./routes/notification.route')

// parse application/json
app.use(BodyParser.json())

app.use('/api/v1/notify', notificationRoutes)

app.get('/health', (req, res) => {
res.send({ status: 'OK' })
})

module.exports = app
31 changes: 31 additions & 0 deletions api/app/controllers/notification.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const notificationService = require('../services/notification.service')

exports.email = async function (req, res) {
const data = req.body.data
try {
await notificationService.email(data)
return res.status(201).json({ status: 201, message: 'Email queued successfully.' })
} catch (e) {
return res.status(400).json({ status: 400, message: e.message })
}
}

exports.whatsapp = async function (req, res) {
const data = req.body.data
try {
await notificationService.whatsapp(data)
return res.status(201).json({ status: 201, message: 'whatsapp message queued successfully.' })
} catch (e) {
return res.status(400).json({ status: 400, message: e.message })
}
}

exports.sms = async function (req, res) {
const data = req.body.data
try {
await notificationService.sms(data)
return res.status(201).json({ status: 201, message: 'SMS queued successfully.' })
} catch (e) {
return res.status(400).json({ status: 400, message: e.message })
}
}
9 changes: 9 additions & 0 deletions api/app/routes/notification.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const express = require('express')
const router = express.Router()
const notificationController = require('../controllers/notification.controller')

router.post('/email', notificationController.email)
router.post('/whatsapp', notificationController.whatsapp)
router.post('/sms', notificationController.sms)

module.exports = router
36 changes: 36 additions & 0 deletions api/app/services/helper.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const aws = require('aws-sdk')
const uuid = require('uuid')
const splitArray = require('split-array')
const config = require('../../config')

const sqsConfig = {
apiVersion: config.aws.apiVersion,
accessKeyId: config.aws.accessKeyId,
secretAccessKey: config.aws.secretAccessKey,
region: config.aws.region
}
aws.config.update(sqsConfig)

const sqs = new aws.SQS({ apiVersion: config.aws.apiVersion })

exports.queueMessages = async function (messages, queueUrl) {
try {
const splittedArray = splitArray(messages, 10)
for (const arr of splittedArray) {
const params = {
QueueUrl: queueUrl,
Entries: []
}
arr.forEach(message => {
params.Entries.push({
Id: uuid.v4(),
MessageBody: JSON.stringify(message)
})
})
await sqs.sendMessageBatch(params).promise()
};
return (201)
} catch (e) {
throw new Error(e.message)
}
}
67 changes: 67 additions & 0 deletions api/app/services/notification.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

const config = require('../../config')
const helper = require('./helper.service')

exports.email = async function (data) {
try {
const messages = []
data.forEach(item => {
if (!(item.content && item.targets && item.content.html && item.content.subject)) {
throw new Error('Invalid request format, check API documentation.')
};
messages.push(item)
})
console.log(JSON.stringify(messages))
await helper.queueMessages(messages, config.aws.queueUrls.email)
} catch (e) {
throw new Error(e.message)
}
}

exports.whatsapp = async function (data) {
try {
const messages = []
data.forEach(item => {
if (!(item.content && item.targets && item.content.body)) {
throw new Error('Invalid request format, check API documentation.')
};
item.targets.forEach(target => {
if (!(target.countryCode && target.phone)) {
throw new Error('Invalid request format, check API documentation.')
}
if (isNaN(target.phone) || isNaN(target.countryCode.substring(1))) {
throw new Error(`Invalid phone number in ${target}`)
}
})
messages.push(item)
})
console.log(JSON.stringify(messages))
await helper.queueMessages(messages, config.aws.queueUrls.whatsapp)
} catch (e) {
throw new Error(e.message)
}
}

exports.sms = async function (data) {
try {
const messages = []
data.forEach(item => {
if (!(item.content && item.targets && item.content.body)) {
throw new Error('Invalid request format, check API documentation.')
};
item.targets.forEach(target => {
if (!(target.countryCode && target.phone)) {
throw new Error('Invalid request format, check API documentation.')
}
if (isNaN(target.phone) || isNaN(target.countryCode.substring(1))) {
throw new Error('Invalid phone number or country code')
}
})
messages.push(item)
})
console.log(JSON.stringify(messages))
await helper.queueMessages(messages, config.aws.queueUrls.sms)
} catch (e) {
throw new Error(e.message)
}
}
16 changes: 16 additions & 0 deletions api/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require('dotenv').config()

exports.aws = {
region: process.env.AWS_REGION,
apiVersion: process.env.AWS_API_VERSION,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
queueUrls: {
sms: process.env.SMS_QUEUE_URL,
firebase: process.env.FIREBASE_QUEUE_URL,
whatsapp: process.env.WHATSAPP_QUEUE_URL,
email: process.env.EMAIL_QUEUE_URL
}
}

exports.port = process.env.PORT
Loading

0 comments on commit 2ce2810

Please sign in to comment.