Criando um envio de newsletter com AWS SNS e Nodejs
Tags: braziliandevs, javascript, aws, node
Published: 2022-04-25 22:45:57


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)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aohh82cm9emua6x4zza5.png)

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](https://aws.amazon.com/pt/), 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](https://aws.amazon.com/pt/cli/) da AWS e para configurar o login da CLI você pode seguir esse [Tutorial CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-prereqs.html) 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](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html) `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)](https://nodejs.org/en/) e [serverless framework(estou utilizando a versão 3.14.0)](https://www.serverless.com/framework/docs/getting-started).

## Criando o projeto

Para criar o projeto abra o terminal e execute o comando abaixo:
```shell
serverless create --template aws-nodejs --path newsletter
```
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:
```shell
npm i -D serverless-offline-sns serverless-offline
```

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**:
```shell
npm i aws-sdk nodemailer
```

### 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*:
```json
{
  "data": ["emailfake@fake.com"]
}
```
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*:
```javascript
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>
`;
};

```
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*:

```javascript
"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 }),
    };
  }
};
```
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*.

```javascript
"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 }),
    };
  }
};
```
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](https://mailtrap.io/) 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](https://nodemailer.com/about/) deles.

Agora vamos ajustar o arquivo *serverless.yml*:
```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
```
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](https://www.serverless.com/plugins/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:
```shell
sls offline start --stage local
```

Você pode testar a rota ` http://localhost:3000/local/publish-newsletter` no postman, passando no body esse json:
```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
    }
    ]
}
```
e o resultado que teremos é:
![print de uma requisição com status 200 no postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1w0hcx9wjrsz8g7cqip2.png)

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:
```shell
sls deploy --stage dev
```

**⚠️ 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
- https://docs.aws.amazon.com/pt_br/sdk-for-javascript/v2/developer-guide/sns-examples-managing-topics.html
- https://www.serverless.com/examples/serverless-lambda-sns-example
- https://www.youtube.com/watch?v=XuCTOamnOW4&list=PL4GYDFQO_BHCOt_sYCM3vier6_EQ150Ga&ab_channel=BalaTech


## Finalização
O projeto final está aqui [exemplo-sns](https://github.com/janapc/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](https://github.com/janapc/pipeline-order)

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