Using an AWS Lambda Function to Send an Email After Form Submission

In our article about Posting Form Content to a Slack Channel via an AWS Lambda Function, we elaborated on how to take advantage of AWS Lambda to handle a delicate process you don’t want to expose to the public in your single-page application.

In this guide, we’ll provide you with an AWS Lambda function for sending an email to a visitor who submitted a form, leaving their email address for registering, downloading a file, getting access to some service or benefit, or something similar. We’ll use AWS SES (Simple Email Service) for sending out the emails, but you might as well use SendGrid, MailChimp, or what you prefer.

We are assuming that you already have a form and have gathered from the article linked above how to set up an AWS gateway for passing the form fields to a Lambda function that processes them. So let’s concentrate on the core functionality! 

How the Lambda function works

The task of the Lambda function is to handle the events coming in via the AWS Gateway associated with it. It receives two arguments, objects named event and context here. event includes the request details, first and foremost the form field values posted on form submission. The context object is for notifying the caller (our form in this case) of the execution result, which can then be reflected on the page. Alternatively, you can have the gateway act on the result, for example, by setting the HTTP status code accordingly.  

Here’s the Lambda function; we’re going to explain further details below.

Copy
const AWS = require("aws-sdk");

exports.handler = function(event, context) {
  AWS.config.update({ region: "eu-west-1" });
  console.log('Handling confirmation email to', event);
  
  if (!event.email.match(/^[^@]+@[^@]+$/)) {
    console.log('Not sending: invalid email address', event);
    context.done(null, "Failed");
    return;
  }

  const name = event.name.substr(0, 40).replace(/[^\w\s]/g, '');

  const htmlBody = `
    <!DOCTYPE html>
    <html>
      <head>
      </head>
      <body>
        <p>Hi ${name},</p>
        <p>...</p>
      </body>
    </html>
  `;

  const textBody = `
    Hi ${name},
    ...
  `;

  // Create sendEmail params
  const params = {
    Destination: {
      ToAddresses: [event.email]
    },
    Message: {
      Body: {
        Html: {
          Charset: "UTF-8",
          Data: htmlBody
        },
        Text: {
          Charset: "UTF-8",
          Data: textBody
        }
      },
      Subject: {
        Charset: "UTF-8",
        Data: "Thanks for registering with ACME!"
      }
    },
    Source: "Jack from ACME <jack.smith@example.com>"
  };

  // Create the promise and SES service object
  const sendPromise = new AWS.SES({ apiVersion: "2010-12-01" })
    .sendEmail(params)
    .promise();

  // Handle promise's fulfilled/rejected states
  sendPromise
    .then(data => {
      console.log(data.MessageId);
      context.done(null, "Success");
    })
    .catch(err => {
      console.error(err, err.stack);
      context.done(null, "Failed");
    });
};

In our case, the request parameters passed to the function are email and name. After setting up and configuring AWS, identifying invalid email addresses, and cleaning up the name, the HTML and plain text versions of the email contents are defined:  htmlBody and textBody.

After these preparatory steps, an email params object is filled with the message details and then handed over to the AWS SES sendEmail function. sendEmail is executed asynchronously, so we are using a Promise to handle the execution result.

Please keep in mind that AWS SES requires you to verify the email address specified as the Source in the params before it can be used as a sender.

A word about form submission

As said, the basic functioning of a form, which connects to a remote service, is explained in Posting Form Content to a Slack Channel via an AWS Lambda Function. So we are limiting ourselves to the form submission handler:

Copy
  handleSubmit(event) {
    this.setState({ submissionState: 'processing' });

    const payload = {
      name: this.state.name,
      email: this.state.email,
    };

    const API_URL =
        "https://yourgateway.execute-api.eu-west-1.amazonaws.com/form_registration";

    $.ajax({
      contentType: 'application/json',
      data: JSON.stringify(payload),
      dataType: 'json',
      type: 'POST',
      url: API_URL,
    })
    .done(() => this.handleSuccess())
    .fail(() => this.handleError());

    event.preventDefault();
  }

This handler simply collects the form field values from the form’s state variables and places them into the payload object, which is then posted via an AJAX request to the given AWS Gateway URL.

That was it!

Almost. Don’t forget to harden your form and your AWS Gateway against spamming and misuse, for example by not blindly accepting all user input, integrating a captcha service, setting a rate limit and processing only requests originating from your site.