No tutorial de hoje vou te ensinar a criar uma aplicação simples para envio de e-mail usando ReactJS, Node.js e Nodemailer. Apesar de simples, acredito que vai te ajudar a aprender uma série de coisas novas.
Se preferir, pode assistir a aula gravada abaixo e usar o post apenas para pegar os fontes.
Use o sumário abaixo caso você queira aprender apenas frontend (interface) ou apenas o backend desta aplicação (que envia os emails).
Atenção: este é um tutorial intermediário. Se você não sabe programar NADA em ReactJS, comece por esta série aqui. E se não sabe programar NADA em Node.js, recomendo começar pela minha playlist no Youtube abaixo.
Frontend em ReactJS
O que acha de começarmos a nossa aplicação pelo frontend?
Nesta primeira etapa deste tutorial vou te ensinar a criar a camada de interface da nossa aplicação, que nada mais é do que um formulário de envio de email.
Vamos começar criando nosso projeto ReactJS, usando o comando abaixo.
1 2 3 |
npx create-react-app frontend |
Sim, isso vai demorar um pouco.
Depois de terminar, entre na pasta frontend e coloque o projeto a rodar com npm start. Agora toda vez que editar alguma coisa na aplicação, apenas salve e verá atualizado no seu navegador em localhost:3000.
Agora vamos editar o App.js dentro de src, para termos um formulário de envio de email, como abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import React from 'react'; import './App.css'; function App() { return ( <div className="container"> <form> <label htmlFor="email">E-mail</label> <input type="text" id="email" name="email" placeholder="E-mail de destino.." /> <label htmlFor="nome">Nome</label> <input type="text" id="nome" name="nome" placeholder="Nome da pessoa.." /> <label htmlFor="mensagem">Mensagem</label> <textarea id="mensagem" name="mensagem" placeholder="Escreva algo.." className="textArea"></textarea> <label htmlFor="anexo">Anexo</label> <input type="file" id="anexo" name="anexo" /> <input type="submit" value="Enviar" /> </form> </div> ); } export default App; |
E para estilizá-lo, altere o arquivo App.css substituindo os estilos dele pelos abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
input[type=text], input[type=file], select, textarea { width: 100%; padding: 12px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; margin-top: 6px; margin-bottom: 16px; resize: vertical; } input[type=submit] { background-color: #4CAF50; color: white; padding: 12px 20px; border: none; border-radius: 4px; cursor: pointer; } input[type=submit]:hover { background-color: #45a049; } .container { border-radius: 5px; background-color: #f2f2f2; padding: 20px; max-width: 600px; } |
O resultado esperado você vê na imagem abaixo.
Vamos importar agora uma function do React chamada useState, que usaremos para mapear as mudanças de estados dos inputs do formulário.
1 2 3 4 |
import React, {useState} from 'react'; import './App.css'; |
E usaremos ela da seguinte maneira, definindo valores iniciais para as variáveis de estados que iremos monitorar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const [campos, setCampos] = useState({ nome: '', email: '', mensagem: '', anexo: '' }); function handleInputChange(event){ if(event.target.name === "anexo") campos[event.target.name] = event.target.files[0]; else campos[event.target.name] = event.target.value; setCampos(campos); } |
A function handleInputChange irá ser disparada toda vez que um campo do formulário for alterado. Ela recebe um objeto event por parâmetro que, dentre suas várias propriedades tem o target, que é o campo que originou o evento. Com este target, conseguimos descobrir o name e o value atualizado do mesmo.
Cada vez que essa function for disparada, vamos chamar o setCampos atualizando uma propriedade dinamicamente com base no event.target.name e o seu valor com o event.target.value.
A única diferença é quando o campo é o input-file, porque nele temos de ler a propriedade files ao invés de value.
Ok, preparamos o terreno para a leitura dos campos, agora falta associarmos a function handleInputChange a cada um dos componentes do formulário. Todos eles possuem uma propriedade onChange, que você deve preencher com o nome da função. Abaixo, apenas o exemplo do input de nome, preencha também os demais.
1 2 3 |
<input type="text" name="nome" id="nome" onChange={handleInputChange} /> |
E para ver se nossos campos foram lidos corretamente a cada modificação, ou seja, se o estado dos campos está sendo monitorado corretamente, vamos criar uma function para mapear a submissão do formulário (ainda dentro da function principal do App.js):
1 2 3 4 5 6 |
function handleFormSubmit(event){ event.preventDefault(); console.log(campos); } |
Essa função é bem simples, além de imprimir no console o campos mapeados, ela previne o comportamento padrão do formulário de acontecer. Isso porque, por padrão, o HTML FORM tem uma série de comportamentos ligados à sua submissão como enviar todos a página para o servidor, mas não queremos isso.
Essa função deve ser adicionada no campo onSubmit da tag FORM do seu HTML, como abaixo.
1 2 3 |
<form onSubmit={handleFormSubmit}> |
Agora, ao rodar a nossa aplicação ReactJS, preencher os campos do formulário e clicar no botão de Salvar, o console do navegador deve exibir o objeto JSON dos campos mapeados.
Isso mostra que nosso frontend está pronto!
Backend em Node.js
Quer você já tenha criado o frontend deste mesmo tutorial ou tem o seu próprio frontend, seja ele web ou mobile, esta seção vai te ajudar a construir uma web API em Node.js como sendo o seu backend, responsável pelo recebimento das informações da interface e envio do email em si, usando Nodemailer, a biblioteca mais popular em Node.js para esta finalidade.
Crie uma pasta backend e rode um npm init dentro dela para inicializar um projeto Node.js. Depois, instale as dependências que vamos usar:
1 2 3 |
npm install express cors nodemailer multer |
Primeiro, vamos fazer a web API apenas receber os dados do formulário e devolver na própria resposta. A construção abaixo é de um arquivo api.js usa ExpressJS como web framework criando apenas uma rota GET simples e um POST que ecoa o request recebido. Note que usei o CORS aqui, para permitir que essa web API seja chamada pelo frontend mais tarde.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const express = require('express'); const app = express(); app.use(require("cors")()); app.use(express.json()); app.get('/', (req, res, next) => { res.json({message: "Tudo ok por aqui!"}); }) app.post('/send', (req, res, next) => { res.json(req.body); }) app.listen(3030, () => console.log("Servidor escutando na porta 3030...")); |
Se você colocar este arquivo para rodar ele vai ficar aguardando um POST em http://localhost:3030/send.Você pode testar no Postman, Insomnia ou via cURL mesmo.
Abaixo, resultado no Postman.
O próximo passo, é criarmos um módulo de envio de emails, que vamos chamar de nodemail.js, com o conteúdo abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
const mailer = require("nodemailer"); module.exports = (email, nome, mensagem, anexo) => { const smtpTransport = mailer.createTransport({ host: 'smtp.umbler.com', port: 587, secure: false, //SSL/TLS auth: { pass: 'XXXXX' } }) const mail = { to: email, subject: `${nome} te enviou uma mensagem`, text: mensagem, //html: "<b>Opcionalmente, pode enviar como HTML</b>" } if(anexo){ console.log(anexo); mail.attachments = []; mail.attachments.push({ filename: anexo.originalname, content: anexo.buffer }) } return new Promise((resolve, reject) => { smtpTransport.sendMail(mail) .then(response => { smtpTransport.close(); return resolve(response); }) .catch(error => { smtpTransport.close(); return reject(error); }); }) } |
Este módulo exporta somente uma única função de envio que recebe os dados do destinatário, possui as configurações SMTP no seu interior e envia o email, retornando uma Promise para ser tratada externamente. Minha sugestão pessoal é que use serviços profissionais de SMTP Gateway como Mailgun, SendGrid e AWS SES (tutoriais aqui no blog, clique nos links).
Note que tomei o cuidado de fazer um tratamento especial quando vier um anexo, para adicioná-lo adequadamente à mensagem de email.
Agora, vamos alterar o conteúdo do nosso POST para usar o módulo que criamos anteriormente.
1 2 3 4 5 6 7 8 9 10 11 12 |
const upload = require("multer")(); app.post('/send', upload.single('anexo'), (req, res, next) => { const nome = req.body.nome; const email = req.body.email; const mensagem = req.body.mensagem; const anexo = req.file; require("./nodemail")(email, nome, mensagem, anexo) .then(response => res.json(response)) .catch(error => res.json(error)); }) |
Note que adicionei um middleware ‘upload’ baseado em Multer. Este middleware servirá para receber o anexo que for enviado para a API, quando houver, pois ele é transferido como uma stream de bytes, ao invés de JSON tradicional, que será carregado em req.file ao invés de req.body.
Este ponto é muito importante: para que sua Web API consiga receber tanto o JSON quanto arquivos anexos, o Content-Type do HTTP Request deve ser multipart/form-data.
Se você testar novamente com Postman, dessa vez além de ver que continua funcionando, o email chegará no destino que você preencheu. Note na imagem abaixo que usei o tipo de request form-data, onde além de colocar key-values para os campos da mensagem, consigo adicionar um campo File contendo um arquivo qualquer (basta passar o mouse sobre a key do campo e aparece a opção File).
Isso garante que o nosso backend está funcionando, inclusive com anexos.
Plugando as partes
Agora que temos o nosso frontend e nosso backend construídos, é hora de conectar as duas pontas.
Vamos fazer isso no frontend, usando o Axios, um pacote muito popular em Node.js para fazer chamadas HTTP, para fazer a comunicação. Primeiramente, instale-o via NPM.
1 2 3 |
npm install axios |
E no cabeçalho do arquivo App.js do frontend, vamos alterar os imports no topo.
1 2 3 4 5 |
import React, {useState} from 'react'; import './App.css'; import axios from 'axios'; |
Dentro da função principal do App.js, vamos criar uma nova função para envio dos campos para a API, usando Axios, além de alterar o handleFormSubmit para chamar esta nova função.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function send(){ const formData = new FormData(); Object.keys(campos).forEach(key => formData.append(key, campos[key])); axios.post('http://localhost:3030/send', formData, { headers: { "Content-Type": `multipart/form-data; boundary=${formData._boundary}`, } }) .then(response => { console.log(response.data); }) } function handleFormSubmit(event){ event.preventDefault(); console.log(campos); send(campos); } |
Este uso do Axios é ligeiramente diferente do que você já deve ter visto na internet. Como o request vai conter tanto campos quanto um possível anexo ao email, teremos de usar o Content-Type multipart/form-data em nossa requisição POST. Esse tipo de conteúdo espera receber um objeto FormData com todos os campos e arquivos a serem postados.
Repare também no uso de um boundary que serve para a web API saber como separar os conteúdos do FormData.
Agora, se você subir o seu backend Node.js e o seu frontend ReactJS, verá que você consegue usar o formulário de envio normalmente, incluindo anexos quando quiser enviar mensagens com eles.
Ufa, enfim chegamos no final deste tutorial!
Espero que tenha gostado e, se tiver qualquer dúvida, deixe nos comentários.
Para receber os fontes, cadastre-se no formulário ao final deste artigo.
Como próximo passo, que tal aprender como publicar a sua aplicação? Aqui no blog tem tutoriais para deploy na AWS, Digital Ocean e Heroku!
E se quiser aprender outro projeto simples mas interessante com React, leia este tutorial!
E para conhecer o meu curso de Node.js e MongoDB, clique no banner abaixo!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.
Minha aplicação funciona lindo em localhost, enviando o e-mail certinho. Mas quando subo pro vercer ele me retorna Error 405 Axios… Ja tenteu de tudo com o CORS oq pode ser
HTTP 405 é código de erro para método HTTP não permitido. Nunca fiz deploy na Vercel, possivelmente deve estar faltando alguma configuração para permitir a sua request.