How to set up an email contact form using AWS Lambda, API Gateway, and SES.

Maybe you went through my last article on how to build a CI/CD pipeline for your website using GitLab, or maybe you just have a website or application in an S3 bucket that you want to add a contact form for. Either way, you've come to the right article -- today I'll show you how to set up this reliable contact form using Lambda, API Gateway, and SES (and, boy, is it easy).

Dan, what's the point?

When you have a traditional website on a server that uses things like Apache and PHP, you can just use PHP's mail() function if you setup an email service like Postfix. If you're serving your website from a bucket like I do, you can't exactly use Postfix (nor do I really want to because then I have to worry about the availability of another server). That's when this combination of services comes in handy!

Prerequisites

There aren't too many prerequisites for this (especially if you went through my last tutorial).

  1. An AWS account
  2. Terraform is installed
  3. Simple Email Service is configured in AWS for a domain or email account

So for number 3, we need that set up because we'll need to use this service to send emails when someone fills out our contact form. I won't go through it because it's super easy, but if you need to do it, here's the AWS documentation that you can check out.

The infrastructure

I went through how to do this by hand using this guide, but I always try to recreate my infrastructure with Terraform so that if I ever need it again, I can quickly use a module I have written to spin up the same thing...so luckily for you, you don't need to go through that guide! You'll just use the module I wrote.

Go ahead and create your main.tf file in your Terraform project directory. Let's make it look like this:

provider "aws" {
  region = "us-east-1"
}

module "las" {
    source = "git::https://gitlab.com/nextlink/lambda-api-sendmail.git?ref=v1.0"
    role_name = "las_role"
    function_name = "lambda-function-name"
    billing_tag = "las"
    api_gateway_name = "las"
    api_gateway_description = "API gateway for las"
}

This is all the code we need to write to build our infrastructure. Can you believe it? The module is doing a lot of work behind the scenes to make our lives easier. Feel free to change those variables to whatever you need or want them to be.

Besides this Terraform config, we just need to do a couple more things. Our Lambda function is defined by a file called exports.js. I have a copy of it here. You should copy the source code and create a new file in your working directory called exports.js. You need to do just a little bit of work, though. Lines 4 and 5 in that file look like this:

var RECEIVER = "YOUR_EMAIL@example.com"
var SENDER = "no-reply@example.com"

You'll need to change the RECEIVER variable to be the email you want to receive contact forms at, and SENDER to be the email that you want to send emails from. This is why we set up SES earlier -- so that AWS can send emails from our domain to us.

After that's all done, we have one more task to do. We need to zip up the exports file using this command:

zip exports.js.zip exports.js

After that's done, all that's left to do is to run the config.

terraform apply

Awesome! Our infrastructure is all set up!

Dwayne Johnson nodding approvingly

Using the infrastructure

I won't get too into the HTML and JS side of this because how you do this will vary depending on what framework your website/app uses, but I want to show you what endpoint you need to call in order to send a message.

AWS API Gateway console

The Invoke URL in that image is what we will POST our completed form to. The only stipulation with our form (due to the exports.js file we used in the Terraform config) is that we post JSON data to that url with these fields:

{
  "name": "",
  "email": "",
  "message": ""
}

If those fields aren't what you want, you can change them back in the exports.js file, zip it, and rerun the Terraform config.

And that's that

With that short Terraform configuration, and a little HTML/JavaScript magic, we have an endpoint that is always available (and is really inexpensive to use) so that our users can reach out and contact us! This method is a great alternative to third-party paid solutions, and leaves a lot of room for customization. I hope this example can be of use to you in your future creations. If you have any projects that you could use a hand on, reach out us. We'd love to help!