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:
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
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
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
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"]
}
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>
`;
};
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 }),
};
}
};
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 }),
};
}
};
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
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
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
}
]
}
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
⚠️ 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 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 💜.