paint-brush
Serverless ‘contact us’ form for static websitesby@rgfindley
3,487 reads
3,487 reads

Serverless ‘contact us’ form for static websites

by Randy FindleyAugust 22nd, 2017
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Have you ever wanted to add a ‘contact us’ form to your static website but didn’t want to pay for a backend server, running 24/7, to handle the requests? So did I. That’s why I built this <a href="https://github.com/thestackshack/serverless-contact-us-form" target="_blank">handy CloudFormation stack</a> that creates a single API endpoint via AWS API Gateway &amp; Lambda.
featured image - Serverless ‘contact us’ form for static websites
Randy Findley HackerNoon profile picture

Simple to launch, easy to use, scalable, and cheap!

Serverless ‘Contact Us’ Stack

Have you ever wanted to add a ‘contact us’ form to your static website but didn’t want to pay for a backend server, running 24/7, to handle the requests? So did I. That’s why I built this handy CloudFormation stack that creates a single API endpoint via AWS API Gateway & Lambda.

The backend validates the Google reCAPTCHA and sends out the email via SNS.

Simply launch the CloudFormation stack and add your ‘contact us’ form! It’s that easy.

click here to launch the stack

Lets see how this stack works

You can see the entire stack here.

Input Parameters

There are 2 input parameters:

  1. ToEmailAddress — The email address you want the ‘contact us’ form submissions to go to. You will have to verify the SNS Topic subscription with this address.
  2. ReCaptchaSecret — You can get your reCAPTCHA secret here: https://www.google.com/recaptcha/admin














Metadata:AWS::CloudFormation::Interface:ParameterGroups: -Label:default: "Configuration"Parameters: - ToEmailAddress- ReCaptchaSecretParameters:ToEmailAddress:Type: StringDescription: Email address you want contact form submittions to go toReCaptchaSecret:Type: StringDescription: Your Google reCAPTCHA secret

The Metadata section describes which parameters will show up on the AWS CloudFormation ‘create new stack’ wizard when you use this template. This makes it easy for users to just click the button above and create their stack.

SNS Topic

We create a new SNS Topic with a single inline subscription. The subscription is the email address you provided. Our Lambda that handles the ‘contact us’ form submissions will post the form to this SNS topic which will then send an email to you.













ContactUsSNSTopic:Type: AWS::SNS::TopicProperties:DisplayName:Fn::Join: - ''- - Ref: AWS::StackName- ' Topic'Subscription: - Endpoint: !Ref ToEmailAddressProtocol: emailTopicName:Fn::Join: - ''- - Ref: AWS::StackName- '-topic'

Lambda IAM Role

We need to create an IAM Role for our Lambda that gives our Lambda permission to access other AWS resources.


_## Role that our Lambda will assume to provide access to other AWS resources










_IamRoleLambdaExecution:Type: AWS::IAM::RoleProperties:AssumeRolePolicyDocument:Version: '2012-10-17'Statement: - Effect: AllowPrincipal:Service: - lambda.amazonaws.comAction: - sts:AssumeRolePath: '/'


_## Create a Policy and attach it to our Lambda Role.


















_IamPolicyLambdaExecution:Type: AWS::IAM::PolicyProperties:PolicyName: IamPolicyLambdaExecutionPolicyDocument:Version: '2012-10-17'Statement: - Effect: AllowAction: - logs:CreateLogGroup- logs:CreateLogStreamResource: arn:aws:logs:us-east-1:*:*- Effect: AllowAction: - logs:PutLogEventsResource: arn:aws:logs:us-east-1:*:*Resource: '*'- Effect: AllowAction: - sns:PublishResource: !Ref ContactUsSNSTopicRoles: - Ref: IamRoleLambdaExecution

Our Lambda will need access to AWS CloudWatch Logs so it can write logs. It will also need Publish access to our SNS Topic so it can publish to the topic.

Lambda

Our lambda receives the ‘contact us’ form submission via API Gateway and then posts the form details to our SNS Topic.

We have our Lambda code, inline, within our CloudFormation template. We did this so we could have a simple deployment process. Users only need to click the button above to deploy the entire stack.

We also wanted to references CloudFormation parameters within our code. See ${ReCaptchaSecret} and ${ContactUsSNSTopic}.

If the Lambda is greater than 4096 characters you can’t include it inline and will have to upload the lambda to S3 first, then include a reference.












ContactUsFunction:Type: AWS::Lambda::FunctionProperties:Handler: index.handlerTimeout: 5Role:Fn::GetAtt: - IamRoleLambdaExecution- ArnCode:ZipFile: !Sub |<inline code>Runtime: nodejs6.10

Lambda code:

Take a look at the CORS headers we added. These are required when POST’ing to the API Gateway endpoint.




headers: {"Access-Control-Allow-Origin" : "*", // Required for CORS support to work"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS},

API Gateway

The AWS::ApiGateway::RestApi resource contains a collection of Amazon API Gateway resources and methods that can be invoked through HTTPS endpoints.




ApiGatewayContactUs:Type: AWS::ApiGateway::RestApiProperties:Name: ApiGatewayContactUs

The AWS::ApiGateway::Resource resource creates a resource in an Amazon API Gateway (API Gateway) API.









ApiGatewayResource:Type: AWS::ApiGateway::ResourceProperties:ParentId:Fn::GetAtt: - ApiGatewayContactUs- RootResourceIdPathPart: apiRestApiId:Ref: ApiGatewayContactUs

Now we need to add 2 Method’s Options and Post. The Options method will handle the CORS headers and the Post method will handle the ‘contact us’ form submission.

CORS Options Method:






























ApiGatewayMethodOptions:Type: AWS::ApiGateway::MethodProperties:AuthorizationType: NONEResourceId:Ref: ApiGatewayResourceRestApiId:Ref: ApiGatewayContactUsHttpMethod: OPTIONSIntegration:IntegrationResponses: - StatusCode: 200ResponseParameters:method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'"method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"method.response.header.Access-Control-Allow-Origin: "'*'"method.response.header.Access-Control-Allow-Credentials: "'false'"ResponseTemplates:application/json: ''PassthroughBehavior: WHEN_NO_MATCHRequestTemplates:application/json: '{"statusCode": 200}'Type: MOCKMethodResponses: - StatusCode: 200ResponseModels:application/json: 'Empty'ResponseParameters:method.response.header.Access-Control-Allow-Headers: falsemethod.response.header.Access-Control-Allow-Methods: falsemethod.response.header.Access-Control-Allow-Origin: falsemethod.response.header.Access-Control-Allow-Credentials: true

Post Method:






















ApiGatewayMethodPost:Type: AWS::ApiGateway::MethodProperties:HttpMethod: POSTRequestParameters: {}ResourceId:Ref: ApiGatewayResourceRestApiId:Ref: ApiGatewayContactUsAuthorizationType: NONEIntegration:IntegrationHttpMethod: POSTType: AWS_PROXYUri:Fn::Join: - ''- - 'arn:aws:apigateway:'- Ref: AWS::Region- ':lambda:path/2015-03-31/functions/'- Fn::GetAtt: - ContactUsFunction- Arn- '/invocations'MethodResponses: []

This Method uses the Api Gateway AWS_PROXY Lambda integration type.

The Lambda proxy integration, designated by AWS_PROXY in the API Gateway REST API, is for integrating a method request with a Lambda function in the backend. With this integration type, API Gateway applies a default mapping template to send the entire request to the Lambda function and transforms the output from the Lambda function to HTTP responses.

The AWS::ApiGateway::Deployment resource deploys an Amazon API Gateway (API Gateway) [RestApi](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html) resource to a stage so that clients can call the API over the Internet. The stage acts as an environment.







ApiGatewayDeployment:Type: AWS::ApiGateway::DeploymentProperties:RestApiId:Ref: ApiGatewayContactUsStageName: prodDependsOn: - ApiGatewayMethodPost

  • ApiGatewayMethodOptions

Finally we need to grant our Api Gateway endpoint permission to invoke our Lambda.
















ContactUsFunctionPermission:Type: AWS::Lambda::PermissionProperties:Action: lambda:invokeFunctionFunctionName:Ref: ContactUsFunctionPrincipal: apigateway.amazonaws.comSourceArn:Fn::Join: - ''- - 'arn:aws:execute-api:'- Ref: AWS::Region- ':'- Ref: AWS::AccountId- ':'- Ref: ApiGatewayContactUs- '/*/*'

Outputs

We are now at the end of our CloudFormation script. Lets output our new API Gateway endpoint so we can use it within our ‘contact us’ form.










Outputs:ApiUrl:Description: URL of your API endpointValue: !Join- ''- - https://- !Ref ApiGatewayContactUs- '.execute-api.'- !Ref 'AWS::Region'- '.amazonaws.com/prod/api'

Lets see how the ‘contact us’ form works

The HTML

We are using Bootstrap as the CSS framework. Use your API Gateway url in the forms action attribute.

The JavaScript

I ran into a CORS issue when I posted a JSON object. It worked when I posted a stringified JSON object.

This didn’t work:

$.post(url, {}, function(data) {}, 'json');

This did work:

$.post(url, JSON.stringify({}), function(data) {}, 'json');

Some CSS




.errors {color: red;display: none;}



.thanks, .sending {display: none;}



.grecaptcha-badge {float: right;}

Conclusion

I hope you enjoyed this post and I hope this CloudFormation template makes some developers life a little bit easier. Please let me know what you think.