Criando um envio de newsletter com AWS SNS e Nodejs

Janaina - Apr 25 '22 - - Dev Community

Será que dá pra criar uma aplicação de envio de newsletter utilizando AWS SNS e Nodejs?

Nesse post irei ajudar vocês a criar um projeto de publicação de newsletter de um blog de receitas 🥘.

O que é newsletter?

É o recebimento de emails recorrentes enviados para uma lista de assinantes, para que você sempre receba as novidades sobre um determinado assunto.

O que é AWS SNS?

SNS(serviço de notificação simples) é um serviço da AWS que você pode criar um TOPICO e toda vez que ele for publicado os serviços que estão subscritos a ele irão receber uma notificação com os dados passados para esse TOPICO. Com isso podemos criar uma esteira de processos. Pensando no processo do nosso newsletter:

Do blog de receitas(publicador/pub/publish) setas para o serviço da Amazon SNS(topico/topic) e setas em direção aos assinantes(subscritos/sub/subscribe)

Na imagem acima podemos ver como ficaria um processo de envio de emails do blog de receitas, no site podemos colocar um botão de se inscrever no newsletter aonde o usuário envia seu email para receber novidades sobre o blog. Toda semana o autor(publish) do blog envia as receitas mais acessadas para as pessoas(subscription) que estão cadastradas no newsletter, quem ficará como intermpedi.

Pré-requisitos

Antes de começar você precisa ter uma conta na AWS, eles possuem serviços gratuitos mas no momento do cadastro eles pedem seu cartão de crédito só para verificação, mas sempre fique atento aos custos dos serviços que você está utilizando para não exceder o limite gratuito.

Você precisa baixar a CLI da AWS e para configurar o login da CLI você pode seguir esse Tutorial CLI da própria AWS para criar um perfil administrador em sua conta na AWS para ter acesso aos serviços AWS no terminal e por fim no terminal você pode executar o Comando aws configure que vai te pedir algumas informações referente ao perfil que você criou na AWS.

Por último tenha instalado na sua máquina o nodejs(estou utilizando a versão 16.14.0) e serverless framework(estou utilizando a versão 3.14.0).

Criando o projeto

Para criar o projeto abra o terminal e execute o comando abaixo:

serverless create --template aws-nodejs --path newsletter
Enter fullscreen mode Exit fullscreen mode

Esse comando cria um projeto serverless utilizando o template da AWS para Nodejs e o —path é o nome da aplicação no meu caso o nome é newsletter.

Abrindo o projeto no seu editor você irá encontrar uma estrutura assim:

  • .gitignore: são arquivos e pastas que não vão ser enviados para o github
  • handler.js: é uma função já criada pelo serverless
  • serverless.yml: aonde fica toda a configuração da AWS

Para esse projeto podemos deletar o arquivo handler.js.Precisamos instalar algumas dependências:

Para rodar nossa aplicação localmente utilizaremos dois pacotes o serverless-offline-sns que cria um ambiente de SNS local para gente testar e o serverless-offline para rodar nossa aplicação offline:

npm i -D serverless-offline-sns serverless-offline
Enter fullscreen mode Exit fullscreen mode

Para utilizar alguns recursos da AWS dentro do nosso código, precisamos instalar uma SDK aws-sdk e para o envio de emails vamos instalar o nodemailer:

npm i aws-sdk nodemailer
Enter fullscreen mode Exit fullscreen mode

Agora vamos estruturar nosso código:

Crie uma pasta utils na raiz do projeto, nessa pasta podem englobar coisa úteis para nossa aplicação, dentro dele crie um arquivo users.json:

{
  "data": ["emailfake@fake.com"]
}
Enter fullscreen mode Exit fullscreen mode

Como não temos um banco de dados com os emails cadastrados fiz esse arquivo para facilitar o envio de emails, você pode colocar sua lista de emails no qual você quer testar o envio.

Ainda dentro da pasta utils vamos criar um arquivo formatMessageEmail.js:

module.exports.formatMessageEmail = (data) => {
  return `
  <html>
  <head>
  </head>
  <body style="font-size: 18px;color: #353535;font-family: monospace;display: flex;align-items: center;justify-content: center;flex-direction: column;">
    <div>
      <h2>Temos novidades essa semana no blog das receitinhas 🥘</h2>

      <p>${data.introduction}</p>

      <ul style="list-style:none;display: flex;flex-flow: wrap;">
        ${data.recipes
          .map((item) => {
            return `
          <li style="margin:10px;padding: 8px;width: max-content;background: #F8B400;height: max-content;border-radius: 4px;box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%);">
          <p>Nome da receita: ${item.name}</p>
          <p>Tempo da receita: ${item.time}</p>
          <p>Quantidade de ingredientes: ${item.ingredients}</p>
          </li>
          `;
          })
          .join("")}
      </ul>
      <footer> <p>Obrigado por nos acompanhar durante a semana e semana que vem tem mais =)</p> </footer>
    </div>
  </body>
</html>
`;
};

Enter fullscreen mode Exit fullscreen mode

Essa função vai fazer a formatação da mensagem que será enviado por email, mais fique a vontade para estilizar do seu jeito 😊.

Vamos criar uma pasta functions na raiz do projeto e essa pasta vai conter as funções que vão ser acionadas. Vamos criar um arquivo publish-newsletter.js:

"use strict";

const AWS = require("aws-sdk");

module.exports.handler = async (event) => {
  try {
    let config = {
      region: process.env.AWS_REGION, //região aonde está localizado seu serviço
    };

    if (process.env.IS_OFFLINE)
      config.endpoint = process.env.SNS_ENDPOINT_LOCAL; //caso for rodar offline ele utilizara esse endpoint

    const sns = new AWS.SNS(config); // inicializa AWS SNS

    const { subject, introduction, recipes } = JSON.parse(event.body); // pegar informações do body da requisição como titulo do email, introdução do email e as receitas

    const params = {
      Message: JSON.stringify({ subject, introduction, recipes }),
      TopicArn: process.env.SNS_ARN,
    };// alguns parametros que serão passado para o topico como a mensagem que será enviada e o arn do TOPICO(um identificador do topico)

    await sns.publish(params).promise(); // publicar no topico essas informacoes

    console.log("PUBLISH_NEWSLETTER");

    return { 
      statusCode: 200,
      body: JSON.stringify({}),
    };
  } catch (error) {
    console.log(error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message }),
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Essa função vai ser acionada partir de uma requisição HTTP do tipo POST. Nessa função fazemos a publicação para um TOPICO com as informações que serão utilizadas por todos que estão inscritos nele, esse TOPICO será configurado no arquivo serverless.yml mais adiante.

Vamos criar mais uma função dentro da pasta functions.Crie um arquivo send-email.js.

"use strict";

const nodemailer = require("nodemailer");

const { formatMessageEmail } = require("../utils/formatMessageEmail");
const emails = require("../utils/users.json");

module.exports.handler = async (event) => {
  try {
    if (event.Records && event.Records.length) { // verifica se tem records dentro do evento
      const data = JSON.parse(event.Records[0].Sns.Message); // pega os dados que enviamos na publicação do TOPICO no arquivo *publish-newsletter.js*

      const transporter = nodemailer.createTransport({ // configuração do envio do email
        host: process.env.MAIL_HOST,
        port: process.env.MAIL_PORT,
        secure: false,
        auth: {
          user: process.env.MAIL_USER,
          pass: process.env.MAIL_PASS,
        },
      });

      const message = formatMessageEmail(data); // formata a mensagem do email

      await transporter.sendMail({ 
        from: '"Receitinhas" <receitinhas@empresa.com>', // de qual email está sendo enviado
        to: emails.data, // emails que cadastramos em utils/users.json
        subject: data.subject, // titulo do email que passamos ao publicar o TOPICO
        html: message, // a mensagem que será entregue no corpo do email
      });

      console.log("SEND_EMAIL");

      return {
        statusCode: 200,
        body: JSON.stringify({}),
      };
    }
  } catch (error) {
    console.log(error);

    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message }),
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

Esse arquivo é um inscrito no nosso TOPICO e nele fazemos o envio da newsletter para os emails cadastrados no utils/users.json.Eu utilizei para configurar o nodemailer o mailtrap ele possui um serviço aonde você pode testar envios de emails, o nodemailer tambem possui uma serviço de teste que você pode encontrar na documentação deles.

Agora vamos ajustar o arquivo serverless.yml:

service: newsletter # nome do servico
frameworkVersion: '3' # versao do serverless

provider:
  name: aws # nome da cloud
  runtime: nodejs12.x # versao do nodejs
  stage: ${opt:stage,'dev'} # estágio
  region: ${opt:region, 'us-east-1'} # regiao do servico
  environment:
    SNS_ENDPOINT_LOCAL: "http://127.0.0.1:4002" # configuraçao de endpoint do serverles-offline-sns
    SNS_TOPIC_SEND_NEWSLETTER: "${self:service}-${self:provider.stage}-sns-send-newsletter" # o nome do topico
    MAIL_HOST: "SEU_HOST" # host do seu provedor de email
    MAIL_PORT: SUA_PORTA # porta do seu provedor de email
    MAIL_USER: "SEU_USUARIO" # usuario do seu provedor de email
    MAIL_PASS: "SUA_SENHA" # senha do seu provedor de email
  iamRoleStatements: # permissões
    - Effect: Allow # aplicado a todos
      Action:
        - SNS:Publish # publicar um topico
      Resource: "*" # em todos os recursos

custom:
  serverless-offline-sns: # configurações para rodar local
    port: 4002
    debug: false
  sns_arn: # configuração para montar o ARN
    send_newsletter:
      local: "arn:aws:sns:us-east-1:123456789012:${self:provider.environment.SNS_TOPIC_SEND_NEWSLETTER}" # localmente
      dev: { "Fn::Join" : ["", ["arn:aws:sns:${self:provider.region}:", { "Ref" : "AWS::AccountId" }, ":${self:provider.environment.SNS_TOPIC_SEND_NEWSLETTER}" ] ]  } # quando subimos para AWS desenvolvimento

resources: # recursos da AWS utilizados
  Resources:
    sendNewsletter: # nome para o recurso
      Type: AWS::SNS::Topic # tipo do serviço 
      Properties:
        TopicName: "${self:provider.environment.SNS_TOPIC_SEND_NEWSLETTER}" # Nome do topico

functions: #lista de funçoes
  send-email: # faz o envio do email
    handler: functions/send-email.handler #local aonde está nosso código que será executado
    events: #qual evento vai acionar nosso código
      - sns: # serviço SNS
          arn: "${self:custom.sns_arn.send_newsletter.${self:provider.stage}}" # ARN do send newsletter criado na parte de resources
          topicName: "${self:provider.environment.SNS_TOPIC_SEND_NEWSLETTER}" # Nome do topico do send newsletter criado na parte de resources

  publish-newsletter: # faz a publicação para o topico send newsletter
    handler: functions/publish-newsletter.handler #local aonde está nosso código que será executado
    events: #qual evento vai acionar nosso código
      - http: #requisição http
          path: publish-newsletter #rota
          method: post #metodo
    environment: # variaveis de ambiente
      SNS_ARN: "${self:custom.sns_arn.send_newsletter.${self:provider.stage}}" # ARN do send newsletter criado na parte de resources

plugins: #lista de plugin que instalamos no inicio do processo, temos que colocar eles aqui tambem
  - serverless-offline
  - serverless-offline-sns
Enter fullscreen mode Exit fullscreen mode

A nossa função publish-newsletter é acionada através de uma requisição HTTP com método do tipo POST e passamos uma variável de ambiente SNS_ARN essa variável vai ser usada para gente publicar no TOPICO sendNewsletter, depois que a gente publica a função send-email é acionada automaticamente por que passamos pra ela um evento sns que está atrelado ao TOPICO sendNewsletter, então toda vez que tiver uma publicação no TOPICO sendNewsletter ela será acionada.

Nesse arquivos podemos configurar tudo relacionado a AWS como: banco de dados, lambdas, SQS e outros serviços. No nosso caso vamos apenas usar a parte de lambda e SNS. Também configuramos tanto a parte para rodar local serverless-offline-sns quanto para subir para AWS com permissões e serviços.

Quando a gente faz um deploy para AWS a própria AWS lê esse nosso arquivo e a partir dessas instruções ela começa a criar e configurar tudo que foi passado nesse arquivo e isso é muito legal 🙂, esse tipo de instrução é chamado de Infraestrutura como código (IaC) uma prática que consiste na estruturação e configuração de recursos de infraestrutura em códigos.

Rodando local

Para rodar local abra seu terminal e dentro da pasta do seu projeto execute o comando abaixo:

sls offline start --stage local
Enter fullscreen mode Exit fullscreen mode

Você pode testar a rota http://localhost:3000/local/publish-newsletter no postman, passando no body esse json:

{
    "subject": " Temos novidades essa semana",
    "introduction": "Nessa semana no blog da receita temos uma lista de receitas mais acessadas pelos nossos usuarios.Essas sao as receitas que mais tiveram curtidas ❤️:",
    "recipes": [{
        "name": "Feijoada",
        "time": "40m",
        "ingredients": 10
    },
    {
        "name": "Macarrao com queijo",
        "time": "30m",
        "ingredients": 6
    },
    {     "name": "Purê de batata",
        "time": "15m",
        "ingredients": 4
    }
    ]
}
Enter fullscreen mode Exit fullscreen mode

e o resultado que teremos é:
print de uma requisição com status 200 no postman

Você pode ver se em sua caixa de email tem um email novo ou se usar o mailtrap veja sua caixa de email nele.

Subindo para AWS

Para fazer o deploy abra seu terminal e dentro da pasta do seu projeto execute o comando abaixo:

sls deploy --stage dev
Enter fullscreen mode Exit fullscreen mode

⚠️ Lembrando que cada serviço da AWS tem custo e tem uma parte gratuita fique sempre atento a isso.

Demora um pouco para terminar de executar porque ele cria toda a estrutura e tudo que precisa para nossa API funcionar, você pode acompanhar o andamento no cloudformation dentro do console da AWS, no final da execução ele mostra um endpoint acessando esse endpoint no curl, postman ou insomnia teremos o mesmo resultado que tivemos rodando localmente.

Para deletar tudo que subimos você pode esvaziar o s3 bucket que foi criado e depois deletar o cloudformation com isso ele vai deletar tudo relacionado a API que a gente subiu.

Fonte dos meus estudos

Finalização

O projeto final está aqui exemplo-sns não esquece de deixar um estrelinha se te ajudou 😊.

Existem muitos projetos que podemos fazer utilizando o SNS e outros serviços da AWS ou usar um serviço com outro serviço, são muitas possibilidades basta buscar mais a respeito de cada serviço e ver qual irá te ajudar no seu projeto 🙂.

Tem um outro projeto que eu fiz utilizando coisas que eu aprendi pipeline-order

Espero ter ajudado de alguma forma e muito obrigado por ler 💜.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .