Adding a contact form to an Eleventy site via AWS


For this site, I write all demos and blog posts in Markdown format and then use Eleventy to convert them into web pages.

Since Eleventy generates static HTML, I needed to use a third-party service or serverless function to handle the form submission.

I recently completed my AWS Certified Cloud Practitioner training, so I used this as an opportunity to practice my AWS skills.

The basic steps were:

Testing

AWS provides a nice test interface for the lambda function itself. I also tested the end-to-end procuss using the HTML form I created (see below).

I also wrote 2 tests using curl to help debug the lambda function and the API. One uses JSON data, while the other used an encoded multi-part form.

curl -X POST "$ENDPOINT" \
  -H 'Content-Type: application/json' \
  -d '{"name": "John Doe", "email": "john.doe@example.com", "message": "Hello, this is a test message."}'

echo

curl -X POST "$ENDPOINT" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'name=Alice' \
  --data-urlencode 'email=alice@example.com' \
  --data-urlencode 'message=Hello, this from Alice.'

Lambda function

Below is the final python script, which parses the incoming payload then sends an email via the boto3 library.

import json
from base64 import b64decode
from urllib.parse import parse_qs, unquote_plus
import boto3

client = boto3.client('ses', region_name='us-east-1')

def lambda_handler(event, context):
    # Parse the incoming payload
    if event.get("isBase64Encoded"):
        body = b64decode(event['body']).decode('utf-8')  # decode base64 string
        body = unquote_plus(body)  # decode the percent-encoded characters and + signs
        body = parse_qs(body)      # to dict
    elif isinstance(event["body"], dict):
        body = event["body"]
    else:
        body = json.loads(event['body'])

    name = body['name']
    email = body['email']
    message = body['message']

    print(f"Name: {name}, Email: {email}, Message: {message}")

    msg = f"name: {name}\n"
    msg += f"email: {email}\n"
    msg += f"message: {message}"

    response = client.send_email(
        Destination={
            'ToAddresses': [MyPersonalEmail]
        },
        Message={
            'Body': {
                'Text': {
                    'Charset': 'UTF-8',
                    'Data': msg,
                }
            },
            'Subject': {
                'Charset': 'UTF-8',
                'Data': 'Contact form received from jeremyschaub.us',
            },
        },
        Source='MyPersonalEmail'
    )

    html_content = """
    <html>
        <head>
            <title>Confirmation</title>
        </head>
        <body>
            <p>Thank you for reaching out! I'll be in touch within a day or two.</p>
            <p> Return to <a href="https://jeremyschaub.us">Jeremy's site</a>.
        </body>
    </html>
    """

    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'text/html'
        },
        'body': html_content
    }

Contact form

The contact form is simple enough.

---
title: Contact Me
layout: layout.html
permalink: contact.html
---

<form id="contact-form" action="https://tseww1okmf.execute-api.us-east-1.amazonaws.com/submit" method="POST">
<label for="name">Name:</label><br>
<input type="text" id="name" name="name" class="jds-form" required><br>

<label for="email">Email:</label><br>
<input type="email" id="email" name="email" required><br>

<label for="message">Message:</label><br>
<textarea id="message" name="message" required></textarea><br>

<input type="submit" value="Submit">
</form>

Currently, this sends you to the AWS API endpoint, which displays a message that redirects you back to my site. Contact

It would be cleaner if I implemented this via client-side javascript, so I may do that in the future.

I also need to implement a CAPTCHA or alternative so to reduce spam when I add my site to the popular search engines for indexing. I like the simple Cloudflare Turnstile, which we use on the ISSI site.