Dev (Back & Front)ARTIGO

Como enviar SMS em NodeJS com SNS AWS

No seguinte artigo, o autor apresenta um passo a passo ensinando o procedimento para enviar SMS em NodeJS utilizando o serviço SNS da AWS.

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.

bash
mkdir bot-sendsms && cd bot-sendsms && npm init -f
bash
npm i --save aws-sdk dotenv mongoose interval-promise babel babel-cli
bash
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.

Engenheiro de Software, atuando no desenvolvimento web desde 2005, utilizando as mais variadas tecnologias. Com experiência prática em software as a service, sistemas distribuídos e cloud computing. Empreendedor, consultor, agilista de paixão, atleta amador e autor de artigos sobre programação, agilidade, product management e empreendedorismo em seu blog.

Ver perfil