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.
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.
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) => { e.preventDefault(); try { setSubmitting(true); await axios.post('https://api.example.com/contact', { name, email, message }); 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 { setName(''); setEmail(''); setMessage(''); setSubmitting(false); } } return ( <> <h1>Contact</h1> <form onSubmit={onSubmit}> <label> Name: <input type="text" required value={name} onChange={e => setName(e.target.value)} /> </label> <br /> <label> Email: <input type="email" required value={email} onChange={e => setEmail(e.target.value)} /> </label> <br /> <label> Message: <textarea required value={message} onChange={e => setMessage(e.target.value)} /> </label> <br /> <input type="submit" value="Submit" disabled={submitting} /> </form> </> ); }
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.
First, we will create a node.js lambda function.
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.name || !body.email || !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: [ MAIL_RECEIVER ] }, Message: { Body: { Text: { Data: 'name: ' + body.name + '\nemail: ' + body.email + '\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.
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.
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.
We then need to add a permission to the role.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ses:SendEmail", "Resource": "*" } ] }
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.
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 example.com
domain.
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.