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 that creates a single API endpoint via AWS API Gateway & Lambda. handy CloudFormation stack 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: — 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. ToEmailAddress — You can get your reCAPTCHA secret here: ReCaptchaSecret https://www.google.com/recaptcha/admin - "Configuration" - ToEmailAddress- ReCaptchaSecret String Email address you want contact form submittions to go to String Your Google reCAPTCHA secret Metadata:AWS::CloudFormation::Interface:ParameterGroups: Label:default: Parameters: Parameters:ToEmailAddress:Type: Description: ReCaptchaSecret:Type: Description: The 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. Metadata 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. AWS::SNS::Topic - ''- - AWS::StackName- ' Topic' - !Ref ToEmailAddress email - ''- - AWS::StackName- '-topic' ContactUsSNSTopic:Type: Properties:DisplayName:Fn::Join: Ref: Subscription: Endpoint: Protocol: TopicName:Fn::Join: Ref: 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 _ AWS::IAM::Role '2012-10-17' - Allow - lambda.amazonaws.com - sts:AssumeRole '/' IamRoleLambdaExecution:Type: Properties:AssumeRolePolicyDocument:Version: Statement: Effect: Principal:Service: Action: Path: _## Create a Policy and attach it to our Lambda Role. _ AWS::IAM::Policy IamPolicyLambdaExecution '2012-10-17' - Allow - logs:CreateLogGroup- logs:CreateLogStream arn:aws:logs:us-east-1:*:*- Allow - logs:PutLogEvents arn:aws:logs:us-east-1:*:* '*'- Allow - sns:Publish !Ref ContactUsSNSTopic - IamRoleLambdaExecution IamPolicyLambdaExecution:Type: Properties:PolicyName: PolicyDocument:Version: Statement: Effect: Action: Resource: Effect: Action: Resource: Resource: Effect: Action: Resource: Roles: Ref: Our Lambda will need access to AWS CloudWatch Logs so it can write logs. It will also need access to our SNS Topic so it can publish to the topic. Publish 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 and . ${ReCaptchaSecret} ${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. AWS::Lambda::Function index.handler 5 - IamRoleLambdaExecution- Arn !Sub |<inline code> nodejs6.10 ContactUsFunction:Type: Properties:Handler: Timeout: Role:Fn::GetAtt: Code:ZipFile: Runtime: 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 resource contains a collection of Amazon API Gateway resources and methods that can be invoked through HTTPS endpoints. AWS::ApiGateway::RestApi AWS::ApiGateway::RestApi ApiGatewayContactUs ApiGatewayContactUs:Type: Properties:Name: The resource creates a resource in an Amazon API Gateway (API Gateway) API. AWS::ApiGateway::Resource AWS::ApiGateway::Resource - ApiGatewayContactUs- RootResourceId api ApiGatewayContactUs ApiGatewayResource:Type: Properties:ParentId:Fn::GetAtt: PathPart: RestApiId:Ref: Now we need to add 2 Method’s and . The method will handle the CORS headers and the method will handle the ‘contact us’ form submission. Options Post Options Post CORS Options Method: AWS::ApiGateway::Method NONE ApiGatewayResource ApiGatewayContactUs OPTIONS - 200 "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'" "'POST,OPTIONS'" "'*'" "'false'" '' WHEN_NO_MATCH '{"statusCode": 200}' MOCK - 200 'Empty' false false false true ApiGatewayMethodOptions:Type: Properties:AuthorizationType: ResourceId:Ref: RestApiId:Ref: HttpMethod: Integration:IntegrationResponses: StatusCode: ResponseParameters:method.response.header.Access-Control-Allow-Headers: method.response.header.Access-Control-Allow-Methods: method.response.header.Access-Control-Allow-Origin: method.response.header.Access-Control-Allow-Credentials: ResponseTemplates:application/json: PassthroughBehavior: RequestTemplates:application/json: Type: MethodResponses: StatusCode: ResponseModels:application/json: ResponseParameters:method.response.header.Access-Control-Allow-Headers: method.response.header.Access-Control-Allow-Methods: method.response.header.Access-Control-Allow-Origin: method.response.header.Access-Control-Allow-Credentials: Post Method: AWS::ApiGateway::Method POST {} ApiGatewayResource ApiGatewayContactUs NONE POST AWS_PROXY - ''- - 'arn:aws:apigateway:'- AWS::Region- ':lambda:path/2015-03-31/functions/'- - ContactUsFunction- Arn- '/invocations' [] ApiGatewayMethodPost:Type: Properties:HttpMethod: RequestParameters: ResourceId:Ref: RestApiId:Ref: AuthorizationType: Integration:IntegrationHttpMethod: Type: Uri:Fn::Join: Ref: Fn::GetAtt: MethodResponses: This Method uses the Api Gateway Lambda integration type. AWS_PROXY The Lambda proxy integration, designated by 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. AWS_PROXY The resource deploys an Amazon API Gateway (API Gateway) resource to a stage so that clients can call the API over the Internet. The stage acts as an environment. AWS::ApiGateway::Deployment [RestApi](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html) AWS::ApiGateway::Deployment ApiGatewayContactUs prod - ApiGatewayMethodPost ApiGatewayDeployment:Type: Properties:RestApiId:Ref: StageName: DependsOn: ApiGatewayMethodOptions Finally we need to grant our Api Gateway endpoint permission to invoke our Lambda. AWS::Lambda::Permission lambda:invokeFunction ContactUsFunction apigateway.amazonaws.com - ''- - 'arn:aws:execute-api:'- AWS::Region- ':'- AWS::AccountId- ':'- ApiGatewayContactUs- '/*/*' ContactUsFunctionPermission:Type: Properties:Action: FunctionName:Ref: Principal: SourceArn:Fn::Join: Ref: Ref: Ref: 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. URL of your API endpoint !Join- ''- - https://- !Ref ApiGatewayContactUs- '.execute-api.'- !Ref 'AWS::Region'- '.amazonaws.com/prod/api' Outputs:ApiUrl:Description: Value: 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, , function(data) {}, 'json'); JSON.stringify({}) 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.