Criando um formulário de contato sem servidor para seu site estático
Publicados: 2022-03-10Os 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!
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
Usamos o Yarn para instalar o Serverless Framework em um diretório local.
- Crie um novo diretório para hospedar o projeto.
- Navegue até o diretório em sua interface de linha de comando.
- Execute
yarn init
para criar um arquivopackage.json
para este projeto. - Execute
yarn add serverless
para instalar a estrutura localmente. - Execute
yarn serverless create --template aws-nodejs --name static-site-mailer
para criar um modelo de serviço Node e nomeie-o comostatic-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
O Serverless Framework gravou um passo a passo em vídeo para configurar as credenciais da AWS, mas também listei as etapas aqui.
- Inscreva-se para uma conta da AWS ou faça login se já tiver uma.
- Na barra de pesquisa da AWS, procure por “IAM”.
- Na página do IAM, clique em “Usuários” na barra lateral e depois no botão “Adicionar usuário”.
- 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.
- 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.
- 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.
- 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
, substituindoYOUR_ACCESS_KEY_ID
eYOUR_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.
- Clique em Console Home no canto superior esquerdo para ir para casa.
- Na página inicial, na barra de pesquisa da AWS, procure por “Simple Email Service”.
- Na página inicial do SES, clique em “Endereços de e-mail” na barra lateral.
- Na página de listagem de endereços de e-mail, clique no botão “Verificar um novo endereço de e-mail”.
- Na janela de diálogo, digite seu endereço de e-mail e clique em “Verificar este endereço de e-mail”.
- 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.
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 comJSON.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
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\":{}}" }
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.
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
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
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
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!
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!