Ioannis Ioannou
HomeAbout MeContact

Create a contact form for a static website

Ioannis Ioannou
Ioannis Ioannou
May 02, 2021
2 min
Create a contact form for a static website

Hosting a static website is easier, cheaper, and faster than a traditional hosted website. Unfortunately, though, most static websites need to provide some basic dynamic features like a contact form. There are many available services which provide this functionality, but in this post we will create our own AWS serverless solution, using API Gateway, a lambda function, and SES, to forward the form details to our email.


Let’s examine the architecture of the solution first.

Architecture diagram

We have a static website hosted in S3 and served using CloudFront. The static website has a contact form, which will be submitted to the API Gateway, which will then forward all information to a lambda function. The lambda will then process the request and email us the form details using SES.

Contact Form

We have a very simple React contact form. It collects the name, email, and message of the user and submits it to our serverless API.

import { useState } from "react";
import axios from 'axios';

const ContactForm = () => {

  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");

  const [submitting, setSubmitting] = useState(false);

  const onSubmit = async (e) => {
    try {
      await'', {
      alert("Successfully submitted form");
    } catch (e) {
      alert("Something went wrong while submitting the form");
      console.error("Something went wrong while submitting the form", e);
    } finally {

  return (
      <form onSubmit={onSubmit}>
          <input type="text" required value={name} onChange={e => setName(} />
        <br />
          <input type="email" required value={email} onChange={e => setEmail(} />
        <br />
          <textarea required value={message} onChange={e => setMessage(} />
        <br />
        <input type="submit" value="Submit" disabled={submitting} />

Serverless API

As mentioned above, the serverless API will consist of the API Gateway, which will define our REST API, and the lambda function which will process the event forwarded by the API Gateway and email us using SES.

Create the lambda function

First, we will create a node.js lambda function.

Create lambda

The function parses the body of the event, then does some basic validation and then sends an email using the AWS SDK.

const AWS = require('aws-sdk');
const ses = new AWS.SES();

const MAIL_SENDER = process.env.MAIL_SENDER;
const MAIL_RECEIVER = process.env.MAIL_RECEIVER;

exports.handler = async (event) => {
    const body = JSON.parse(event.body);
    if (! || ! || !body.message) {
        return {
            statusCode: 400,
            body: JSON.stringify({
                error: true,
                message: 'Incorrect input parameters'
    await sendEmail(body);
    return {
        statusCode: 200,
        body: JSON.stringify({})

const sendEmail = (body) => {
    const params = {
        Destination: {
            ToAddresses: [
        Message: {
            Body: {
                Text: {
                    Data: 'name: ' + + '\nemail: ' + + '\nmessage: ' + body.message,
                    Charset: 'UTF-8'
            Subject: {
                Data: 'Website Contact Form',
                Charset: 'UTF-8'
        Source: MAIL_SENDER
    return ses.sendEmail(params).promise();

For the mail sender and receiver we use environmental variables, so we need to configure them. We can do that by going to the configuration tab.

Configure environmental variables

An important note here is that we need to verify the source email in order to be able to use it to send emails. We can do that by vising SES service, clicking domains and then verify a new domain. We need to add few DNS records to prove that the domain belongs to us.

Verify email sender

We also need to give permissions to the lambda function, so it can send emails. When we create a lambda function, a role is automatically assigned to it. We can find the assigned role by looking at the lambda configuration.

Lambda role

We then need to add a permission to the role.

    "Version": "2012-10-17",
    "Statement": [
            "Effect": "Allow",
            "Action": "ses:SendEmail",
            "Resource": "*"

Create the API Gateway

Now that we created the lambda function, we need to create our API, which will accept our requests. To do this, we need to go to the API Gateway and create a new API. We will use the HTTP API option here given that is cheaper. You can learn about the difference of HTTP and REST options here.

Following the instructions, we create an API Gateway with one route, /contact, which targets our lambda.

Create API Gateway

Configure routes

Define stages

Create API Gateway summary

Before we are able to use our newly created API, we need to configure CORS settings. Given that our website domain is different from our API domain, we need to allow requests from the website domain. Otherwise, the browser will block such requests. An example is given below where the website uses domain.

Configure CORS

Given that we configured manual deployments for the PROD stage, we need to click deploy after every change to take effect.

Some additional nice-to-have configurations include throttling requests to protect the API and the use of a custom domain.


We are ready to test our API. We can copy the URL of the PROD stage of the API Gateway, append /contact, and use the resulting URL as the destination of the POST request of the form. We should receive an email with the form details.



In this post, we created a serverless AWS solution to provide a contact me functionality to a static website. To achieve this, we used API Gateway to define our API, a lambda function to process the message, and SES to forward the form details to our email.


Ioannis Ioannou © 2021