It’s estimated that approximately emails are sent in a single day. Over 10 million were sent as you read that previous sentence. Even in 2018, over 40 years after its creation, email is still one of the most reliable and versatile means of communication on the . 269 billion internet Throughout this blog post I’ll be providing a technical walkthrough of the latest release of Formplug, an open-source form forwarding service that I’ve been working on. It makes it incredibly simple to receive form submissions by email. Typically you’d use Formplug in an environment where you don’t have the capability to execute sever-side code, like Github Pages for example. View the project on GitHub Try the demo How it works Upon deploying the Formplug service to AWS, you get back an API Gateway endpoint URL. This should set as the form action for any forms that you want to receive by email. Behaviour can then be customised using hidden form inputs prefixed by an underscore. For example, to set the recipient you would use the aptly named input. _to <form action="https://apigatewayurl.com" method="post"><input type="hidden" name="_to" value="johndoe@example.com"><input type="text" name="message"><input type="submit" value="send"></form> Among other features, there’s support for encrypting email addresses, spam prevention and URL redirection. See for the full range of configuration options. the readme Architecture A Lambda in the context of AWS can be thought of as a function that gets invoked based on a predefined event. Formplug is made up of two Lambdas: A Lambda — for parsing form submissions. receive A Lambda — for building and sending emails. send In the case of the Lambda, events are generated from form submissions to our API Gateway endpoint (a public URL which is generated on the first deploy). In the case of the Lambda the event is generated from an API call from the Lambda. receive send receive Configuration Both of these Lambdas are defined in the configuration file, which describes the required AWS infrastructure. The infrastructure will be automatically instantiated on deployment using . serverless.yml CloudFormation functions:receive:handler: src/receive/handler.handleevents:- http:path: /method: postrequest:parameters:querystrings:format: truesend:handler: src/send/handler.handle For each Lambda we’re defining an exported function as the handler. This will be invoked each time the Lambda’s event is triggered. The handler itself is just an arrow function which takes three arguments, the first of which is which in our handler holds the form data itself. JavaScript event receive module.exports.handle = (event, context, callback) => { } Whenever a form is posted to our endpoint, this function is invoked. In order to return a response we need to use the provided argument. This is an so we should pass as the first argument for a successfully formed response. The second argument should be an object that contains a status code and response body. The callback defaults to the content type. callback error-first callback null application/json callback(null, {statusCode: 200, body: 'Email sent'}) Validating the form submission At the top of the Formplug handler a new instance is created and is passed into the constructor. The class is responsible for validation. Defined in the constructor is everything that needs to be extracted from . This is primarily just the recipients for the email and the form inputs to forward on. receive request event Request Request event class Request {constructor (event) {this.singleEmailFields = ['_to']this.delimeteredEmailFields = ['_cc', '_bcc', '_replyTo']this.recipients = {to: '',cc: [],bcc: [],replyTo: []} this.responseFormat = 'html' this.redirectUrl = null this.pathParameters = event.pathParameters || {} this.queryStringParameters = event.queryStringParameters || {} this.userParameters = querystring.parse(event.body) }} We in the constructor. Also notice how we use the to set . This takes the request body containing the form data encoded as and turns it into a JavaScript object. don’t do any real work Node.js querystring module userParamters application/x-www-form-urlencoded Validate using promises There’s a method on that’s responsible for parsing the event. It takes advantage of the ability to sequentially chain promises so that the resolution of one promise becomes the success handler argument of the next. Each success handler in the chained promise sequence is used to validate a different aspect of the form submission. Rejected promises bubble up to their top-level parent, so we can define a single method in the handler that’s shared amongst all of the validation methods. validate Request catch validate () {return Promise.resolve().then(() => this._validateResponseFormat()).then(() => this._validateNoHoneyPot()).then(() => this._validateSingleEmails()).then(() => this._validateDelimiteredEmails()).then(() => this._validateToRecipient()).then(() => this._validateRedirect())} If you’re wondering what one of these specific validation methods looks like, let’s look at which is responsible for setting the recipient. It checks whether any have been provided in , resolving the promise if the validation was successful and rejecting the promise if there were errors. _validateSingleEmails _to singleEmailFields userParameters _validateSingleEmails () {return new Promise((resolve, reject) => {this.singleEmailFields.filter((field) => field in this.userParameters).forEach((field) => {let input = this.userParameters[field]if (!this._parseEmail(input, field)) {let msg = `Invalid email in '${field}' field`let err = new HttpError().unprocessableEntity(msg)return reject(err)}}) return resolve() })} HTTP errors are rejected Instead of rejecting a JavaScript error directly in the promise, an error is generated from a method. This class has been created to provide a friendly API for generating common HTTP response errors. This class contains a public method for each supported error response. Shown below is an example of the class with just the response. HttpError 422 unprocessable entity class HttpError {unprocessableEntity (message) {return this._buildError(422, message)} _buildError (statusCode, message) {const error = new Error(message)error.statusCode = statusCodereturn error}} You might be thinking that it’s not worth structuring error handling like this as requires more upfront work. The benefits become more apparent when you consider how clean the handler can become. We can reject a error from anywhere in the promise chain and have it caught in the top-level method. At this point an appropriate response can then be built for display to the user. receive HttpError catch Generating responses To generate a response it’s not quite as simple as providing a status code and message to . Both JSON and HTML content types are supported, each require different headers to be set and have a different body format. Redirect responses through the form input are also supported, which similarly has different requirements. callback _redirect A class has been created that can build the different response objects that can be passed to . callback class Response {constructor (statusCode, message) {this.statusCode = statusCodethis.message = message} buildJson () {return {statusCode: this.statusCode,headers: {'Access-Control-Allow-Origin': '*','Content-Type': 'application/json'},body: JSON.stringify({statusCode: this.statusCode,message: this.message})}} buildHtml (template) {return {statusCode: this.statusCode,headers: {'Content-Type': 'text/html'},body: template.replace('{{ message }}', this.message)}} buildRedirect (redirectUrl) {return {statusCode: this.statusCode,headers: {'Content-Type': 'text/plain','Location': redirectUrl},body: this.message}}} To make use of the above class you would first create an instance of it by passing in a status code and message, as this is common across all response types. You would then call one of the three public methods according to the desired response. For example, to build a JSON a response you would do the following. const statusCode = 200const message = 'Form submission successfully made'const response = new Response(statusCode, message)callback(null, response.buildJson()) By default a HTML response is shown by with a generic success message. This page is generated by loading a local HTML template file and doing a find and replace on a variable. The template is loaded in the handler using the . {{ message }} Node.js fs module const path = path.resolve(__dirname, 'template.html')const template = fs.readFileSync().toString() const message = 'Form submission successfully made'const html = template.replace('{{ message }}', message) Determining the response type The class is responsible for building the responses, but it has no concept of what the appropriate response is. The choice of the response is determined by a series of condition statements that look at the validated created earlier in the handler. Response request receive if (request.redirectUrl) {callback(null, response.buildRedirect(request.redirectUrl))return}if (request.responseFormat === 'json') {callback(null, response.buildJson())return}if (request.responseFormat === 'html') {const path = path.resolve(__dirname, 'template.html')const template = fs.readFileSync(path).toString()callback(null, response.buildHtml(template))return} If you weren’t aware, you can return in JavaScript by not specifying a return value. In the code block above we’re using it to stop the script execution so that is only ever invoked once. undefined callback Structuring the receive handler Building again on the ability to sequentially chain , the handler takes the following format. JavaScript promises receive module.exports.handle = (event, context, callback) => {const request = new Request(event) request.validate().then(function () {// send email}).then(function () {// build success response}).catch(function (error) {// build error response}).then(function (response) {// response callback})} Objects built using are passed to to return a HTTP response in the final success handler on this top-level promise chain. This response object could have been resolved from either the previous or method. Replacing the above comments with implementation code we arrive at the finalised handler. Response callback then() catch() module.exports.handle = (event, context, callback) => {const request = new Request(event)request.validate().then(function () {const payload = {recipients: request.recipients,userParameters: request.userParameters}return aws.invokeLambda('formplug', 'dev', 'send', payload)}).then(function () {const statusCode = request.redirectUrl ? 302 : 200const message = 'Form submission successfully made'const respnse = new Response(statusCode, message)return Promise.resolve(response)}).catch(function (error) {const response = new Response(error.statusCode, error.message)return Promise.resolve(response)}).then(function (response) {if (request.redirectUrl) {callback(null, response.buildRedirect(request.redirectUrl))return}if (request.responseFormat === 'json') {callback(null, response.buildJson())return}if (request.responseFormat === 'html') {const path = path.resolve(__dirname, 'template.html')const template = fs.readFileSync(path).toString()callback(null, response.buildHtml(template))return}})} Invoking the send Lambda Not discussed is how emails are actually sent. Emails get sent using in the Lambda, which is invoked in the first promise success handler in the Lambda. It’s invoked with a payload containing the recipients and form inputs. Amazon Simple Email Service (SES) send receive const payload = {recipients: request.recipients,userParameters: request.userParameters} return aws.invokeLambda('formplug', 'dev', 'send', payload) The Lambda is invoked using the available on NPM. Although it might look as though we’re directly calling the AWS SDK, we’re not. We’re actually calling a method on a instance of a wrapping class. It provides a proxy to the library, exposing only the relevant methods in a simpler API. AWS SDK singleton const aws = require('aws-sdk') class AwsService {constructor (aws) {this.aws = aws} invokeLambda (serviceName, stage, functionName, payload) {let event = {FunctionName: `${serviceName}-${stage}-${functionName}`,InvocationType: 'Event',Payload: JSON.stringify(payload)}return new this.aws.Lambda().invoke(event).promise()} sendEmail (email) {return new this.aws.SES().sendEmail(email).promise()}} module.exports = new AwsService(aws) Sending emails The Lambda first creates an instance of and then calls the method on the previously described class. In this handler, the argument is just the payload from the Lambda. send Email sendEmail AwsService event receive module.exports.handle = (event, context, callback) => {const email = new Email(config.SENDER_ARN, config.MSG_SUBJECT)email.build(event.recipients, event.userParameters).then(function (email) {return aws.sendEmail(email)}).catch(function (error) {callback(error)})} Config variables are loaded from a local JSON file. The variable is the of the sending email address (SES only sends emails from verified email addresses). SENDER_ARN Amazon Resource Name const config = require('./config.json') The method on the class creates an SES compatible object to pass to to the AWS SDK. It first checks that is valid and then returns the SES object. build Email SENDER_ARN build (recipients, userParameters) {return this._validateArn().then(() => {let email = {Source: this._buildSenderSource(),ReplyToAddresses: recipients.replyTo,Destination: {ToAddresses: [recipients.to],CcAddresses: recipients.cc,BccAddresses: recipients.bcc},Message: {Subject: {Data: this.subject},Body: {Text: {Data: this._buildMessage(userParameters)}}}} return Promise.resolve(email) }) } The email body is built by looping over the user parameters that were sent as part of . Formplug configuration variables that are prefixed by an underscore are omitted. event _buildMessage (userParameters) {return Object.keys(userParameters).filter(function (param) {// don't send private variablesreturn param.substring(0, 1) !== '_'}).reduce(function (message, param) {// uppercase the field names and add each parameter valuemessage += param.toUpperCase()message += ': 'message += userParameters[param]message += '\r\n'return message}, '')} Wrapping up I hope you found this high-level codebase walkthrough interesting. For more information and for deployment instructions, you should go check out the . If you have any comments or suggestions, please leave a reply below and I’ll happily get back to you. repository on Github View the project on GitHub Try the demo _formplug-serverless - Form forwarding service for AWS Lambda_github.com danielireson/formplug-serverless If you enjoyed this blog post you might also enjoy a where I built a serverless URL shortener using AWS Lambda and S3. previous medium tutorial _Throughout this post we’ll be building a serverless URL shortener using Amazon Web Services (AWS) Lambda and S3. Whilst…_medium.freecodecamp.org How to build a Serverless URL shortener using AWS Lambda and S3