Envio de e-mail com aws ses

Nesta publicação vamos implementar uma integração com serviço AWS SES e vamos criar uma aplicação completa em que é possível enviar um e-mail.

Para esta implementação vamos utilizar as seguintes tecnologias, que utiliza o backend para renderizar o frontend e integrar o serviço da aws. É uma aplicação tradicional html renderizada no servidor (server side rendering). No modelo single page application.

  • nodejs para backend e frontend (utilizando html puro)
  • aws ses

O aws ses (é um serviço pago), essa implementação é uma prova de conceito de implementação de uma página de contato em que é enviado um e-mail para mim mesmo. Via o serviço da aws ses.

Você pode ver um exemplo do funcionamento da página aqui. Esta página não possui integração com  o backend, está disponível apenas para ser ter uma ídeia do funcionamento e como ficará o projeto depois de pronto (do ponto de vista de apresentação).

Implementando o frontend

A implementação do frontend possui apenas duas rotas uma para a GET vai renderizar o formulário de contato.

export default function layout({ contato }) {
  const { email, message, title, errors } = contato;
  return `
    <html lang="pt-br">
    <head>
      <title>Sendmail app</title>
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
    </head>

    <body class="">
      <nav class="container-fluid navbar navbar-expand navbar-light bg-light">
        <ul class="navbar-nav ms-auto">
          <li class="nav-item"><a class="nav-link" href="/">Home</a></li>
        </ul>
      </nav>
      <main class="mt-4 container py-4 position-relative">
      <h2>Contato</h2>
      <form method="post" class="has-validation mt-3" action="/">
      <div class="mb-3">
          <label for="servicestatus" class="form-label"></label>
          <input type="hidden" class="form-control ${errors.service.length ? 'is-invalid': ''}" id="servicestatus" name="servicestatus" value="">
          <div class="invalid-feedback">
            ${errors.service.length > 0 ? errors.service[0]: ''}
          </div>
        </div>
        <div class="mb-3">
          <label for="email" class="form-label">E-mail</label>
          <input type="text" class="form-control ${errors.email.length ? 'is-invalid': ''}" id="email" name="email" value="${email}">
          <div class="invalid-feedback">
            ${errors.email.length > 0 ? errors.email[0]: ''}
          </div>
        </div>
        <div class="mb-3">
          <label for="title" class="form-label">Título</label>
          <input type="text" class="form-control ${errors.title.length ? 'is-invalid': ''}" id="title" name="title" value="${title}">
          <div class="invalid-feedback">
            ${errors.title.length > 0 ? errors.title[0]: ''}
          </div>
        </div>
        <div class="mb-3">
          <label for="message" class="form-label">Mensagem</label>
          <textarea class="form-control ${errors.message.length ? 'is-invalid': ''}" id="message" name="message">${message}</textarea>
          <div class="invalid-feedback">
            ${errors.message.length > 0 ? errors.message[0]: ''}
          </div>
        </div>
        <button id="btnenviar" type="submit" class="btn btn-primary">

        Enviar </button>
      </form>
      </main>
    </body>
    </html>
  `;
}

Outra a mesma rota para o POST que vai fazer a validação dos dados do formulário e caso esteja tudo certo fará o envio do e-mail. Caso seja feito o envio com sucesso, então sera exibida uma tela de sucesso ao enviar.

export default function sucesso() {
    return `
      <html lang="pt-br">
      <head>
        <title>Sendmail app</title>
          <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
          <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
      </head>

      <body class="">
        <nav class="container-fluid navbar navbar-expand navbar-light bg-light">
          <ul class="navbar-nav ms-auto">
            <li class="nav-item"><a class="nav-link" href="/">Home</a></li>
          </ul>
        </nav>
        <main class="mt-4 container py-4 position-relative">
        <div class="row justify-content-md-center">
        <div class="col col-md-auto text-center">
          <div id="sucesso" class="d-flex justify-content-center h1"> Mensagem enviada com sucesso
          </div>
          <br>
          <a href="/" class="btn btn-primary justify-content-center" role="button">Voltar</a>
        </div>
      </div>
        </main>
      </body>
      </html>
    `;
  }

Agora para integrar os serviços vamos implementar a funcionalidade de envio de e-mai, como dito, o envio será feito via o serviço AWS  simple email service

Para isso vamos apenas adicionar a biblioteca do aws sdk, e criar uma instância do servico SES, ele vai ficar responsável pelo envio do e-mail.

Vamos instalar o sdk da aws, para isso utilize o npm install

npm install @aws-sdk/client-sesv2

Após fazer essa instalação, edite o arquivo server/index.js, adicionar o import e fazer as configurações básicas para o envio de um e-mail, nesta tarefa fomos guiados pela documentação da AWS do serviço sesv2.

O resultado final fica assim, aqui vou mostrar apenas a implementação do método sendMail:

async sendMail(req, res) {
    const contato = new Contato(req.body)
    if (contato.email.trim() === '') {
      contato.errors.email.push('E-mail não pode ser vazio')
    }
    if (contato.title.trim() === '') {
      contato.errors.title.push('Título não pode ser vazio')
    }
    if (contato.message.trim() === '') {
      contato.errors.message.push('Mensagem não pode ser vazio')
    }
    if (contato.errors && (contato.errors.email.length > 0 || contato.errors.message.length > 0 || contato.errors.title.length > 0)) {
      return res.send(layout({ contato: contato }))
    } //até aqui faz validação dos campos obrigatórios, renderiza o form com as mensagens de erro e os dados do formulário

    const input = { // SendEmailRequest
      FromEmailAddress: "contato@danizavtz.com.br",
      ReplyToAddresses: [
        "daniellucena@yahoo.com.br",
      ],
      Destination: { // Destination
          ToAddresses: [ // EmailAddressList
            "daniellucena@yahoo.com.br",
          ]
      },
      Content: { // EmailContent
        Simple: { // Message
          Subject: { // Content
            Data: req.body.title, // required
            Charset: "UTF-8",
          },
          Body: { // Body
            Text: {
              Data: `Mensagem de: ${req.body.email}, \nMensagem: ${req.body.message}`, // required
              Charset: "UTF-8",
            }
          }
        }
      }
    };
    const command = new SendEmailCommand(input);
    try {
        await client.send(command);
        res.send(sucesso())
    } catch (err) {
        console.log(err); //esta linha não é necessária, é utilizada apenas para debug
        contato.errors.service.push('Houve um erro durante o envio')
        return res.send(layout({ contato: contato }))
    }
  }

Repare que temos um input do tipo "hidden", ele serve para incluir a mensagem de erro no caso de haver um erro no envio, (quando esse erro vem do serviço aws-ses).

Veja a versão completa deste projeto.