Creación de un formulario de contacto sin servidor para su sitio estático
Publicado: 2022-03-10Los generadores de sitios estáticos proporcionan una alternativa rápida y sencilla a los sistemas de gestión de contenido (CMS) como WordPress. No hay configuración de servidor o base de datos, solo un proceso de compilación y HTML, CSS y JavaScript simples. Desafortunadamente, sin un servidor, es fácil alcanzar sus límites rápidamente. Por ejemplo, al agregar un formulario de contacto.
Con el auge de la arquitectura sin servidor, agregar un formulario de contacto a su sitio estático ya no tiene por qué ser la razón para cambiar a un CMS. Es posible obtener lo mejor de ambos mundos: un sitio estático con un back-end sin servidor para el formulario de contacto (que no necesita mantener). ¡Quizás lo mejor de todo es que en sitios de poco tráfico, como carteras, los altos límites de muchos proveedores sin servidor hacen que estos servicios sean completamente gratuitos!
En este artículo, aprenderá los conceptos básicos de Amazon Web Services (AWS) Lambda y las API de Simple Email Service (SES) para crear su propio sitio estático de correo en Serverless Framework. El servicio completo tomará los datos de forma enviados desde una solicitud AJAX, alcanzará el punto final de Lambda, analizará los datos para crear los parámetros SES, enviará la dirección de correo electrónico y devolverá una respuesta a nuestros usuarios. Lo guiaré a través de la configuración de Serverless por primera vez a través de la implementación. Debería tardar menos de una hora en completarse, ¡así que comencemos!
Configuración
Existen requisitos previos mínimos para comenzar con la tecnología Serverless. Para nuestros propósitos, es simplemente un entorno de nodo con Yarn, el marco sin servidor y una cuenta de AWS.
Configuración del proyecto
Usamos Yarn para instalar Serverless Framework en un directorio local.
- Cree un nuevo directorio para alojar el proyecto.
- Navegue hasta el directorio en su interfaz de línea de comandos.
- Ejecute
yarn init
para crear un archivopackage.json
para este proyecto. - Ejecute
yarn add serverless
para instalar el marco localmente. - Ejecute
yarn serverless create --template aws-nodejs --name static-site-mailer
para crear una plantilla de servicio de nodo y asígnele el nombrestatic-site-mailer
.
Nuestro proyecto está configurado, pero no podremos hacer nada hasta que configuremos nuestros servicios de AWS.
Configuración de una cuenta, credenciales y un servicio de correo electrónico simple de Amazon Web Services
Serverless Framework ha grabado un recorrido en video para configurar las credenciales de AWS, pero también he enumerado los pasos aquí.
- Regístrese para obtener una cuenta de AWS o inicie sesión si ya tiene una.
- En la barra de búsqueda de AWS, busque "IAM".
- En la página de IAM, haga clic en "Usuarios" en la barra lateral, luego en el botón "Agregar usuario".
- En la página Agregar usuario, asigne un nombre al usuario; algo como "sin servidor" es apropiado. Marque "Acceso programático" en Tipo de acceso y luego haga clic en Siguiente.
- En la pantalla de permisos, haga clic en la pestaña "Adjuntar políticas existentes directamente", busque "AdministratorAccess" en la lista, márquelo y haga clic en siguiente.
- En la pantalla de revisión, debería ver su nombre de usuario, con "Acceso programático" y "Acceso de administrador", luego cree el usuario.
- La pantalla de confirmación muestra la "ID de la clave de acceso" y la "Clave de acceso secreta" del usuario; las necesitará para proporcionar acceso a Serverless Framework. En su CLI, escriba
yarn sls config credentials --provider aws --key YOUR_ACCESS_KEY_ID --secret YOUR_SECRET_ACCESS_KEY
, reemplazandoYOUR_ACCESS_KEY_ID
yYOUR_SECRET_ACCESS_KEY
con las claves en la pantalla de confirmación.
Sus credenciales están configuradas ahora, pero mientras estamos en la consola de AWS configuremos Simple Email Service.
- Haga clic en Console Home en la esquina superior izquierda para ir a casa.
- En la página de inicio, en la barra de búsqueda de AWS, busque "Servicio de correo electrónico simple".
- En la página de inicio de SES, haga clic en "Direcciones de correo electrónico" en la barra lateral.
- En la página de lista de direcciones de correo electrónico, haga clic en el botón "Verificar una nueva dirección de correo electrónico".
- En la ventana de diálogo, escriba su dirección de correo electrónico y luego haga clic en "Verificar esta dirección de correo electrónico".
- Recibirá un correo electrónico en unos momentos con un enlace para verificar la dirección. Haga clic en el enlace para completar el proceso.
Ahora que nuestras cuentas están hechas, echemos un vistazo a los archivos de plantilla de Serverless.
Configuración del marco sin servidor
Ejecutar la serverless create
crea dos archivos: handler.js, que contiene la función Lambda, y serverless.yml, que es el archivo de configuración para toda la arquitectura sin servidor. Dentro del archivo de configuración, puede especificar tantos controladores como desee, y cada uno se asignará a una nueva función que puede interactuar con otras funciones. En este proyecto, solo crearemos un solo controlador, pero en una arquitectura sin servidor completa, tendría varias de las diversas funciones del servicio.
En handler.js, verá una sola función exportada llamada hello
. Esta es actualmente la función principal (y única). Este, junto con todos los controladores de nodos, toma tres parámetros:
-
event
Esto se puede considerar como los datos de entrada para la función. -
context object
Contiene la información de tiempo de ejecución de la función Lambda. -
callback
Un parámetro opcional para devolver información a la persona que llama.
// 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); };
En la parte inferior de hello
, hay una devolución de llamada. Es un argumento opcional para devolver una respuesta, pero si no se llama explícitamente , devolverá implícitamente un null
. La devolución de llamada toma dos parámetros:
- erro error
Para proporcionar información de error para cuando el propio Lambda falla. Cuando Lambda tiene éxito, se debe pasarnull
a este parámetro. - resultado del objeto
Para proporcionar un objeto de respuesta. Debe ser compatible conJSON.stringify
. Si hay un parámetro en el campo de error, este campo se ignora.
Nuestro sitio estático enviará los datos de nuestro formulario en el cuerpo del evento y la devolución de llamada devolverá una respuesta para que nuestro usuario la vea.
En serverless.yml verá el nombre del servicio, la información del proveedor y las funciones.
# serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: hello: handler: handler.hello
¿Observe el mapeo entre la función hola y el controlador? Podemos nombrar nuestro archivo y función cualquier cosa y siempre que se asigne a la configuración, funcionará. Cambiemos el nombre de nuestra función a staticSiteMailer
.
# serverless.yml functions: staticSiteMailer: handler: handler.staticSiteMailer
// handler.js module.exports.staticSiteMailer = (event, context, callback) => { ... };
Las funciones de Lambda necesitan permiso para interactuar con otra infraestructura de AWS. Antes de que podamos enviar un correo electrónico, debemos permitir que SES lo haga. En serverless.yml, debajo provider.iamRoleStatements
, agregue el permiso.
# serverless.yml provider: name: aws runtime: nodejs6.10 iamRoleStatements: - Effect: "Allow" Action: - "ses:SendEmail" Resource: ["*"]
Dado que necesitamos una URL para nuestra acción de formulario, debemos agregar eventos HTTP a nuestra función. En serverless.yml creamos una ruta, especificamos el método como post
y establecemos CORS en verdadero por seguridad.
functions: staticSiteMailer: handler: handler.staticSiteMailer events: - http: method: post path: static-site-mailer cors: true
Nuestros archivos serverless.yml y handler.js actualizados deberían verse así:
# 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); };
Nuestra arquitectura sin servidor está configurada, así que vamos a implementarla y probarla. Obtendrá una respuesta JSON simple.
yarn sls deploy --verbose yarn sls invoke --function staticSiteMailer { "statusCode": 200, "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}" }
Crear el formulario HTML
La entrada de nuestra función Lambda y la salida del formulario deben coincidir, por lo que antes de compilar la función, compilaremos el formulario y capturaremos su salida. Lo mantenemos simple con campos de nombre, correo electrónico y mensaje. Agregaremos la acción del formulario una vez que hayamos implementado nuestra arquitectura sin servidor y obtenido nuestra URL, pero sabemos que será una solicitud POST para que podamos agregarla. Al final del formulario, agregamos una etiqueta de párrafo para mostrar mensajes de respuesta al usuario que actualizaremos en la devolución de llamada de envío.
<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 el resultado, agregamos un controlador de envío al formulario, convertimos nuestros parámetros de formulario en un objeto y enviamos JSON en cadena a nuestra función Lambda. En la función Lambda usamos JSON.parse()
para leer nuestros datos. Alternativamente, puede usar Serialize o query-string de jQuery para enviar y analizar los parámetros del formulario como una cadena de consulta, pero JSON.stringify()
y JSON.parse()
son 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)); }; })();
Continúe y envíe su formulario y luego capture la salida de la consola. Lo usaremos en nuestra función Lambda a continuación.
Invocación de funciones de Lambda
Especialmente durante el desarrollo, necesitamos probar que nuestra función hace lo que esperamos. Serverless Framework proporciona el comando de invoke
e invoke local
para activar su función desde entornos en vivo y de desarrollo , respectivamente. Ambos comandos requieren que se pase el nombre de la función, en nuestro caso staticSiteMailer
.
yarn sls invoke local --function staticSiteMailer
Para pasar datos simulados a nuestra función, cree un nuevo archivo llamado data.json
con la salida de la consola capturada bajo una clave de body
dentro de un objeto JSON. Debería verse algo como:
// data.json { "body": "{\"name\": \"Sender Name\",\"reply_to\": \"[email protected]\",\"message\": \"Sender message\"}" }
Para invocar la función con los datos locales, pase el argumento --path
junto con la ruta al archivo.
yarn sls invoke local --function staticSiteMailer --path data.json
Verá una respuesta similar a la anterior, pero la tecla de input
contendrá el evento del que nos burlamos. ¡Usemos nuestros datos simulados para enviar un correo electrónico usando Simple Email Service!
Enviar un correo electrónico con el servicio de correo electrónico simple
Vamos a reemplazar la función staticSiteMailer
con una llamada a una función sendEmail
privada. Por ahora, puede comentar o eliminar el código de la plantilla y reemplazarlo con:
// 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); } }); };
Primero, analizamos el cuerpo del event.body
para capturar los datos del formulario, luego lo pasamos a una función privada de envío de sendEmail
electrónico. sendEmail
es responsable de enviar el correo electrónico, y la función de devolución de llamada devolverá una respuesta de error o éxito con err
o data
. En nuestro caso, simplemente podemos registrar el error o los datos, ya que lo reemplazaremos con la devolución de llamada de Lambda en un momento.
Amazon proporciona un SDK conveniente, aws-sdk
, para conectar sus servicios con las funciones de Lambda. Muchos de sus servicios, incluido SES, forman parte de él. Lo agregamos al proyecto con yarn add aws-sdk
y lo importamos en la parte superior del archivo del controlador.
// handler.js const AWS = require('aws-sdk'); const SES = new AWS.SES();
En nuestra función sendEmail
privada, construimos los parámetros SES.sendEmail
a partir de los datos del formulario analizados y usamos la devolución de llamada para devolver una respuesta a la persona que llama. Los parámetros requieren lo siguiente como objeto:
- Fuente
La dirección de correo electrónico desde la que envía SES. - Direcciones de respuesta
Una matriz de direcciones de correo electrónico agregadas al campo de respuesta en el correo electrónico. - Destino
Un objeto que debe contener al menos una ToAddresses , CcAddresses o BccAddresses . Cada campo toma una matriz de direcciones de correo electrónico que corresponden a los campos para , cc y bcc respectivamente. - Mensaje
Un objeto que contiene el Cuerpo y el Sujeto .
Dado que formData
es un objeto, podemos llamar a nuestros campos de formulario directamente como formData.message
, crear nuestros parámetros y enviarlos. Pasamos su correo electrónico verificado por SES a Source
y Destination.ToAddresses
. Siempre que el correo electrónico esté verificado, puede pasar cualquier cosa aquí, incluidas diferentes direcciones de correo electrónico. Extraemos nuestro reply_to
, message
y name
de nuestro objeto formData
para completar los campos ReplyToAddresses
y 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á el correo electrónico y nuestra devolución de llamada devolverá una respuesta. Al invocar la función local, se enviará un correo electrónico a su dirección verificada.
yarn sls invoke local --function staticSiteMailer --path data.json
Devolver una respuesta del controlador
Nuestra función envía un correo electrónico usando la línea de comando, pero no es así como nuestros usuarios interactuarán con ella. Necesitamos devolver una respuesta a nuestro envío de formulario AJAX. Si falla, deberíamos devolver un código de err.message
statusCode
Cuando tiene éxito, el statusCode
de estado 200
es suficiente, pero también devolveremos la respuesta del correo en el cuerpo. En staticSiteMailer
construimos nuestros datos de respuesta y reemplazamos nuestra función de devolución de llamada sendEmail
con la devolución de llamada de 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); }); };
Nuestra devolución de llamada de Lambda ahora devuelve mensajes de éxito y de error de SES.sendEmail
. Creamos la respuesta comprobando si err
está presente para que nuestra respuesta sea consistente. La propia función de devolución de llamada de Lambda pasa null
en el campo de argumento de error y la respuesta como segundo. Queremos pasar errores en adelante, pero si Lambda falla, su devolución de llamada se llamará implícitamente con la respuesta de error.
En los headers
, deberá reemplazar Access-Control-Allow-Origin
con su propio dominio. ¡Esto evitará que otros dominios utilicen su servicio y potencialmente acumulen una factura de AWS a su nombre! Y no lo cubro en este artículo, pero es posible configurar Lambda para usar su propio dominio. Deberá tener un certificado SSL/TLS cargado en Amazon. El equipo de Serverless Framework escribió un fantástico tutorial sobre cómo hacerlo.
Invocar la función local ahora enviará un correo electrónico y devolverá la respuesta adecuada.
yarn sls invoke local --function staticSiteMailer --path data.json
Llamar a la función Lambda desde el formulario
¡Nuestro servicio es completo! Para implementarlo, ejecute yarn sls deploy -v
. Una vez que se implemente, obtendrá una URL similar a https://r4nd0mh45h.execute-api.us-east-1.amazonaws.com/dev/static-site-mailer
que puede agregar a la acción del formulario. A continuación, creamos la solicitud AJAX y devolvemos la respuesta al usuario.
(() => { 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); } }; }; })();
En la devolución de llamada de AJAX, verificamos el código de estado con response.target.status
. Si es diferente a 200
, podemos mostrar un mensaje de error al usuario; de lo contrario, infórmele que se envió el mensaje. Dado que nuestro Lambda devuelve JSON en cadena, podemos analizar el mensaje del cuerpo con JSON.parse(response.target.response).message
. Es especialmente útil para registrar el error.
¡Debería poder enviar su formulario completamente desde su sitio estático!
Próximos pasos
Agregar un formulario de contacto a su estática es fácil con Serverless Framework y AWS. Hay margen de mejora en nuestro código, como agregar validación de formularios con un honeypot, evitar llamadas AJAX para formularios no válidos y mejorar la experiencia de usuario si la respuesta, pero esto es suficiente para comenzar. Puede ver algunas de estas mejoras en el repositorio de correo del sitio estático que he creado. ¡Espero haberte inspirado para que pruebes Serverless tú mismo!