How to Setup a Serverless Contact Form with AWS Lambda, reCAPTCHA and Storyblok

    Try Storyblok

    Storyblok is the first headless CMS that works for developers & marketers alike.

    In this tutorial I’ll explain you how to setup an api endpoint that accepts a JSON to send an email and create an entry in Storyblok’s data storage. You can use this for a contact form or a comment form on your dynamic or static website.

    The Lambda function also validates the request with Google reCAPTCHA to prevent spam.

    Architectural overview

    Let’s see how this works:

    1. The user sends the request together with the captcha parameter ‘g-recaptcha-response’ to the AWS API Gateway
    2. g-recaptcha-response will be validated by reCAPTCHA in the Lambda function
    3. If the validation was successful we’ll send and email via AWS SES
    4. Then we will create a content entry with the content type ‘form_entry’ in a Storyblok folder

    AWS Lambda serverless form


    To make things easy I created a Cloudformation template which you can use to launch the api with a single click. You can find the code of the template at following repository:

    Clicking on the button above will bring you to the AWS Cloudformation setup page with a preselected template.

    New cloudformation stack

    The next screen let’s you define the configuration of the Lambda function. Fill out all the parameters:

    • StoryblokOauthToken

      You can generate a Storyblok OAuth token in the “My account” section. The OAuth token let’s the Lambda function access the management API of Storyblok to create content entries.

    • StoryblokParentFolderId

      The parent folder id tells the script in which Storyblok folder to create the content entries. When creating the folder define form_entry as default content type and click the checkbox of disabling the visual editor.

      Form entry

      After creating the folder click on it to get the folder id from the url: /me/spaces/39837/stories/index/THIS_IS_THE_FOLDER_ID

    • StoryblokSpaceId

      Get the space id from the url in Storyblok: /me/spaces/THIS_IS_THE_SACE_ID/stories

    • ToEmailAddress

      Define the email address where the notifications will be sent.

    • ReCaptchaSecret

      Define the reCAPTCHA secret from If you have not created a token yet create one with “Invisible reCAPTCHA” selected.

    Cloudformation settings

    On the next pages of the Cloudformation stack creation process you can continue with the default values and finally initiate the creation.

    Creating the form

    After creating the Cloudformation stack we’ll setup a simple contact form to test the API.

    The HTML part

    Paste following code somewhere on your page and replace YOUR_RECAPTCHA_PUBLIC_KEY with the “Site key” from Google reCAPTCHA.

    <form id="form">
      <p class="form__errors" style="display: none;">Please fill in all fields.</p>
      <label for="name_input">Name</label><br>
      <input type="text" id="name_input" name="name"><br>
      <label for="message_input">Message</label><br>
      <textarea type="textarea" id="message_input" name="message"></textarea>
      <p class="form__success" style="display: none;">Sent successfully!</p>
      <p class="form__sending" style="display: none;">Sending...</p>
      <button type="submit" class="g-recaptcha form__button"

    The Javascript part

    Copy the following Javascript code after the contact form HTML.

    <script src='' async defer></script>
      var handleFormSubmit = function () {
        var formApiEndpoint = 'YOUR_API_ENDPOINT'
        var successEl = document.querySelector('.form__success')
        var sendingEl = document.querySelector('.form__sending')
        var errorsEl = document.querySelector('.form__errors')
        var buttonEl = document.querySelector('.form__button')
        var nameInput = document.querySelector('#name_input')
        var messageInput = document.querySelector('#message_input')
        var recaptchaResponse = document.querySelector('#form textarea[name=\'g-recaptcha-response\']')
    = 'none' = 'none'
        if (nameInput.value == '' || messageInput == '') {
 = 'block'
 = 'block'
 = 'none'
        } else {
          var formRequest = new Request(formApiEndpoint, {
            method: 'POST',
            body: JSON.stringify({
              name: nameInput.value,
              message: messageInput.value,
              'g-recaptcha-response': recaptchaResponse.value
            .then(function(response) {
              if (response.status === 200) {
                return response.json()
              } else {
                throw new Error('Something went wrong on api server!')
            .then(function(response) {
     = 'block'
     = 'block'
     = 'none'
              nameInput.value = ''
              messageInput.value = ''
            }).catch(function(error) {
     = 'block'
     = 'block'
     = 'none'
      document.querySelector('.form__button').addEventListener('click', function() {
        document.querySelector('.form__sending').style.display = 'block'
        document.querySelector('.form__button').style.display = 'none'

    Replace YOUR_API_ENDPOINT with your Amazon API-Gateway url which you can grab at the Outputs section of your Cloudformation stack:

    Cloudformation output

    Testing the API

    Make sure that your domain and email is verified in the Amazon SES (Simple Email Service) management console. Otherwise sending will not work.

    If you have setup everything correctly you should receive an email when sending the contact form. Additionally there should be created a content item in the predefined folder in Storyblok.


    With this setup you can easily capture any user input and have the data stored in Storyblok for contact forms or a comments system. Thanks to Lambda you don’t need to run a server just for handling a few form submissions and use a static site generator for your website.

    Resource Link
    Github repository of this tutorial
    Amazon AWS console
    Storyblok App Storyblok