ajaxlocomotivecms

Locomotive CMS (hosted): send email upon submission of Content Type (Model) form


I am using the hosted version of Locomotive CMS.

Locomotive CMS provides a means by which to collect form submissions and a means by which to send an email using the Actions API.

I want to collect form submissions and then send an email to the person who submitted the form.

I have 3 caveats:

  1. I wish to collect the form data in a content type (public submissions enabled)
  2. I wish to use Google reCAPTCHA v3
  3. I wish to submit the form using AJAX

All of the aforementioned items are available out of the box in the hosted version of Locomotive CMS. However, I can't figure out how to submit a content type form with Google reCAPTCHA via AJAX and send an email at the same time. The Send Email functionality requires an action on a dedicated page which I believe needs to be loaded in order for the action to run. Additionally I don't know how to get the data from the form submission to the page on which the Send Email action runs.

How can I submit a Locomotive CMS content type form via AJAX with Google reCAPTCHA enabled and send an email containing the form submission data to the person who submitted in the form?


Solution

  • A couple of prerequisites:

    1. Set up metafields_schema.yml to include the necessary information for sending emails:

      smtp_settings:
        label: Outgoing Email
        fields:
      
          server:
            label: SMTP Server
            type: string
            localized: false
      
          username:
            label: Username
            type: string
            localized: false
      
          password:
            label: Password
            type: string
            localized: false
      
          port:
            label: Port
            type: string
            hint: TLS
            localized: false
      
    2. Set up an email template:

      email-template.liquid:

      <p>Dear {{ entryData.first_name }},</p>
      <!-- include form submission data via entryData.form_field_name -->
      

    If the condition of using Google reCAPTCHA is removed, the task is relatively straight-forward.

    We "simply" pass the form data to a custom page where we send an email and create a content entry using the Actions API.

    Setup as follows:

    1. Manually create a form to collect the content type data. Note the form action points to a custom page:

      <form method="POST" enctype="multipart/form-data" class="form" action="{% path_to 'page-with-send-email-and-create-content-entry-action' %}">
          <!-- form fields here -->
      </form>
      
      <script defer src="{{ 'form-submit.js' | javascript_url }}"></script>
      
    2. Submit the form with AJAX:

      form-submit.js:

      $('.form').on('submit', function(e) {
          e.preventDefault();
          var form = $(this);
      
          // error handling functions removed for brevity
      
          $.ajax({
              type: 'POST',
              url: form.attr('action'),
              data: form.serialize(),
              dataType: 'json',
              success: function(data) {
                  console.log('Success:', data);
              },
              error: function(xhr, status, error) {
                  console.log('Status:', status);
                  console.log('Error:', error);
                  var errors = jQuery.parseJSON(xhr.responseText).errors;
                  console.log(errors);
                  for (error in errors) { 
                      myError = error;
                      console.log(myError);
                  }
              }
          });
      
      });
      
    3. Send an email and create a content type entry using the Actions API:

      page-with-send-email-and-create-content-entry-action.liquid:

      {% action "send email and create content type entry" %}
      
          var SMTPsettings = getProp('site').metafields.smtp_settings;
          var entryData = getProp('params'); // params holds the data passed to the page via AJAX
      
          // save entryData into a Liquid variable to be called in the Email Template
          setProp('entryData',entryData);
      
          sendEmail({
      
              to: formData.email_address, // must match the name attribute of the field used to collect the email address 
              from: SMTPsettings.username,
              subject: '[Email Subject]',
              page_handle: 'email-template', // template page for email
      
              smtp: {
                  address: SMTPsettings.server,
                  port: SMTPsettings.port,
                  user_name: SMTPsettings.username,
                  password: SMTPsettings.password,
                  authentication: 'plain',
                  enable_starttls_auto: true
              }
      
          });
      
          createEntry('content_entry_name', {
              content_type_field_name: formData.form_field_name,
              // comma-separated list - last item has no comma
          });
      
      {% endaction %}
      
      {"success":true}
      

    If Google reCAPTCHA is required, the task is more complex.

    I don't believe there is a way to manually create a form in Locomotive CMS with Google reCAPTCHA enabled, which means the above method won't work.

    Google reCAPTCHA is available in Locomotive CMS via the default content type form setup:

    {% model_form 'content_type_slug', class: 'form', json: true, recaptcha: true %}
        // form fields here
    {% endmodel_form %}
    
    <script src="https://www.google.com/recaptcha/api.js?render={{ site.metafields.google.recaptcha_site_key }}"></script>
    
    <script>
        grecaptcha.ready(function() {
            grecaptcha.execute('{{ site.metafields.google.recaptcha_site_key }}', {
                action: 'enquiry'
            })
            .then(function(token) {
                document.getElementById('g-recaptcha-response').value  = token;
            });
        });
    </script>
    
    <script defer src="{{ 'form-submit.js' | javascript_url }}"></script>
    

    Note: The property recaptcha_required needs to be set to true in the content_type yml file.

    In this case we cannot set a custom url for the form action. Additionally the reCAPTCHA verification means we need to let the form submit and create the content entry via the usual process and send the email separately.

    To do this we will need to get the ID of the content entry created upon form submission. We can then run an additional AJAX request and pass the ID to a custom page containing a send email action. On the custom page we will use the ID to reference the content entry and get the data to populate the email.

    Setup as follows:

    1. Create form via default method above.

    2. Submit the form with AJAX. Upon successful submission, get the content entry ID and pass it to a secondary AJAX request:

      form-submit.js:

      $('.form').on('submit', function(e) {
          e.preventDefault();
          var form = $(this);
      
          // error handling functions removed for brevity
      
          $.ajax({
              type: 'POST',
              url: form.attr('action'),
              data: form.serialize(),
              dataType: 'json',
              success: function(data) {
                  console.log('Success:', data);
      
                  // get the content entry ID
                  var entryID = data._id;
                  // set up the data to be sent in the correct format
                  var newData = 'id=' + entryID;
      
                  // set up our secondary AJAX request
                  function sendEmail() {
      
                      $.ajax({
                          type: 'POST',
                          url: 'page-with-send-email-action',
                          data: newData,
                          dataType: 'json',
                          success: function(data) {
                              console.log('Success:', data);
                          },
                          error: function(xhr, status, error) {
                              console.log('Status:', status);
                              console.log('Error:', error);
                              var errors = jQuery.parseJSON(xhr.responseText).errors;
                              console.log(errors);
      
                              for (error in errors) { 
                                  myError = error;
                                  console.log(myError);
                              }
                          }
                      });
      
                  }
      
                  sendEmail();
                  showSuccess();
      
              },
              error: function(xhr, status, error) {
                  console.log('Status:', status);
                  console.log('Error:', error);
                  var errors = jQuery.parseJSON(xhr.responseText).errors;
                  console.log(errors);
      
                  for (error in errors) { 
                      myError = error;
                      console.log(myError);
                      showError();
                  }
              }
          });
      });
      
    3. Find the submitted content entry and send an email using the Actions API:

      page-with-send-email-action.liquid:

      {% action "send email" %}
      
          var SMTPsettings = getProp('site').metafields.smtp_settings;
          var entryID = getProp('params').id;
          var entryData = findEntry('content_type_slug', entryID);
      
          // save entryData into a Liquid variable to be called in the Email Template
          setProp('entryData',entryData);
      
          sendEmail({
      
              to: entryData.email_address,
              from: SMTPsettings.username,
              subject: '[Email Subject]',
              page_handle: 'email-template',
      
              smtp: {
                  address: SMTPsettings.server,
                  port: SMTPsettings.port,
                  user_name: SMTPsettings.username,
                  password: SMTPsettings.password,
                  authentication: 'plain',
                  enable_starttls_auto: true
              }
      
          });
      
      {% endaction %}
      
      {"success":true}