Hugo, AWS Amplify and a sneaky Lambda backend…

Seán Murphy
4 min readFeb 28, 2021

In a previous post, I described how I set up a Hugo site on AWS Amplify. One feature missing from this basic site is a contact form. Here, I consider how this can be implemented in an AWS context.

A github repo with related content is here.

Backend options

One aspect of AWS Amplify is that it can easily provide a managed backend for an Amplify served frontend; there are two primary options: (i) a REST backend or (ii) a GraphQL backend. Obviously, for this simple case a GraphQL backend was not appropriate.

AWS recommends not using the REST API provided by Amplify for anything other than basic experimentation; hence I decided set up a basic Lambda function behind an API Gateway endpoint which receives a message and an email address and forwards it on via the Simple Email Service (SES).

Hugo and Forms

The last post focused on ensuring that Hugo modules are supported; this becomes useful now as The New Dynamic has created a forms module for Hugo here. This module provides support for Netlify and Formspree out of the box where all that is necessary is to provide an identifier for the form in their respective systems and the appropriate content will be generated for the frontend to post to the appropriate form on the backend.

While Netlify and Formspree provided excellent forms support, I wanted to roll my own just for fun.

Implementing a form backend with a Lambda function

Implementing a form backend in a Lambda function is straightforward: usually a form just makes a POST to a backend with the form data containing the fields and the data in a string of the form field~=value1&field2=value2etc. All that was necessary then was to set up a Lambda function which could handle this.

Mail system integration is of course tricky in general; AWS provides SES as their email sending solution; SES is very programmable as would be expected and only the simplest functionalities were required in this case.

One important issue with SES is that AWS approval is required to send emails to arbitrary addresses; it can operate in so-called sandbox mode in which sending to verified addresses is permitted without this approval (usually for testing)— luckily that was sufficient in this case. I set up one email recipient, verified it via SES and all form submissions trigger an email just to that address.

The implementation uses the standard go Lambda pattern in which the main entry point is a handler which takes a general event as an input; this is transformed to a specific event generated by the HTTP API using the APIGatewayV2HTTPRequest and the submitted data is extracted from this. This data is then put into an email which is sent to the (hard-coded) email address. The response includes a Locationheaders for redirecting the source, typically to a ‘Thank you for your interest’ page.

Backend Deployment

Deployment of the backend was performed with the Serverless Application Model (SAM); for this simple case, SAM can infer much of the configuration resulting in the following short configuration file:

AWSTemplateFormatVersion: '2010-09-09'
Description: AWS SAM template with a simple API definition
Transform: AWS::Serverless-2016-10-31
Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
CodeUri: ./backend.zip
Handler: backend
Runtime: go1.x
Policies: AmazonSESFullAccess

This sets up quite a lot of API Gateway configuration, including stage configuration and endpoint and integration configuration. It is also necessary to give the Lambda function access to SES; in this case full access to SES is given.

The backend can easily be tested as follows:

$ curl -X POST https://<endpoint>.execute-api.eu-west-1.amazonaws.com/contact -d 'email=tom@test.com&name=Tom&message=hello&_next=return'
{"message": "Will respond to email addr asap..."}

Frontend changes

With a working backend, it was now possible to make minor changes to the frontend to get the system working. The design of the Hugo module is such that there is a notion of a form provider which currently can be either Netlify or Formspree; it was necessary to add a new form provider for this case. The form provider contains data and logic specific to the form backend used, eg in the case of Formspree, an id associated with the (pre-configured) form.

In this case, the following simple changes were necessary:

  • add a new action clause specific to the new form provider — this generates HTML specific to this provider which in this case simply points the frontend to the correct endpoint. This was implemented as some logic additional to the provided Hugo forms module
  • create a form using the same approach as for other form but add a field which shows the endpoint that should be used for posting to Amplify

Repo organization

The repo for this project contains both a backend and a frontend. The frontenddirectory contains the Hugo website so the Amplify instructions for publishing the website need to point to this directory.

Final thoughts

I thought it might be possible to set up a very simple, cheap contact form backend using Amplify; Amplify’s backend is not recommended for this but setting up a Lambda function to which contact data can be sent was straightforward. The limitations of SES mean that it is probably most sensible to use a form provider for most use cases— the Formspree low-cost for low-traffic model is attractive and offers a much more comprehensive solution (spam handling, history views etc). The solution described here be interesting if there are some quite specific integrations required which are not possible with other form providers.

To look into this in more detail, check out the github repo here.

--

--

Seán Murphy

Tech Tinkerer, Curious Thinker(er). Lost Leprechaun. Always trying to improve.