Serverless Contact Us Form

Page content

Introduction

Launching the Denizen Security site, I had the goal of making the site easy to update and inexpensive to maintain. A means to make an inquiry into the services is a common feature of a marketing page.

Of course, an email “contact us” form is a minimal feature for any site. However, this traditionally requires a server to capture the inputs and forward them into an email message.

With the goal of at least utilizing platform-as-a-service (PaaS) rather than a more complex and expensive infrastructure-as-a-service solution, this was a chance to leverage AWS Lambda for the processing of use inputs and Simple Email Service (SES) for handling SMTP. However, directly using SES would limit my messaging capabilities. Using SNS, messages are published to a topic that can, in-turn, send via SES, SMS, or by other means. This can, for example, be used later in a CRM implementation, landing on business logic in another Lambda function, to ensure agents are quickly responding to inquiries in a system that manages the interactions with customers or potential customers.

The Code

The following code meets my goals. It’s run within AWS Lambda, executing only as-needed, minimizing the cost and any maintenance that would otherwise be required when managing a server. Messages are sent using the SNS. I subscribe to the topic using SMS (text messaging) and email to ensure I’m alerted to the customer inquiry.

As I am very familiar with the abusive tendencies of spammers, I added Recaptcha capabilities to thwart spammers who would otherwise fill my inbox with noise.

var https = require('https');
var querystring = require('querystring');
var AWS = require("aws-sdk");

exports.handler = function (event, context, callback) {
    // Validate the recaptcha
    var input_data = JSON.parse(event.body);
    var postData = querystring.stringify({
        'secret': process.env.ReCaptchaSecret,
        'response': input_data['g-recaptcha-response']
    });

    var options = {
        hostname: 'www.google.com',
        port: 443,
        path: '/recaptcha/api/siteverify',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': Buffer.byteLength(postData)
        }
    };

    var req = https.request(options, function(res) {
        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            var captchaResponse = JSON.parse(chunk);
            if (captchaResponse.success) {
                var sns = new AWS.SNS();
                delete input_data['g-recaptcha-response'];
                var message = "";
                Object.keys(input_data).forEach(function(key) {
                    message += key+':\n';
                    message += '\t'+input_data[key]+'\n\n';
                });
                console.log("Raw Input: " + input_data.stringify)
                console.log("Message: " + message);
                var params = {
                    Message: message,
                    Subject: process.env.Subject,
                    TopicArn: process.env.ContactUsSNSTopic
                };
                sns.publish(params, function (error, response) {
                    callback(null, {
                        statusCode: '200',
                        headers: {
                            "Access-Control-Allow-Origin" : "*", // Required for CORS support to work"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
                        },
                        body: JSON.stringify(response)
                    });
                });
            } else {
                callback(null, {
                    statusCode: '500',
                    headers: {
                        "Access-Control-Allow-Origin" : "*", // Required for CORS support to work"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
                    },
                    body: JSON.stringify({message:'Invalid recaptcha'})
                });
            }
        });
    });

    req.on('error', function(e) {
        callback(null, {
            statusCode: '500',
            headers: {
                "Access-Control-Allow-Origin" : "*", // Required for CORS support to work"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
            },
            body: JSON.stringify({message:e.message})
        });
    });

    // write data to request body
    req.write(postData);
    req.end();
};

Postscript

The messages from this are relatively raw, but suit me just fine. SES email templates could be employed to make the emails more elegant. I will be using templates to create an elegant “got your message” reply to inquiries. Check back for details on this in the future.

Please feel free to use this code as you wish. Of course, I would especially appreciate if you’d like help implementing the solution. Contact me, if you’re interested.