Blog
February 23, 2016 Marie H.

Introduction to AWS Lambda, SES, and Google Recaptcha

Introduction to AWS Lambda, SES, and Google Recaptcha

Photo by The New York Public Library on Unsplash

I wanted to get my feet wet with AWS Lambda and had the perfect opportunity today when setting up my freelance and consulting website http://x-qa.com. I went with something simple, managing e-mail from our contact form via an API without having to build out an actually API and configuring Apache/Nginx etc. So pretty much I wrote a Lambda function in Python using the SES boto3 interface to send e-mail and also confirm captcha’s as shown below.

The code

Updated 2026-03-20: Original used Python 2 (urllib2, urllib.urlencode). Updated to Python 3. AWS Lambda's Python 3.12 runtime is the current recommended target.

from botocore.exceptions import ClientError
from boto3.session import Session
import boto3, json
from urllib.parse import urlencode
from urllib.request import urlopen


def format_text(name, number, project, description):
    """
    Text E-mail Format
    """
    return """Hello!
A new form has been submitted on x-qa.com!
Name: {}
Number: {}
Project Type: {}
Description:
{}
Have a great day!""".format(name, number, project, description)

def format_html(name, number, project, description):
    """
    Format HTML E-mail for sending
    Restricted via Lambda to one file so e-mail goes here
    """
    css = 'p { margin-top:10px; margin-bottom:20px;}'
    return '''<html>
    <head><style>{}</style></head>
    <body>
    <p>Hello!</p>
    <p>A new form has been submitted on x-qa.com!</p>
    <p>Name: {}</p>
    <p>Number: {}</p>
    <p>{}</p>
    <p>Have a great day!</p>
    </body>
    </html>'''.format(css, name, number, project, description)

def confirm_captcha(captcha):
    """
    Handle Google Recaptcha Confirmation (Python 3)
    """
    verify_url = 'https://www.google.com/recaptcha/api/siteverify'
    payload = {
        'secret': '',
        'response': captcha,
    }
    response = urlopen(verify_url, urlencode(payload).encode('utf-8'))
    data = json.loads(response.read().decode('utf-8'))
    if data['success'] is True:
        return True

def send_mail(session, text, html):
    """
    Send SES Mail or raise Error
    """
    ses = session.client('ses')
    try:
        ses.send_email(
            Source='no-reply@x-qa.com',
            Destination={
                'ToAddresses': [
                    'marie@x-qa.com',
                ]
            },
            Message={
                'Subject': {
                    'Data': 'New message from X-QA.com',
                },
                'Body': {
                    'Text': {
                        'Data': text,
                    },
                    'Html': {
                        'Data': html,
                    },
                }
            },
        )
    except ClientError:
        raise ValueError("Unable to Send E-mail via SES")

def lambda_handler(event, context):
    """
    Handle POST request form X-QA
    """
    operation = event['operation']
    if operation == 'send_email':
        name = event['name']
        number = event['number']
        project = event['project']
        description = event['description']
        captcha = event['g-recaptcha-response']
        if confirm_captcha(captcha):
            text = format_text(name, number, project, description)
            html = format_html(name, number, project, description)
            session = Session(
              aws_access_key_id='',
              aws_secret_access_key='',
            )
            if session:
                send_mail(session, text, html)
                return True
            else:
                raise ValueError("Unable to create AWS Session")
        else:
            raise ValueError("Captcha was not confirmed")

Aftermath

I’m now wondering why I don’t just write full applications using Lambda and API Gateway… maybe because it would be slower but I think the service will be great for new developers to get their feet wet as well as more advanced users to handle event automation — including scheduled tasks via CloudWatch Events that replace crontabs entirely.

I’m also really excited to see what amazing things I will do with AWS Lambda in the future. Feel free to use this in your own mail implementations. A few things I’d do differently now: the AWS credentials in the session constructor should come from environment variables with KMS encryption rather than hardcoded strings, and shared dependencies like boto3 are better managed as Lambda Layers when you have multiple functions. See below to see how it is implemented on the frontend side.

Frontend integration

I use a simple Angular App to integrate my form with both Google Recaptcha and the AWS Lambda function above. Here is a simple overview of how I am doing it.

app.controller('landingCtrl', function($scope, $http, vcRecaptchaService) {
   $scope.publicKey = "6LfqKxkTAAAAAIrGZB_zgrW_mBUtjV2teyYXcYko";
   /*
    * Object To Capture Contact Information
    */
   $scope.contact = {};
   /*
    * Callback Functions for submit
    */
   var successMail = function(response) {
       if ( response.data == true ) {
           $scope.contact = {};
           $scope.successMessage = "Contact Sent!";
           $("input, select, textarea").removeClass('ng-touched')
       } else {
           console.log(response);
           $scope.errorMessage = "Unable to send mail right now";
       }
   };
   var errorMail = function(response) {
       console.log('something went wrong');
       console.log(response);
   };
   /*
    * Send API Call to SES
    * To Handle Contact Form
    */
   $scope.submit = function(contact) {
       var name = contact.name;
       var number = contact.number;
       var project = contact.project;
       var description = contact.description;
       if ( vcRecaptchaService.getResponse() === "" ) {
         $scope.errorMessage = "Please resolve the captcha before submitting";
       } else {
         $scope.errorMessage = null;
         if ( name && number && project && description ) {
           var html = description;
           var api_url = 'https://9e87218e09.execute-api.us-west-2.amazonaws.com/prod/xqaMail';
           var data = {
             "operation": "send_email",
             "name": name,
             "number": number,
             "project": project,
             "description": description,
             "g-recaptcha-response": vcRecaptchaService.getResponse()
           };
           $http.post(api_url, data).then(successMail, errorMail);
         } else {
           $scope.errorMessage = "Missing required fields";
         }
       }
   };
 });
☕ Buy me a coffee