Criando um formulário de contato sem servidor para seu site estático

Publicados: 2022-03-10
Resumo rápido ↬ Com a ajuda deste artigo, você finalmente poderá aprender os conceitos básicos das APIs Lambda e Simple Email Service (SES) da Amazon Web Services (AWS) para ajudá-lo a criar seu próprio mailer de site estático no Serverless Framework. Vamos começar!

Os geradores de sites estáticos fornecem uma alternativa rápida e simples aos sistemas de gerenciamento de conteúdo (CMS) como o WordPress. Não há configuração de servidor ou banco de dados, apenas um processo de construção e HTML, CSS e JavaScript simples. Infelizmente, sem um servidor, é fácil atingir seus limites rapidamente. Por exemplo, ao adicionar um formulário de contato.

Com o surgimento da arquitetura sem servidor, adicionar um formulário de contato ao seu site estático não precisa mais ser o motivo para mudar para um CMS. É possível obter o melhor dos dois mundos: um site estático com um back-end sem servidor para o formulário de contato (que você não precisa manter). Talvez o melhor de tudo, em sites de baixo tráfego, como portfólios, os altos limites de muitos provedores sem servidor tornam esses serviços totalmente gratuitos!

Neste artigo, você aprenderá os conceitos básicos das APIs Lambda e Simple Email Service (SES) da Amazon Web Services (AWS) para criar seu próprio mailer de site estático no Serverless Framework. O serviço completo pegará os dados do formulário enviados de uma solicitação AJAX, atingirá o endpoint Lambda, analisará os dados para criar os parâmetros SES, enviará o endereço de e-mail e retornará uma resposta para nossos usuários. Vou orientá-lo na configuração do Serverless pela primeira vez por meio da implantação. Deve levar menos de uma hora para ser concluído, então vamos começar!

O formulário de site estático, enviando a mensagem para o endpoint do Lambda e retornando uma resposta ao usuário.
O formulário de site estático, enviando a mensagem para o endpoint do Lambda e retornando uma resposta ao usuário.
Mais depois do salto! Continue lendo abaixo ↓

Configurando

Existem pré-requisitos mínimos para começar a usar a tecnologia Serverless. Para nossos propósitos, é simplesmente um ambiente de nó com Yarn, o Serverless Framework e uma conta da AWS.

Configurando o Projeto

O site do Serverless Framework. Útil para instalação e documentação.
O site do Serverless Framework. Útil para instalação e documentação.

Usamos o Yarn para instalar o Serverless Framework em um diretório local.

  1. Crie um novo diretório para hospedar o projeto.
  2. Navegue até o diretório em sua interface de linha de comando.
  3. Execute yarn init para criar um arquivo package.json para este projeto.
  4. Execute yarn add serverless para instalar a estrutura localmente.
  5. Execute yarn serverless create --template aws-nodejs --name static-site-mailer para criar um modelo de serviço Node e nomeie-o como static-site-mailer .

Nosso projeto está configurado, mas não poderemos fazer nada até configurarmos nossos serviços da AWS.

Configurando uma conta Amazon Web Services, credenciais e serviço de e-mail simples

A página de inscrição do Amazon Web Services, que inclui um nível gratuito generoso, permitindo que nosso projeto seja totalmente gratuito.
A página de inscrição do Amazon Web Services, que inclui um nível gratuito generoso, permitindo que nosso projeto seja totalmente gratuito.

O Serverless Framework gravou um passo a passo em vídeo para configurar as credenciais da AWS, mas também listei as etapas aqui.

  1. Inscreva-se para uma conta da AWS ou faça login se já tiver uma.
  2. Na barra de pesquisa da AWS, procure por “IAM”.
  3. Na página do IAM, clique em “Usuários” na barra lateral e depois no botão “Adicionar usuário”.
  4. Na página Adicionar usuário, dê um nome ao usuário – algo como “sem servidor” é apropriado. Marque “Acesso programático” em Tipo de acesso e clique em próximo.
  5. Na tela de permissões, clique na guia “Anexar políticas existentes diretamente”, procure por “AdministratorAccess” na lista, marque-o e clique em próximo.
  6. Na tela de revisão, você deve ver seu nome de usuário, com “Acesso programático” e “Acesso de administrador”, depois crie o usuário.
  7. A tela de confirmação mostra o usuário “Access key ID” e “Secret access key”, você precisará deles para fornecer acesso ao Serverless Framework. Em sua CLI, digite yarn sls config credentials --provider aws --key YOUR_ACCESS_KEY_ID --secret YOUR_SECRET_ACCESS_KEY , substituindo YOUR_ACCESS_KEY_ID e YOUR_SECRET_ACCESS_KEY pelas chaves na tela de confirmação.

Suas credenciais estão configuradas agora, mas enquanto estivermos no console da AWS, vamos configurar o Simple Email Service.

  1. Clique em Console Home no canto superior esquerdo para ir para casa.
  2. Na página inicial, na barra de pesquisa da AWS, procure por “Simple Email Service”.
  3. Na página inicial do SES, clique em “Endereços de e-mail” na barra lateral.
  4. Na página de listagem de endereços de e-mail, clique no botão “Verificar um novo endereço de e-mail”.
  5. Na janela de diálogo, digite seu endereço de e-mail e clique em “Verificar este endereço de e-mail”.
  6. Você receberá um e-mail em instantes contendo um link para verificar o endereço. Clique no link para concluir o processo.

Agora que nossas contas estão feitas, vamos dar uma olhada nos arquivos de template Serverless.

Configurando a estrutura sem servidor

A execução serverless create cria dois arquivos: handler.js, que contém a função do Lambda, e serverless.yml, que é o arquivo de configuração de toda a arquitetura sem servidor. Dentro do arquivo de configuração, você pode especificar quantos manipuladores desejar, e cada um será mapeado para uma nova função que pode interagir com outras funções. Neste projeto, criaremos apenas um único handler, mas em uma Serverless Architecture completa, você teria várias das várias funções do serviço.

A estrutura de arquivo padrão gerada a partir do Serverless Framework contendo handler.js e serverless.yml.
A estrutura de arquivo padrão gerada a partir do Serverless Framework contendo handler.js e serverless.yml.

Em handler.js, você verá uma única função exportada chamada hello . Esta é atualmente a principal (e única) função. Ele, junto com todos os manipuladores do Node, recebe três parâmetros:

  • event
    Isso pode ser pensado como os dados de entrada para a função.
  • context object
    Ele contém as informações de tempo de execução da função do Lambda.
  • callback
    Um parâmetro opcional para retornar informações ao chamador.
 // handler.js 'use strict'; module.exports.hello = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }), }; callback(null, response); };

Na parte inferior de hello , há um retorno de chamada. É um argumento opcional para retornar uma resposta, mas se não for explicitamente chamado, retornará implicitamente com null . O retorno de chamada recebe dois parâmetros:

  • Erro de erro
    Para fornecer informações de erro para quando o próprio Lambda falhar. Quando o Lambda for bem-sucedido, null deve ser passado para esse parâmetro.
  • Resultado do objeto
    Para fornecer um objeto de resposta. Deve ser compatível com JSON.stringify . Se houver um parâmetro no campo de erro, esse campo será ignorado.

Nosso site estático enviará nossos dados de formulário no corpo do evento e o retorno de chamada retornará uma resposta para nosso usuário ver.

Em serverless.yml, você verá o nome do serviço, as informações do provedor e as funções.

 # serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: hello: handler: handler.hello 
Como os nomes de funções em serverless.yml são mapeados para handler.js.
Como os nomes de funções em serverless.yml são mapeados para handler.js.

Observe o mapeamento entre a função hello e o manipulador? Podemos nomear nosso arquivo e funcionar qualquer coisa e, desde que mapeie para a configuração, ele funcionará. Vamos renomear nossa função para staticSiteMailer .

 # serverless.yml functions: staticSiteMailer: handler: handler.staticSiteMailer
 // handler.js module.exports.staticSiteMailer = (event, context, callback) => { ... };

As funções do Lambda precisam de permissão para interagir com outra infraestrutura da AWS. Antes de podermos enviar um e-mail, precisamos permitir que a SES o faça. Em serverless.yml, em provider.iamRoleStatements , adicione a permissão.

 # serverless.yml provider: name: aws runtime: nodejs6.10 iamRoleStatements: - Effect: "Allow" Action: - "ses:SendEmail" Resource: ["*"]

Como precisamos de uma URL para nossa ação de formulário, precisamos adicionar eventos HTTP à nossa função. Em serverless.yml, criamos um caminho, especificamos o método como post e definimos CORS como true para segurança.

 functions: staticSiteMailer: handler: handler.staticSiteMailer events: - http: method: post path: static-site-mailer cors: true

Nossos arquivos serverless.yml e handler.js atualizados devem ter a seguinte aparência:

 # serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: staticSiteMailer: handler: handler.staticSiteMailer events: - http: method: post path: static-site-mailer cors: true provider: name: aws runtime: nodejs6.10 iamRoleStatements: - Effect: "Allow" Action: - "ses:SendEmail" Resource: ["*"]
 // handler.js 'use strict'; module.exports.staticSiteMailer = (event, context, callback) => { const response = { statusCode: 200, body: JSON.stringify({ message: 'Go Serverless v1.0! Your function executed successfully!', input: event, }), }; callback(null, response); };

Nossa arquitetura sem servidor está configurada, então vamos implantá-la e testá-la. Você receberá uma resposta JSON simples.

 yarn sls deploy --verbose yarn sls invoke --function staticSiteMailer { "statusCode": 200, "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}" } 
A resposta de retorno ao invocar nossa nova função serverless.
A resposta de retorno ao invocar nossa nova função serverless.

Criando o formulário HTML

Nossa entrada da função Lambda e a saída do formulário precisam corresponder, portanto, antes de criarmos a função, criaremos o formulário e capturaremos sua saída. Mantemos a simplicidade com campos de nome, e-mail e mensagem. Adicionaremos a ação de formulário assim que implantarmos nossa arquitetura sem servidor e obtivermos nossa URL, mas sabemos que será uma solicitação POST para que possamos adicioná-la. No final do formulário, adicionamos uma tag de parágrafo para exibir mensagens de resposta para o usuário que atualizaremos no retorno de chamada de envio.

 <form action="{{ SERVICE URL }}" method="POST"> <label> Name <input type="text" name="name" required> </label> <label> Email <input type="email" name="reply_to" required> </label> <label> Message: <textarea name="message" required></textarea> </label> <button type="submit">Send Message</button> </form> <p></p>

Para capturar a saída, adicionamos um manipulador de envio ao formulário, transformamos nossos parâmetros de formulário em um objeto e enviamos JSON stringified para nossa função Lambda. Na função Lambda, usamos JSON.parse() para ler nossos dados. Alternativamente, você pode usar Serialize ou query-string do jQuery para enviar e analisar os parâmetros do formulário como uma string de consulta, mas JSON.stringify() e JSON.parse() são nativos.

 (() => { const form = document.querySelector('form'); const formResponse = document.querySelector('js-form-response'); form.onsubmit = e => { e.preventDefault(); // Prepare data to send const data = {}; const formElements = Array.from(form); formElements.map(input => (data[input.name] = input.value)); // Log what our lambda function will receive console.log(JSON.stringify(data)); }; })();

Vá em frente e envie seu formulário e capture a saída do console. Vamos usá-lo em nossa função Lambda a seguir.

Capturando os dados do formulário em um log do console.
Capturando os dados do formulário em um log do console.

Invocando funções do Lambda

Especialmente durante o desenvolvimento, precisamos testar se nossa função faz o que esperamos. O Serverless Framework fornece o comando invoke e invoke local para acionar sua função de ambientes ao vivo e de desenvolvimento , respectivamente. Ambos os comandos requerem que o nome da função seja passado, no nosso caso staticSiteMailer .

 yarn sls invoke local --function staticSiteMailer

Para passar dados simulados para nossa função, crie um novo arquivo chamado data.json com a saída do console capturada em uma chave de body em um objeto JSON. Deve ser algo como:

 // data.json { "body": "{\"name\": \"Sender Name\",\"reply_to\": \"[email protected]\",\"message\": \"Sender message\"}" }

Para invocar a função com os dados locais, passe o argumento --path junto com o caminho para o arquivo.

 yarn sls invoke local --function staticSiteMailer --path data.json 
Uma resposta de retorno atualizada de nossa função sem servidor quando passamos dados JSON.
Uma resposta de retorno atualizada de nossa função sem servidor quando passamos dados JSON.

Você verá uma resposta semelhante à anterior, mas a chave de input conterá o evento que zombamos. Vamos usar nossos dados simulados para enviar um e-mail usando o Simple Email Service!

Enviando um e-mail com o serviço de e-mail simples

Vamos substituir a função staticSiteMailer por uma chamada para uma função privada sendEmail . Por enquanto, você pode comentar ou remover o código do modelo e substituí-lo por:

 // hander.js function sendEmail(formData, callback) { // Build the SES parameters // Send the email } module.exports.staticSiteMailer = (event, context, callback) => { const formData = JSON.parse(event.body); sendEmail(formData, function(err, data) { if (err) { console.log(err, err.stack); } else { console.log(data); } }); };

Primeiro, analisamos o event.body para capturar os dados do formulário, depois os passamos para uma função privada sendEmail . sendEmail é responsável por enviar o e-mail, e a função callback retornará uma resposta de falha ou sucesso com err ou data . No nosso caso, podemos simplesmente registrar o erro ou os dados, pois substituiremos isso pelo retorno de chamada do Lambda em breve.

A Amazon fornece um SDK conveniente, aws-sdk , para conectar seus serviços com funções do Lambda. Muitos de seus serviços, incluindo o SES, fazem parte dele. Nós o adicionamos ao projeto com yarn add aws-sdk e o importamos para o topo do arquivo handler.

 // handler.js const AWS = require('aws-sdk'); const SES = new AWS.SES();

Em nossa função sendEmail privada, construímos os parâmetros SES.sendEmail a partir dos dados do formulário analisado e usamos o retorno de chamada para retornar uma resposta ao chamador. Os parâmetros requerem o seguinte como um objeto:

  • Fonte
    O endereço de e-mail que a SES está enviando.
  • ReplyToAddresses
    Uma matriz de endereços de email adicionados à resposta ao campo no email.
  • Destino
    Um objeto que deve conter pelo menos um ToAddresses , CcAddresses ou BccAddresses . Cada campo recebe uma matriz de endereços de e-mail que correspondem aos campos to , cc e bcc respectivamente.
  • Mensagem
    Um objeto que contém o Corpo e o Sujeito .

Como formData é um objeto, podemos chamar nossos campos de formulário diretamente como formData.message , construir nossos parâmetros e enviá-los. Passamos seu e-mail verificado com SES para Source e Destination.ToAddresses . Desde que o e-mail seja verificado, você pode passar qualquer coisa aqui, incluindo endereços de e-mail diferentes. Retiramos nosso reply_to , message , e name nosso objeto formData para preencher os campos ReplyToAddresses e Message.Body.Text.Data .

 // handler.js function sendEmail(formData, callback) { const emailParams = { Source: '[email protected]', // SES SENDING EMAIL ReplyToAddresses: [formData.reply_to], Destination: { ToAddresses: ['[email protected]'], // SES RECEIVING EMAIL }, Message: { Body: { Text: { Charset: 'UTF-8', Data: `${formData.message}\n\nName: ${formData.name}\nEmail: ${formData.reply_to}`, }, }, Subject: { Charset: 'UTF-8', Data: 'New message from your_site.com', }, }, }; SES.sendEmail(emailParams, callback); }

SES.sendEmail enviará o e-mail e nosso retorno de chamada retornará uma resposta. Invocar a função local enviará um e-mail para seu endereço verificado.

 yarn sls invoke local --function staticSiteMailer --path data.json 
A resposta de retorno de SES.sendEmail quando for bem-sucedida.
A resposta de retorno de SES.sendEmail quando for bem-sucedida.

Retornando uma resposta do manipulador

Nossa função envia um e-mail usando a linha de comando, mas não é assim que nossos usuários irão interagir com ela. Precisamos retornar uma resposta ao nosso envio de formulário AJAX. Se falhar, devemos retornar um statusCode apropriado, bem como o err.message . Quando for bem-sucedido, o 200 statusCode será suficiente, mas também retornaremos a resposta do mailer no corpo. No staticSiteMailer , criamos nossos dados de resposta e substituímos nossa função de retorno de chamada sendEmail retorno de chamada do Lambda.

 // handler.js module.exports.staticSiteMailer = (event, context, callback) => { const formData = JSON.parse(event.body); sendEmail(formData, function(err, data) { const response = { statusCode: err ? 500 : 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': 'https://your-domain.com', }, body: JSON.stringify({ message: err ? err.message : data, }), }; callback(null, response); }); };

Nosso retorno de chamada Lambda agora retorna mensagens de sucesso e falha de SES.sendEmail . Construímos a resposta com verificações se err está presente para que nossa resposta seja consistente. A própria função de retorno de chamada do Lambda passa null no campo do argumento de erro e a resposta como o segundo. Queremos passar erros adiante, mas se o próprio Lambda falhar, seu retorno de chamada será chamado implicitamente com a resposta de erro.

Nos headers , você precisará substituir Access-Control-Allow-Origin pelo seu próprio domínio. Isso impedirá que outros domínios usem seu serviço e potencialmente acumulem uma fatura da AWS em seu nome! E não abordo neste artigo, mas é possível configurar o Lambda para usar seu próprio domínio. Você precisará ter um certificado SSL/TLS carregado na Amazon. A equipe do Serverless Framework escreveu um tutorial fantástico sobre como fazer isso.

Invocar a função local agora enviará um e-mail e retornará a resposta apropriada.

 yarn sls invoke local --function staticSiteMailer --path data.json 
A resposta de retorno de nossa função sem servidor, contendo a resposta de retorno SES.sendEmail no corpo.
A resposta de retorno de nossa função sem servidor, contendo a resposta de retorno SES.sendEmail no corpo.

Chamando a função Lambda do formulário

Nosso serviço é completo! Para implantá-lo, execute yarn sls deploy -v . Uma vez implantado, você obterá um URL que se parece com https://r4nd0mh45h.execute-api.us-east-1.amazonaws.com/dev/static-site-mailer , que você pode adicionar à ação do formulário. Em seguida, criamos a solicitação AJAX e retornamos a resposta ao usuário.

 (() => { const form = document.querySelector('form'); const formResponse = document.querySelector('js-form-response'); form.onsubmit = e => { e.preventDefault(); // Prepare data to send const data = {}; const formElements = Array.from(form); formElements.map(input => (data[input.name] = input.value)); // Log what our lambda function will receive console.log(JSON.stringify(data)); // Construct an HTTP request var xhr = new XMLHttpRequest(); xhr.open(form.method, form.action, true); xhr.setRequestHeader('Accept', 'application/json; charset=utf-8'); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); // Send the collected data as JSON xhr.send(JSON.stringify(data)); // Callback function xhr.onloadend = response => { if (response.target.status === 200) { // The form submission was successful form.reset(); formResponse.innerHTML = 'Thanks for the message. I'll be in touch shortly.'; } else { // The form submission failed formResponse.innerHTML = 'Something went wrong'; console.error(JSON.parse(response.target.response).message); } }; }; })();

No retorno de chamada AJAX, verificamos o código de status com response.target.status . Se for diferente de 200 , podemos mostrar uma mensagem de erro ao usuário, caso contrário, informá-lo que a mensagem foi enviada. Como nosso Lambda retorna JSON com string, podemos analisar o corpo da mensagem com JSON.parse(response.target.response).message . É especialmente útil registrar o erro.

Você deve poder enviar seu formulário inteiramente de seu site estático!

O formulário de site estático, enviando a mensagem para o endpoint do Lambda e retornando uma resposta ao usuário.
O formulário de site estático, enviando a mensagem para o endpoint do Lambda e retornando uma resposta ao usuário.

Próximos passos

Adicionar um formulário de contato à sua estática é fácil com o Serverless Framework e a AWS. Há espaço para melhorias em nosso código, como adicionar validação de formulário com um honeypot, evitar chamadas AJAX para formulários inválidos e melhorar o UX se a resposta, mas isso é suficiente para começar. Você pode ver algumas dessas melhorias no repositório de mala direta de site estático que criei. Espero ter inspirado você a experimentar o Serverless você mesmo!