AWS Static Website with S3 and Lambda (Part 2)

In the previous post we created a single-page responsive static web site from html, CSS and javascript and hosted it on AWS S3 with a CloudFront Distribution. The purpose for this website is to be a simple point-of-presence on the internet to hopefully generate some traffic and perhaps lure some interested party to reach out for more information. In the past, many would have listed an email address or even a phone number on a contact page, but those days are gone and for some good reasons. First, I don’t know about you, but getting thousands of spam emails would be a real drag. Bots are continuously scanning the web and  your pages for an email addresses that they can sell in mailing lists. It’s even worse if you provide a phone number. The better way to have people contact you is to use a Contact Form with Captcha to eliminate bots and call a Web API to deliver a message to you. Here’s how to do it with AWS Web API Gateway and Lambda.

If you haven’t been following along, the website we are working with utilizes a server-less architecture. The web server is provided by AWS S3 and the site assets are simply files in an S3 bucket. This does not necessarily mean we cannot have any dynamic content, it just means the scaffolding for the site is just html and javascript. Adding server side capabilities then is a matter of calling some Web API. Historically, one would host that Web API on one or more web servers, but there’s another option. AWS provides a very easy to use service for providing RESTful Web Services called AWS API Gateway. Using the gateway, you can create an endpoint that can then invoke other services in the Amazon cloud. The service that most naturally fits in the is architect is Lambda, which is a managed server-less compute service. Lambda allows you to write bite-sized chunks of functionality in the cloud in several very popular programming languages. To date, Lambda supports: Node.js, Python, Java, C# and Go. And since we are trying to do this on the cheap, the pricing for Lambda includes 1 Million request per month free of charge. That ought to be a enough for a presence web site, don’t you think? AWS API Gateway pricing is similar.

So lets get started. The basic steps we’re going to follow are:

Create the Lambda function

To create the Lambda function that will send a contact request email to you, first go to the AWS Console and select Services->Compute->Lambda. Click Create a function and then Author from scratch. Give the function a name like SendContactRequest and then for the Runtime, select Node.js 8.10. The Lambda function will need authorization to access other AWS services so give it a Role Name of SendContactRequestRole. After clicking Create Function, you should see the following:

As you can see, we can add one or more triggers from the left. We will be adding an API Gateway in a subsequent step. On the right side, we see the AWS resources that this Lambda function has access to. When we created the function, AWS also created a role using the name we provided. By default, the Role includes the permissions to write to CloudWatch Logs. We want this Lambda function to send an email so we need to add additional permissions to the role for SES (Simple Email Service). Now lets go check out the Role that was created by clicking Services->Security Identity & Compliance->IAM. On the left panel click Roles, and then the SendContactRequestRole in the list. There should a single policy called AWSLambdaBasicExecutionRole. Select that to see the permissions for the role. Lambda gave the Role access to CloudWatch Logs automatically. We need to add the permission for SES. Click Edit Policy and then Add Additional permissions. Chose the SES service and select Write as follows:

Returning to the Lambda console. Your Lambda function should now look like this:

 

Create and configure API Gateway API

Now it’s time to create your Web API, go to the AWS Console and select Services->Network and Content Deliver->API Gateway. Click the Get Started button and select New API. For the API name, type “ContactUs” and click Create API.  Click the Actions button and select Create Resource and give the resource name ‘contact’ as shown below:

Click Create Resource. Next, click the Actions button again and this time select Create Method. Then from the drop down menu, select Post, the click the check mark.   Now select Post and enter the name of the Lambda function created in the previous step.

Next, its time to Deploy the API so click the Actions button and select Deploy API. This will also create a Stage for the API.  A stage is a named reference to a deployment, such as Prod or Dev. You can use a Stage to manage a particular deployment. Now we’re ready to test the API. Click the blue Test icon to bring up the test page.

Test page appears and you can add any params or message body you wish and re-run. Since we haven’t implemented the Lambda function yet, we get a message back from Lambda “Hello from Lambda.

Code the Lambda function

The code for the Lambda function is pretty straight forward. We chose node.js for the platform so the code will be javascript. So go ahead and click Services->Compute->Lambda and select your function. The code window is located at the bottom of the screen and already has a file named index.js with some boilerplate code. Paste in the code below.

console.log('Loading function');
var AWS = require('aws-sdk')
var ses = new AWS.SES()
 
var RECEIVER = 'info@thefundwiz.com'
var SENDER = 'info@thefundwiz.com'
 
exports.handler = function (event, context) {
    console.log('Received event:', event)
    sendEmail(event, function (err, data) {
        context.done(err, null)
    })
}
 
function sendEmail (event, done) {
    var params = {
        Destination: {
            ToAddresses: [
                RECEIVER
            ]
        },
        Message: {
            Body: {
                Text: {
                    Data: 'Name: ' + event.name + '\nEmail: ' + event.email + '\nPhone: ' + event.phone + '\nDesc: ' + event.description,
                    Charset: 'UTF-8'
                }
            },
            Subject: {
                Data: 'Website Contact Form: ' + event.name,
                Charset: 'UTF-8'
            }
        },
        Source: SENDER
    }
    ses.sendEmail(params, done)
}

Obviously, you will want to change the RECEIVER and SENDER variables to your own domain. Make sure both are set to an email address you control. You will need to verify them later. The main elements here are to pull in the AWS SDK and provide an implementation for exports.handler. This is the entry point for your function. The sendEmail function accepts the event parameter which contains the form data that was sent in the post. Our form will have a field for name, email, phone and description. The method constructs an object which contains the values that SES needs to send the email. 

The method can be tested right from this page by clicking the Test button on the top-right corner of the page. You should see a failure. Clicking the Details down arrow will reveal that the email address that is to be the RECEIVER has not been verified. So select Services and type SES in the search box. Select Simple Email Service. On the left hand side, select Email Addressesand then Verify a New Email AddressEnter a valid email address that you have access to and press Send. Within a few minutes, you will get an email from AWS with a confirm link. Once you click that link, try testing your lambda function again. If it fails again, check to make sure that the case of the text of the verified email address and the case in the code match. 

Update the website to Post the ContactUs form to the Web API

To submit the form we’re going to use a little bit of javascript added to the index.html file:

        var correctCaptcha = function (response) {
            $('#submit').prop('disabled', false);
        }

        var URL = 'https://cczxmtl722.execute-api.us-east-1.amazonaws.com/Prod/contactus'

        $('#contact-form').submit(function (event) {
            event.preventDefault()

            var data = {
                name: $('#name-input').val(),
                email: $('#email-input').val(),
                phone: $('#phone-input').val(),
                description: $('#description-input').val()
            }

            $.ajax({
                type: 'POST',
                url: URL,
                dataType: 'json',
                contentType: 'application/json',
                data: JSON.stringify(data),
                success: function () {
                    $('#name-input').val(""),
                    $('#email-input').val(""),
                    $('#phone-input').val(""),
                    $('#description-input').val("")
                    $('#submit').prop('disabled', true);
                    $('#contactSuccess').modal('show');
                    setTimeout(function () {
                        $('#contactSuccess').modal('hide');
                    }, 5000);
                },
                error: function () {
                    $('#contactError').modal('show');
                    setTimeout(function () {
                        $('#contactError').modal('hide');
                    }, 10000);
                }
            })
        })

Note the URL variable will need to be changed to the endpoint of your API. This can be found by clicking the Stages link on the left side of the API Gateway console. Make sure your form name is also matching the id in line 7. The submit method is very simple. It packages up the fields in your form and Posts the data to your API. Its good if you also ensure that there is a “contactError” and “contactSuccess” form available to provide feedback. The only thing left is to enable Captcha. This implementation simply disables the form’s submit button until the captcha successfully verifies. The Captcha javascript can be downloaded from here:

<script src=’https://www.google.com/recaptcha/api.js’></script>

And that’s it. We have created a serverless website and are hosting it for free. We’ve protected our assets on S3 with the CloudFront worldwide CDN. We’ve created a serverless web api with API Gateway and serverless compute Lambda. Cost wise, I don’t think we will ever see a bill for this. Unless, of course your business idea becomes wildly successful.

I hope you enjoyed the article and come back soon for more AWS learning.

About the Author Don McRae

CEO of McRaeSoft.com and Independent Software Development Consultant. Specializing in AWS Cloud Architecture, Development and DevOps and Cloud Security.

Leave a Comment: