Nodemail: Envio de email em ReactJS + Node.js + Nodemailer – LuizTools
Escrito por Luiz Duarte em 04/08/2020
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).
Frontend em ReactJS
Plugando as partes
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.
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.
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.
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;
<input type="text" id="email" name="email" placeholder="E-mail de destino.." />
<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" />
E para estilizá-lo, altere o arquivo App.css substituindo os estilos dele pelos abaixo.
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; }
input[type=text], input[type=file], select, textarea {
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.
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.
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); }
anexo: ''
if(event.target.name === "anexo")
campos[event.target.name] = event.target.files[0];
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.
<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):
Isso mostra que nosso frontend está pronto!
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:
npm install express body-parser 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.
const http = require('http'); const express = require('express'); const app = express(); const bodyParser = require('body-parser'); app.use(require("cors")()); app.use(bodyParser.json()); app.get('/', (req, res, next) => { res.json({message: "Tudo ok por aqui!"}); }) app.post('/send', (req, res, next) => { res.json(req.body); }) const server = http.createServer(app); server.listen(3030); console.log("Servidor escutando na porta 3030...")
res.json({message: "Tudo ok por aqui!"});
app.post('/send', (req, res, next) => {
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.
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: { user: 'contato@luiztools.com.br', pass: 'XXXXX' } }) const mail = { from: "Prof. Luiz <contato@luiztools.com.br>", 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); }); }) }
const mailer = require("nodemailer");
module.exports = (email, nome, mensagem, anexo) => {
secure: false, //SSL/TLS
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
smtpTransport.sendMail(mail)
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.
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)); })
const upload = require("multer")();
app.post('/send', upload.single('anexo'), (req, res, next) => {
const mensagem = req.body.mensagem;
const anexo = req.file;
require("./nodemail")(email, nome, mensagem, anexo)
.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.
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.
E no cabeçalho do arquivo App.js do frontend, vamos alterar os imports no topo.
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.
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); }
Object.keys(campos).forEach(key => formData.append(key, campos[key]));
axios.post('http://localhost:3030/send',
"Content-Type": `multipart/form-data; boundary=${formData._boundary}`,
.then(response => { console.log(response.data); })
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 na Amazon AWS? Clique neste link.