Using Amazon Lambda to create a contact form

While Hurricane Harvey rains down on Texas I decided this would be a prefect time to build a contact form for a static HTML page that I need. At first I looked at services like FormKeep to send emails but I felt like it was too expensive (starting $59 a month). And it was probably overkill.

I need a simple endpoint I can post data to and kick off an email to me. We’re going to build exactly that with Amazon Lambda, nodemailer, and Send Grid.

Getting Amazon setup

This probably will be the trickiest part of this tutorial since Amazons UI is a bit clunky. To summarize what we need to get done before writing any JavaScript:

  • Create API gateway endpoint
  • Create Lambda function
  • Setup POST requests to route to Lambda function
  • Set up deploy environment (to get API URL)

Create API gateway endpoint

Navigate to Amazon API Gateway and click the “Get started” button. Select “New API” radio box (not “Example API” which is checked by default) and fill in the name of the API. Click “Create API”.

Example image of Amazon API Gateway create screen

On the next screen click the actions button and select “Create Method” from the dropdown. Then select “POST” from the dropdown in the the the pane.

Animated GIF setting up POST endpoint

Now you should see a new card in the right pane titled “POST”. We need to setup what the API does when it receives this POST request. It should route to the lambda function we’re going to write.

Create Amazon Lambda Function

We’re going to use Serverless to help manage and deploy our Lambda function. First, we should install the serverless NPM package:

npm i -g serverless

Next, follow the serverless docs on setting up your AWS credentials. Their documentation is pretty thorough. This will allow you to create lambda functions from the command line

After you get your credentials setup we’re going to create our lambda function:

serverless create --template aws-nodejs --path contact-lambda
cd contact-lambda
serverless deploy

This will create a lambda function with AWS and scaffold the project. This is an AWESOME time saver!

Setup POST requests to route to Lambda function

Navigate back to your API endpoints resources page and click the title “POST”. Click “Setup up now”. Select “Lambda Function” for Integration type and the region you created your function. Then type in the name of the Lambda function you just created in the last step.

Setup a deploy environment

From the Resources page inside API Gateway click the “Actions” button and select “Deploy API” from the dropdown. Inside of the modal that pops up select “new stage” and name it whatever you would like. Fill in any details you would like and click “Deploy”.

Deploy the API

This will give you a URL that you can POST to and send data to the Lambda function.

https://[example].execute-api.us-east-1.amazonaws.com/prod

That’s it! We’ve setup everything we need on AWS for now. To summarize we:

  • Created an API endpoint
  • Pointed POST requests to our Lambda function
  • Deployed the API

The code

Since I want to use the object spread operator and we can only use Node 6.10, we’re going to need to setup webpack. We’ll use babel to polyfill the missing features. Let’s install the needed dependencies:

yarn add -D babel-core babel-loader babel-preset-stage-2 babel-preset-es2015 dotenv nodemailer nodemailer-sendgrid-transport serverless-webpack webpack

Create a webpack.config.js file in the project root:

module.exports = {
entry: './handler.js',
target: 'node',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['es2015', 'stage-2']
}
}
}
]
}
};

And modify the serverless.yml file to add our webpack plugin:

provider:
name: aws
runtime: nodejs6.10
plugins:
- serverless-webpack
....

Setting up the mailer

Let’s dig into some JavaScript and set up nodemailer with Send Grid. First, we need to initialize dotenv to keep the Send Grid API key out of source control.

'use strict';
require('dotenv').config();
module.exports.hello = (email, context, callback) => {
// TODO
};

And then create a .env file in the project root with your API key:

API_KEY=[YOUR_KEY]

Next, we can setup nodemailer. Require nodemailer and Send Grid transport dependencies:

'use strict';
require('dotenv').config();
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');

We’re going to create an options object that has a nested auth object with the Send Gridapi_key. This is a great resource for learning how to generate an API key for send grid.

Then we can create our nodemailer transport class by passing it the sgTransport function with our options object.

'use strict';
require('dotenv').config();
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');
const options = {
auth: {
api_key: process.env.API_KEY
}
};
const client = nodemailer.createTransport(sgTransport(options));
module.exports.hello = (email, context, callback) => {
// TODO
};

Now that the transporter has been created we can send an email. Inside of the hello function, we’re going to create a variable called mailOptions. This variable will hold the data of the email like who it’s from, where its going, the subject, and body. Here’s a list of valid nodemailer options.

The first argument to our lambda function is the data that is sent to the API. For my use case, I’m going to always send the email to one person. So I’ll hard code the to key to my email. Then spread the email object onto our mailOptions object.

'use strict';
require('dotenv').config();
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');
const options = {
auth: {
api_key: process.env.API_KEY
}
};
const client = nodemailer.createTransport(sgTransport(options));
module.exports.hello = (email, context, callback) => {
// setup email data
let mailOptions = {
to: 'youremail@example.com',
...email
};
};

Now that we’ve constructed the data of the email we can send it using the transporter we instantiated earlier.

'use strict';
require('dotenv').config();
const nodemailer = require('nodemailer');
const sgTransport = require('nodemailer-sendgrid-transport');
const options = {
auth: {
api_key: process.env.API_KEY
}
};
const client = nodemailer.createTransport(sgTransport(options));
module.exports.hello = (email, context, callback) => {
// setup email data
let mailOptions = {
to: 'youremail@example.com',
...email
};
  // send mail with defined transport object
client.sendMail(mailOptions, (error, info) => {
if (error) {
throw new Error(error);
}
    const response = {
statusCode: 200,
body: JSON.stringify({
...info
})
};
    callback(null, response);
});
};

If there was an error we’ll throw and if it succeeds we’ll create a response object and spread the response from send grid onto it.

Bam! That’s all we need to do to send emails from a lambda function. Let’s deploy these changes:

serverless deploy

Example use

How do you use this thing? It’s simple now! If we were to use fetch, our POST request might look something like:

fetch('https://[yoururl].execute-api.us-east-2.amazonaws.com/[yourstage]', {
method: "POST",
body: JSON.stringify({
"from": 'email@example.com', // sender address
"subject":`New Contact: fromemail@example.com`, // Subject line
"text": 'New contact from: fromemail@example.com \n Hello world!'
}),
  headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});

This would send an email from email@example.com with the title New Contact: email@example.com and the body of:

New contact from: email@example.com
Hello world!

That’s it! You can now send POST requests to the API endpoint you setup and send emails. This works great if you’re looking to have a simple contact form on a static site.

Show your support

Clapping shows how much you appreciated Robert DeLuca’s story.