..

Creating a Contact form using Amazon Web Services

I recently wanted to add a simple contact form to my static website. When users submit the form, I wanted to receive their message via email. Amazon S3 hosts the site. I thought of three options to handle the dynamic behavior of parsing the form and sending me an email:

  1. Use a VPS to parse the form and send the email.
  2. Use a third party service.
  3. Use Amazon API Gateway and Amazon Lambda

Option 1 seemed like overkill. Option 2 and 3 both appealed to me. I eventually settled on using Amazon’s services due to their low cost. The remainder of this post contains my notes about setting up the services. See the Amazon documentation for a more in depth look at each service.

Lambda Function

Create a new Lambda function with a NodeJs runtime. Use code like the following, modified from lithostec:

'use strict';
var AWS = require('aws-sdk');
var ses = new AWS.SES({apiVersion: '2010-12-01'});

function validateEmail(email) {
  var tester = /^[-!#$%&'*+\/0-9=?A-Z^_a-z{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z`{|}~])*@[a-zA-Z0-9](-?\.?[a-zA-Z0-9])*(\.[a-zA-Z](-?[a-zA-Z0-9])*)+$/;
  if (!email) return false;
  if(email.length>254) return false;
  var valid = tester.test(email);
  if(!valid) return false;
  var parts = email.split("@");
  if(parts[0].length>64) return false;
  var domainParts = parts[1].split(".");
  if(domainParts.some(function(part) { return part.length>63; })) return false;
  return true;
}

exports.handler = (event, context, callback) => {
  console.log('Received event:', JSON.stringify(event, null, 2));
  console.log('Received context:', JSON.stringify(context, null, 2));
  if (!event.data) { context.fail('We are sorry, an error has occurred. Please try again later.'); return; }
  if (!event.data.email) { context.fail('Email is required.'); return; }
  if (!event.data.message || event.data.message === '') { context.fail('You must provide a message.'); return; }
  if (!event.data.name || event.data.name === '') { context.fail('You must provide your name.'); return; }
  var email = unescape(event.data.email);
  if (!validateEmail(email)) { context.fail('You must provide a valid email address.'); return; }
  var messageParts = [];
  var replyTo = event.data.name + " <" + email + ">";
  messageParts.push("Message: " + event.data.message);
  var subject = "Contact Form Email - " + email;
  var params = {
    Destination: { ToAddresses: [ 'Your Name ' ] },
    Message: {
      Body: { Text: { Data: messageParts.join("\r\n"), Charset: 'UTF-8' } },
      Subject: { Data: subject, Charset: 'UTF-8' }
    },
    Source: "Contact Form ",
    ReplyToAddresses: [ replyTo ]
  };
  ses.sendEmail(params, function(err, data) {
    if (err) {
      console.log(err, err.stack);
      context.fail(err);
    } else {
      console.log(data);
      context.succeed('Thank you for contacting us!');
    }
  });
};

Your form’s data will be in the event.data JSON. This assumes you have Amazon SES setup.

Amazon API Gateway

Create a new API or use an existing one. Create a resource to handle the form submission. I named my resource “/submit-contact-form”. Add a POST method. Select the new method and navigate to the Integration Request section.

Set the Integration Type to Lambda Function and set Lambda Function to your previously created function.

Expand Body Mapping Templates. Select “When there are no templates defined”. Add a Content-Type of “application/x-www-form-urlencoded”. Set the template to the following code courtesy of Marcus’s Stackoverflow answer:

{
    "data": {
        #foreach( $token in $input.path('$').split('&') )
            #set( $keyVal = $token.split('=') )
            #set( $keyValSize = $keyVal.size() )
            #if( $keyValSize >= 1 )
                #set( $key = $util.urlDecode($keyVal[0]) )
                #if( $keyValSize >= 2 )
                    #set( $val = $util.urlDecode($keyVal[1]) )
                #else
                    #set( $val = '' )
                #end
                "$key": "$val"#if($foreach.hasNext),#end
            #end
        #end
    }
}

Enable CORS unless you are using your own custom domain name for your API. Deploy the API.

Contact Form

For the HTML side, I had the Submit button of the contact form execute an Ajax request to the API with a function similar to this:

function submitContactForm() {
    $('#submit-button').attr('disabled','disabled');
    $.ajax({
	type: "POST",
	url: 'https://example.amazonaws.com/prod/submit-contact-form',
	data: $('#contact-form').serialize(),
	success: function(message) {
	    if (message.errorMessage) {
	    } else {
	    }
	},
	error: function(message) {
	},
	dataType: 'json',
	crossDomain: true
    });
}