Before you go, check out these stories!

0
Hackernoon logoServerless ‘contact us’ form for static websites by@randy-findley

Serverless ‘contact us’ form for static websites

Author profile picture

@randy-findleyRandy Findley

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
- ReCaptchaSecret
Parameters:
ToEmailAddress:
Type:
String
Description: Email address you want contact form submittions to go to
ReCaptchaSecret:
Type:
String
Description: 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::Topic
Properties:
DisplayName:
Fn::Join:
- ''
- - Ref: AWS::StackName
- ' Topic'
Subscription:
- Endpoint: !Ref ToEmailAddress
Protocol: email
TopicName:
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::Role
Properties:
AssumeRolePolicyDocument:
Version:
'2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: '/'

#
# Create a Policy and attach it to our Lambda Role.
#
IamPolicyLambdaExecution:
Type:
AWS::IAM::Policy
Properties:
PolicyName:
IamPolicyLambdaExecution
PolicyDocument:
Version:
'2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
Resource: arn:aws:logs:us-east-1:*:*
- Effect: Allow
Action:
- logs:PutLogEvents
Resource: arn:aws:logs:us-east-1:*:*
Resource: '*'
- Effect: Allow
Action:
- sns:Publish
Resource: !Ref ContactUsSNSTopic
Roles:
- 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::Function
Properties:
Handler:
index.handler
Timeout: 5
Role:
Fn::GetAtt:
- IamRoleLambdaExecution
- Arn
Code:
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::RestApi
Properties:
Name:
ApiGatewayContactUs

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

ApiGatewayResource:
Type:
AWS::ApiGateway::Resource
Properties:
ParentId:
Fn::GetAtt:
- ApiGatewayContactUs
- RootResourceId
PathPart: api
RestApiId:
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::Method
Properties:
AuthorizationType:
NONE
ResourceId:
Ref:
ApiGatewayResource
RestApiId:
Ref:
ApiGatewayContactUs
HttpMethod: OPTIONS
Integration:
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
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_MATCH
RequestTemplates:
application/json:
'{"statusCode": 200}'
Type: MOCK
MethodResponses:
- StatusCode: 200
ResponseModels:
application/json:
'Empty'
ResponseParameters:
method.response.header.Access-Control-Allow-Headers:
false
method.response.header.Access-Control-Allow-Methods: false
method.response.header.Access-Control-Allow-Origin: false
method.response.header.Access-Control-Allow-Credentials: true

Post Method:

ApiGatewayMethodPost:
Type:
AWS::ApiGateway::Method
Properties:
HttpMethod:
POST
RequestParameters: {}
ResourceId:
Ref:
ApiGatewayResource
RestApiId:
Ref:
ApiGatewayContactUs
AuthorizationType: NONE
Integration:
IntegrationHttpMethod:
POST
Type: AWS_PROXY
Uri:
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 resource to a stage so that clients can call the API over the Internet. The stage acts as an environment.

ApiGatewayDeployment:
Type:
AWS::ApiGateway::Deployment
Properties:
RestApiId:
Ref:
ApiGatewayContactUs
StageName: prod
DependsOn:
- ApiGatewayMethodPost
- ApiGatewayMethodOptions

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

ContactUsFunctionPermission:
Type:
AWS::Lambda::Permission
Properties:
Action:
lambda:invokeFunction
FunctionName:
Ref:
ContactUsFunction
Principal: apigateway.amazonaws.com
SourceArn:
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 endpoint
Value: !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.

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.