Créer un formulaire de contact sans serveur pour votre site statique

Publié: 2022-03-10
Résumé rapide ↬ Grâce à cet article, vous allez enfin pouvoir apprendre les bases des API Amazon Web Services (AWS) Lambda et Simple Email Service (SES) pour vous aider à construire votre propre mailer de site statique sur le Serverless Framework. Commençons!

Les générateurs de sites statiques offrent une alternative simple et rapide aux systèmes de gestion de contenu (CMS) comme WordPress. Il n'y a pas de configuration de serveur ou de base de données, juste un processus de construction et de simples HTML, CSS et JavaScript. Malheureusement, sans serveur, il est facile d'atteindre rapidement ses limites. Par exemple, en ajoutant un formulaire de contact.

Avec l'essor de l'architecture sans serveur, l'ajout d'un formulaire de contact à votre site statique n'a plus besoin d'être la raison de passer à un CMS. Il est possible d'obtenir le meilleur des deux mondes : un site statique avec un back-end sans serveur pour le formulaire de contact (que vous n'avez pas besoin de maintenir). Peut-être le meilleur de tous, dans les sites à faible trafic, comme les portefeuilles, les limites élevées de nombreux fournisseurs sans serveur rendent ces services totalement gratuits !

Dans cet article, vous apprendrez les bases des API Amazon Web Services (AWS) Lambda et Simple Email Service (SES) pour créer votre propre messagerie de site statique sur Serverless Framework. Le service complet prendra les données de formulaire soumises à partir d'une requête AJAX, frappera le point de terminaison Lambda, analysera les données pour créer les paramètres SES, enverra l'adresse e-mail et renverra une réponse à nos utilisateurs. Je vais vous guider dans la configuration de Serverless pour la première fois via le déploiement. Cela devrait prendre moins d'une heure, alors commençons !

Le formulaire de site statique, envoyant le message au point de terminaison Lambda et renvoyant une réponse à l'utilisateur.
Le formulaire de site statique, envoyant le message au point de terminaison Lambda et renvoyant une réponse à l'utilisateur.
Plus après saut! Continuez à lire ci-dessous ↓

Mise en place

Il existe des prérequis minimaux pour démarrer avec la technologie sans serveur. Pour nos besoins, il s'agit simplement d'un environnement de nœud avec Yarn, du Serverless Framework et d'un compte AWS.

Configuration du projet

Le site Web de l'infrastructure sans serveur. Utile pour l'installation et la documentation.
Le site Web de l'infrastructure sans serveur. Utile pour l'installation et la documentation.

Nous utilisons Yarn pour installer le Serverless Framework dans un répertoire local.

  1. Créez un nouveau répertoire pour héberger le projet.
  2. Accédez au répertoire dans votre interface de ligne de commande.
  3. Exécutez yarn init pour créer un fichier package.json pour ce projet.
  4. Exécutez yarn add serverless pour installer le framework localement.
  5. Exécutez yarn serverless create --template aws-nodejs --name static-site-mailer pour créer un modèle de service Node et nommez-le static-site-mailer .

Notre projet est configuré mais nous ne pourrons rien faire tant que nous n'aurons pas configuré nos services AWS.

Configuration d'un compte Amazon Web Services, d'informations d'identification et d'un service de messagerie simple

La page d'inscription d'Amazon Web Services, qui comprend un niveau gratuit généreux, permettant à notre projet d'être entièrement gratuit.
La page d'inscription d'Amazon Web Services, qui comprend un niveau gratuit généreux, permettant à notre projet d'être entièrement gratuit.

Le Serverless Framework a enregistré une vidéo pas à pas pour configurer les informations d'identification AWS, mais j'ai également répertorié les étapes ici.

  1. Créez un compte AWS ou connectez-vous si vous en avez déjà un.
  2. Dans la barre de recherche AWS, recherchez « IAM ».
  3. Sur la page IAM, cliquez sur "Utilisateurs" dans la barre latérale, puis sur le bouton "Ajouter un utilisateur".
  4. Sur la page Ajouter un utilisateur, donnez un nom à l'utilisateur - quelque chose comme "sans serveur" est approprié. Cochez « Accès par programme » sous Type d'accès, puis cliquez sur Suivant.
  5. Sur l'écran des autorisations, cliquez sur l'onglet "Attacher directement les stratégies existantes", recherchez "AdministratorAccess" dans la liste, cochez-le et cliquez sur suivant.
  6. Sur l'écran de révision, vous devriez voir votre nom d'utilisateur, avec "Accès programmatique" et "Accès administrateur", puis créez l'utilisateur.
  7. L'écran de confirmation affiche l'utilisateur "Access key ID" et "Secret access key", vous en aurez besoin pour fournir l'accès au Serverless Framework. Dans votre CLI, tapez yarn sls config credentials --provider aws --key YOUR_ACCESS_KEY_ID --secret YOUR_SECRET_ACCESS_KEY , en remplaçant YOUR_ACCESS_KEY_ID et YOUR_SECRET_ACCESS_KEY par les clés sur l'écran de confirmation.

Vos informations d'identification sont maintenant configurées, mais pendant que nous sommes dans la console AWS, configurons Simple Email Service.

  1. Cliquez sur Console Home dans le coin supérieur gauche pour revenir à la maison.
  2. Sur la page d'accueil, dans la barre de recherche AWS, recherchez « Simple Email Service ».
  3. Sur la page d'accueil de SES, cliquez sur "Adresses e-mail" dans la barre latérale.
  4. Sur la page de liste des adresses e-mail, cliquez sur le bouton "Vérifier une nouvelle adresse e-mail".
  5. Dans la fenêtre de dialogue, tapez votre adresse e-mail puis cliquez sur "Vérifier cette adresse e-mail".
  6. Vous recevrez un e-mail dans quelques instants contenant un lien pour vérifier l'adresse. Cliquez sur le lien pour terminer le processus.

Maintenant que nos comptes sont créés, jetons un coup d'œil aux fichiers de modèle Serverless.

Configuration du framework sans serveur

L'exécution serverless create crée deux fichiers : handler.js qui contient la fonction Lambda et serverless.yml qui est le fichier de configuration pour l'ensemble de l'architecture sans serveur. Dans le fichier de configuration, vous pouvez spécifier autant de gestionnaires que vous le souhaitez, et chacun correspondra à une nouvelle fonction qui peut interagir avec d'autres fonctions. Dans ce projet, nous ne créerons qu'un seul gestionnaire, mais dans une architecture sans serveur complète, vous auriez plusieurs des différentes fonctions du service.

La structure de fichiers par défaut générée à partir de Serverless Framework contenant handler.js et serverless.yml.
La structure de fichiers par défaut générée à partir de Serverless Framework contenant handler.js et serverless.yml.

Dans handler.js, vous verrez une seule fonction exportée nommée hello . C'est actuellement la fonction principale (et unique). Il, avec tous les gestionnaires de nœuds, prend trois paramètres :

  • event
    Cela peut être considéré comme les données d'entrée de la fonction.
  • context object
    Il contient les informations d'exécution de la fonction Lambda.
  • callback
    Un paramètre facultatif pour renvoyer des informations à l'appelant.
 // 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); };

Au bas de hello , il y a un rappel. C'est un argument facultatif pour retourner une réponse, mais s'il n'est pas explicitement appelé, il retournera implicitement avec null . Le rappel prend deux paramètres :

  • Erreur erreur
    Pour fournir des informations d'erreur en cas d'échec de Lambda lui-même. Lorsque Lambda réussit, null doit être transmis à ce paramètre.
  • Résultat d'objet
    Pour fournir un objet de réponse. Il doit être compatible JSON.stringify . S'il y a un paramètre dans le champ d'erreur, ce champ est ignoré.

Notre site statique enverra nos données de formulaire dans le corps de l'événement et le rappel renverra une réponse que notre utilisateur pourra voir.

Dans serverless.yml, vous verrez le nom du service, les informations sur le fournisseur et les fonctions.

 # serverless.yml service: static-site-mailer provider: name: aws runtime: nodejs6.10 functions: hello: handler: handler.hello 
Comment les noms de fonction dans serverless.yml correspondent à handler.js.
Comment les noms de fonction dans serverless.yml correspondent à handler.js.

Remarquez le mappage entre la fonction hello et le gestionnaire ? Nous pouvons nommer notre fichier et fonctionner n'importe quoi et tant qu'il correspond à la configuration, cela fonctionnera. Renommez notre fonction en staticSiteMailer .

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

Les fonctions Lambda ont besoin d'une autorisation pour interagir avec d'autres infrastructures AWS. Avant de pouvoir envoyer un e-mail, nous devons autoriser SES à le faire. Dans serverless.yml, sous provider.iamRoleStatements , ajoutez l'autorisation.

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

Comme nous avons besoin d'une URL pour notre action de formulaire, nous devons ajouter des événements HTTP à notre fonction. Dans serverless.yml, nous créons un chemin, spécifions la méthode post et définissons CORS sur true pour la sécurité.

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

Nos fichiers serverless.yml et handler.js mis à jour devraient ressembler à :

 # 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); };

Notre architecture sans serveur est configurée, alors déployons-la et testons-la. Vous obtiendrez une réponse JSON simple.

 yarn sls deploy --verbose yarn sls invoke --function staticSiteMailer { "statusCode": 200, "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}" } 
La réponse de retour après avoir invoqué notre toute nouvelle fonction sans serveur.
La réponse de retour après avoir invoqué notre toute nouvelle fonction sans serveur.

Création du formulaire HTML

L'entrée de notre fonction Lambda et la sortie du formulaire doivent correspondre, donc avant de créer la fonction, nous allons créer le formulaire et capturer sa sortie. Nous gardons les choses simples avec les champs de nom, d'e-mail et de message. Nous ajouterons l'action de formulaire une fois que nous aurons déployé notre architecture sans serveur et obtenu notre URL, mais nous savons qu'il s'agira d'une requête POST afin que nous puissions l'ajouter. À la fin du formulaire, nous ajoutons une balise de paragraphe pour afficher messages de réponse à l'utilisateur que nous mettrons à jour lors du rappel de soumission.

 <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>

Pour capturer la sortie, nous ajoutons un gestionnaire de soumission au formulaire, transformons nos paramètres de formulaire en objet et envoyons du JSON sous forme de chaîne à notre fonction Lambda. Dans la fonction Lambda, nous utilisons JSON.parse() pour lire nos données. Alternativement, vous pouvez utiliser Serialize ou query-string de jQuery pour envoyer et analyser les paramètres de formulaire en tant que chaîne de requête, mais JSON.stringify() et JSON.parse() sont natifs.

 (() => { 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)); }; })();

Allez-y et soumettez votre formulaire, puis capturez la sortie de la console. Nous l'utiliserons ensuite dans notre fonction Lambda.

Capture des données du formulaire dans un journal de la console.
Capture des données du formulaire dans un journal de la console.

Appel des fonctions Lambda

Surtout pendant le développement, nous devons tester que notre fonction fait ce que nous attendons. Le invoke Framework fournit la commande d'appel et invoke local pour déclencher votre fonction à partir d'environnements en direct et de développement respectivement. Les deux commandes nécessitent le nom de la fonction transmis, dans notre cas staticSiteMailer .

 yarn sls invoke local --function staticSiteMailer

Pour transmettre des données fictives à notre fonction, créez un nouveau fichier nommé data.json avec la sortie de la console capturée sous une clé de body dans un objet JSON. Cela devrait ressembler à quelque chose comme :

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

Pour invoquer la fonction avec les données locales, transmettez l'argument --path avec le chemin d'accès au fichier.

 yarn sls invoke local --function staticSiteMailer --path data.json 
Une réponse de retour mise à jour de notre fonction sans serveur lorsque nous lui transmettons des données JSON.
Une réponse de retour mise à jour de notre fonction sans serveur lorsque nous lui transmettons des données JSON.

Vous verrez une réponse similaire à avant, mais la clé input contiendra l'événement dont nous nous sommes moqués. Utilisons nos données fictives pour envoyer un e-mail à l'aide de Simple Email Service !

Envoi d'un e-mail avec un service de messagerie simple

Nous allons remplacer la fonction staticSiteMailer par un appel à une fonction privée sendEmail . Pour l'instant, vous pouvez commenter ou supprimer le code du modèle et le remplacer par :

 // 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); } }); };

Tout d'abord, nous event.body pour capturer les données du formulaire, puis nous le transmettons à une fonction sendEmail privée. sendEmail est responsable de l'envoi de l'e-mail, et la fonction de rappel renverra une réponse d'échec ou de succès avec err ou data . Dans notre cas, nous pouvons simplement consigner l'erreur ou les données puisque nous les remplacerons par le rappel Lambda dans un instant.

Amazon fournit un SDK pratique, aws-sdk , pour connecter leurs services aux fonctions Lambda. Beaucoup de leurs services, dont SES, en font partie. Nous l'ajoutons au projet avec yarn add aws-sdk et l'importons en haut du fichier de gestionnaire.

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

Dans notre fonction privée sendEmail , nous construisons les paramètres SES.sendEmail à partir des données de formulaire analysées et utilisons le rappel pour renvoyer une réponse à l'appelant. Les paramètres nécessitent les éléments suivants en tant qu'objet :

  • La source
    L'adresse e-mail à partir de laquelle SES envoie .
  • Répondre aux adresses
    Un tableau d'adresses e-mail ajoutées à la réponse au champ dans l'e-mail.
  • Destination
    Un objet qui doit contenir au moins un ToAddresses , CcAddresses ou BccAddresses . Chaque champ prend un tableau d'adresses e-mail qui correspondent respectivement aux champs to , cc et bcc .
  • Un message
    Un objet qui contient le Body et le Subject .

Puisque formData est un objet, nous pouvons appeler nos champs de formulaire directement comme formData.message , créer nos paramètres et les envoyer. Nous transmettons votre e-mail vérifié par SES à Source et Destination.ToAddresses . Tant que l'e-mail est vérifié, vous pouvez transmettre n'importe quoi ici, y compris différentes adresses e-mail. Nous cueillons notre reply_to , message et name notre objet formData pour remplir les champs ReplyToAddresses et 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 enverra l'e-mail et notre rappel renverra une réponse. L'appel de la fonction locale enverra un e-mail à votre adresse vérifiée.

 yarn sls invoke local --function staticSiteMailer --path data.json 
La réponse renvoyée par SES.sendEmail en cas de réussite.
La réponse renvoyée par SES.sendEmail en cas de réussite.

Renvoyer une réponse du gestionnaire

Notre fonction envoie un e-mail en utilisant la ligne de commande, mais ce n'est pas ainsi que nos utilisateurs interagiront avec elle. Nous devons renvoyer une réponse à notre soumission de formulaire AJAX. Si cela échoue, nous devrions retourner un statusCode approprié ainsi que le err.message . Lorsqu'il réussit, le statusCode 200 est suffisant, mais nous renverrons également la réponse de l'expéditeur dans le corps. Dans staticSiteMailer nous créons nos données de réponse et remplaçons notre fonction de rappel sendEmail par le rappel 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); }); };

Notre rappel Lambda renvoie désormais les messages de réussite et d'échec de SES.sendEmail . Nous construisons la réponse en vérifiant si err est présent afin que notre réponse soit cohérente. La fonction de rappel Lambda elle-même transmet null dans le champ d'argument d'erreur et la réponse en second. Nous voulons transmettre les erreurs, mais si Lambda lui-même échoue, son rappel sera implicitement appelé avec la réponse d'erreur.

Dans les en- headers , vous devrez remplacer Access-Control-Allow-Origin par votre propre domaine. Cela empêchera tout autre domaine d'utiliser votre service et d'accumuler potentiellement une facture AWS à votre nom ! Et je ne le couvre pas dans cet article, mais il est possible de configurer Lambda pour utiliser votre propre domaine. Vous aurez besoin d'avoir un certificat SSL/TLS chargé sur Amazon. L'équipe Serverless Framework a écrit un didacticiel fantastique sur la façon de procéder.

L'appel de la fonction locale enverra désormais un e-mail et renverra la réponse appropriée.

 yarn sls invoke local --function staticSiteMailer --path data.json 
La réponse de retour de notre fonction sans serveur, contenant la réponse de retour SES.sendEmail dans le corps.
La réponse de retour de notre fonction sans serveur, contenant la réponse de retour SES.sendEmail dans le corps.

Appel de la fonction Lambda à partir du formulaire

Notre service est complet ! Pour le déployer, exécutez yarn sls deploy -v . Une fois déployé, vous obtiendrez une URL qui ressemble à quelque chose comme https://r4nd0mh45h.execute-api.us-east-1.amazonaws.com/dev/static-site-mailer que vous pouvez ajouter à l'action du formulaire. Ensuite, nous créons la requête AJAX et renvoyons la réponse à l'utilisateur.

 (() => { 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); } }; }; })();

Dans le rappel AJAX, nous vérifions le code d'état avec response.target.status . Si c'est autre chose que 200 , nous pouvons afficher un message d'erreur à l'utilisateur, sinon faites-lui savoir que le message a été envoyé. Étant donné que notre Lambda renvoie un JSON sous forme de chaîne, nous pouvons analyser le corps du message avec JSON.parse(response.target.response).message . Il est particulièrement utile de consigner l'erreur.

Vous devriez pouvoir soumettre votre formulaire entièrement à partir de votre site statique !

Le formulaire de site statique, envoyant le message au point de terminaison Lambda et renvoyant une réponse à l'utilisateur.
Le formulaire de site statique, envoyant le message au point de terminaison Lambda et renvoyant une réponse à l'utilisateur.

Prochaines étapes

Ajouter un formulaire de contact à votre statique est facile avec Serverless Framework et AWS. Il y a place à amélioration dans notre code, comme l'ajout d'une validation de formulaire avec un pot de miel, la prévention des appels AJAX pour les formulaires invalides et l'amélioration de l'UX si la réponse, mais cela suffit pour commencer. Vous pouvez voir certaines de ces améliorations dans le référentiel de messagerie de site statique que j'ai créé. J'espère que je vous ai inspiré à essayer Serverless vous-même !