Desenvolvimento

16 out, 2018

Como enviar SMS em NodeJS com SNS AWS

Publicidade

Hoje eu quero compartilhar com vocês como enviar SMS em NodeJS usando o serviço SNS da AWS. Esta aplicação é um microsserviço e você pode utilizar como um componente do seu software atual. Você só precisa subir a app em algum ambiente Node e adicionar itens na fila de notificações.

No exemplo estou usando uma base MongoDB, mas você pode adaptar o serviço para uma base MySQL ou uma fila em um RabbitMQ ou SQS.

Atualmente é comum utilizarmos e-mail ou SMS para confirmar um cadastro, alterar senha, validar um celular ou até ajudar na validação de acesso.

A Google, Uber, Facebook e tantas outras empresas usam esse tipo de funcionalidade em seus apps. É uma maneira eficiente e rápida de confirmar a autenticidade de um usuário. Neste artigo vou te ajudar a aplicar o mesmo recurso em seu software.

O que você precisa saber

O Amazon Simple Notification Service (SNS) é uma solução para envio de SMS e notificações push, principalmente para envio de mensagens curtas, promocionais, notícias e avisos de atualizações.

Você pode enviar para mais de 200 países e conta com a escalabilidade e flexibilidade que praticamente todo serviço AWS oferece. Porém, este serviço tem custos. Em geral você vai pagar pelo envio da notificação e o tráfego de saída.

No caso do SMS, os preços podem variar também por região e operadora de destino. Consulte a página de pricing para não ter surpresas no fim do mês.

Outro ponto importante são os tipos de conteúdo das mensagens (transacionais e promocionais). Para cada também há um pricing diferente e regras de envio que você precisa considerar na integração. Neste exemplo, vamos enviar mensagens transacionais, porque é um shortcode para validar um cadastro.

Criando a aplicação

Serei direto neste artigo. Em breve este conteúdo estará gravado e disponível no meu canal no YouTube e lá vou explicar com mais detalhes cada etapa. Fica a dica: siga o meu canal para ver esses e outros materiais exclusivos.

Dependências e setup

Vamos criar um diretório para nosso app e adicionar as dependências.

mkdir bot-sendsms && cd bot-sendsms && npm init -f
npm i --save aws-sdk dotenv mongoose interval-promise babel babel-cli
npm i --save-dev babel-preset-env

Agora vamos criar o arquivo babel.rc e adicionar o preset env:

{
  "presets": ["env"]
}

Adicione o arquivo .env para as variáveis de conexão ao MongoDB e as chaves de acesso ao seu perfil na AWS.

DB_MONGO_HOSTNAME = 'your_host_mongodb'
DB_MONGO_USERNAME = 'your_user'
DB_MONGO_PASSWORD = 'your_pass'
DB_MONGO_DATABASE = 'your_db_name'
DB_MONGO_PORT = 00000
AWS_ACCESS_KEY_ID = 'your_key'
AWS_SECRET_ACCESS_KEY = 'your_secret_key'

Acessando o MongoDB

Criaremos a estrutura do model para manipular nossos dados. No seu MongoDB crie uma collection chamada notifications, e depois disso crie o diretório src e adicione o arquivo model.js com o seguinte script:

export default (conn, mongoose) => {
  return conn.model("notifications", new mongoose.Schema({
    to: {
      type: String,
      required: true,
    },
    body: {
      type: String,
      required: true,
    },
    sended: {
      type: Boolean,
      default: false,
      required: true,
    },
    createdAt: { 
      type: Date, 
      default: Date.now, 
    },
    sendedAt: Date,
  }));
};

Agora crie o diretório util na raiz de sua app e adicione o arquivo datasource.js com o seguinte script de conexão:

import dotenv from 'dotenv'
import mongoose from 'mongoose'
import model from '../src/model'

dotenv.config()

const database = process.env.DB_MONGO_DATABASE
const username = process.env.DB_MONGO_USERNAME
const password = process.env.DB_MONGO_PASSWORD
const hostname = process.env.DB_MONGO_HOSTNAME
const port = process.env.DB_MONGO_PORT

let datasource = null;

export default () => {
  if (!datasource) {

    const uri = `mongodb://${hostname}:${port}/${database}`;

    const options = {
      user: username,
      pass: password,
      useNewUrlParser: true,
    };

    const connection = mongoose.createConnection(uri, options);
    connection.Promise = global.Promise;

    const models = [];
    const modelNotifications = model(connection, mongoose);
    
    models[modelNotifications.modelName] = modelNotifications;

    datasource = {
      connection,
      mongoose,
      models,
    };
  }

  return datasource;
};

Consultando e atualizando o MongoDB

Crie o arquivo service.js em src e adicione o script abaixo.

O que fizemos foi criar dois métodos. O getAllToSend retornando a lista de notificações pendentes, limitando a quantidade de itens e ordenando pela data de criação. O updateToSended só atualiza a notificação.

export default (model) => {
  const service = {
    getAllToSend(amount) {
      return new Promise((resolve, reject) => {
        model.find({ sended: false }).sort({ createdAt: 'asc' }).limit(amount)
          .then(items => {
            if (items) { resolve(items) }
            resolve();
          })
          .catch(err => {
            reject(err)
          })
      })
    },

    updateToSended(id) {
      return new Promise((resolve, reject) => {
        model.findById(id)
          .then(notification => {
            const update = notification;

            update.sended = true;
            update.sendedAt = Date.now()

            update.save()

            resolve()
          })
          .catch(err => {
            reject(err)
          })
      })
    }
  }

  return service;
}

Enviando o SMS com a API SNS

Adicione o arquivo sms.js em /src com o script a seguir.

O que estamos fazendo é publicar nosso SMS no Simple Notification Service (SNS). Você precisa definir a região que vai utilizar, instanciar o serviço passando a versão da API e adicionar o atributo DefaultSMSType com o valor “Transactional”.

O conteúdo do SMS é um objeto com os parâmetros PhoneNumber, Message e MessageStructure. Com isso pronto, você chama o método publish.

import AWS from 'aws-sdk'

AWS.config.update({ region: 'us-west-2' })

export default () => {
  const sms = {
    send(notification) {
      return new Promise(async(resolve, reject) => {
        const sns = new AWS.SNS({ apiVersion: '2010-03-31' })

        sns.setSMSAttributes({
          attributes: {
            DefaultSMSType: 'Transactional'
          },
          function(error) {
            if (error) reject(error)
          }
        })

        const params = {
          PhoneNumber: notification.to,
          Message: notification.body,
          MessageStructure: 'string'
        }

        sns.publish(params, (err, data) => {
          if (err) { reject(err) }
          resolve()
        })
      })
    }
  }

  return sms;
}

Unindo todos os eventos

Agora vamos criar o index.js na raiz da app.

Neste arquivo vamos criar um método assíncrono para chamar a função getAllToSend a cada cinco segundos e publicar o SMS na fila do SNS.

import datasource from './util/datasource'
import sms from './src/sms'
import service from './src/service'
import interval from 'interval-promise'

const db = datasource()
const model = db.models.notifications
const controller = service(model)

async function main() {
  try {
    const notifications = await controller.getAllToSend(5);
  
    if (notifications !== undefined && notifications.length > 0) {
      console.log(`Sending ${notifications.length} SMS`)

      for (let index = 0; index < notifications.length; index++) {
        const notification = notifications[index];
        
        await sms().send(notification)
          .then(() => {
            controller.updateToSended(notification.id)
          })
          .catch(err => {
            console.log('Could not send.')
            console.error(err)
          })
      }
    } else {
      console.log('No SMS to send.')
    } 
  } catch (error) {
    console.error(error)
  }
}

interval(async () => {
  await main()
}, 5000)

Você pode alterar o valor passado no método getAllToSend para a quantidade que preferir e também o intervalo de cinco segundos.

No exemplo eu peguei as últimas cinco notificações, percorro o array caso tenha algum item e chamo o evento send(), função criada no src/sms. Caso o envio aconteça com sucesso, atualizo a notificação, caso contrário, apenas exibo no meu terminal. Nesta exception você pode registrar um log ou adaptar ao que fizer mais sentido para seu contexto.

Agora vamos finalizar. Adicione o script para start da sua app no package.json.

"scripts": {
  "start": "babel-node index.js"
},

Pronto!

Agora é só digitar npm start no seu terminal e ver sua app rodar.

Este artigo é um microsserviço para rodar em qualquer ambiente NodeJS. Você precisa apenas adicionar itens na fila de envio e deixar em execução. Embora este script apenas envie uma mensagem transacional, você pode customizar. Confira a documentação na AWS e adapte à sua necessidade.

O script não tem regras de negócio, é apenas um sender. A checagem do código ou a criação da mensagem é responsabilidade da sua aplicação. Você apenas vai se conectar no Mongo e adicionar o item na fila.

Este microsserviço pode evoluir para uma function serverless. Na AWS você tem Lambda Functions, e além disso pode integrar com o SQS e Cloud Watch. Seria necessário fazer algumas adaptações, mas o resultado seria ainda mais eficiente.

Espero ter ajudado! O código fonte deste script está neste repositório. Qualquer dúvida ou sugestão, deixe nos comentários.