Sending forms by e-mail Last updated: 21. Jul 2026

Use this documentation to setup page templates that submit forms by e-mail and add custom backend validation such as Google reCAPTCHA.

Setup any form you like in HTML, here is an example:

<form class="form-group" id="SignUpForm" data-smtp="#/MySmtpConfig"
    data-message="Thanks, your form has been submitted! We will send you further details to the e-mail address you specified.">
    <div class="row">
        <div class="col-md-4 col-sm-6">
            <div class="form-group">
                <label>E-mail</label>
                <input name="Email" class="form-control" type="email" required placeholder="(required)" />
            </div>

            <div class="form-group">
                <label>Name</label>
                <input name="Name" class="form-control" required placeholder="(required)" />
            </div>

            <div class="form-group">
                <label>Phone</label>
                <input name="Phone" class="form-control" required placeholder="(required)" />
            </div>
        </div>
    </div>
    <button type="submit" class="btn btn-default">Submit form</button>
</form>
<script src="/jquery.submitform.js"></script>

Use the extra attributes for:

Attribute

Description

id

Required. The server looks the form up by this id, and submission fails without it.

data-message

Message shown in place of the form after a successful submission.

data-message-file

File containing the message, instead of data-message.

data-message-url

Redirect to this URL after a successful submission, instead of showing a message.

data-smtp

Path to an SMTP configuration in the # folder. Uses the platform account if omitted.

data-validate

Backend validation function in the API folder - reCAPTCHA, Turnstile or your own rules.

data-antispam

Set to "on" to show Docly's built-in anti-spam widget before sending. Needs no configuration.

data-submitted-url

Query string added to the page URL after submission. Defaults to submit-success and submit-error.

data-track-event

Turns on analytics tracking for this form. See the "Tracking form submissions" section below.

data-nosend

Excludes the form from the submit script so you can write your own submit logic.

These attributes are only available on the <form> tag.

Two more attributes control how the submitted fields are laid out in the resulting e-mail. These go on the fields or on a container element inside the form, not on the <form> tag:

Attribute

Description

data-group

Groups fields into sections in the resulting e-mail.

data-table

Renders repeated fields as a table in the resulting e-mail.

Your form must have an unique id attribute set.

The data-smtp attribute is optional. If not specified, the form will be sent to the workspace owner via the Docly platform's no-reply account. For full control over sender address and delivery, specify your own SMTP configuration.

Docly ships two equivalent submit handlers, both always available at your root folder. Include one of them:

<!-- Sites that already load jQuery -->
<script src="/jquery.submitform.js"></script>

<!-- Sites without jQuery -->
<script src="/vanilla.submitform.js"></script>

Both pick up every form on the page except those marked data-nosend, post it to the form's action (or the current page if there is none), show the receipt message and set the URL marker. They behave identically, so pick whichever matches the rest of your site.

If your SMTP configuration does not work or stops to work (temporarily or permanently) an error message will be displayed to visitors attempting to submit forms. Your "data-message" will not be displayed.

The owner of the published folder must have read access to the configuration in order for it to work.

Setting up SMTP configurations

Enable developer options on your user.

Click new and choose the "SMTP configuration" document template in the "Developer" section.

Data-messagefile example

<form class="form-group" id="SignUpForm" data-smtp="#/MySmtpConfig"
    data-message-file="/thanks.html">
    ....
</form>

Specify URL absolute for the published folder.

Remember to add the script in your page:

<script src="/jquery.submitform.js"></script>

Tracking form submissions

After a submission the script updates the page URL with a marker - ?submit-success or ?submit-error. The marker is set with history.pushState(), and that is the catch: pushState fires no event. GA4 does not count a page view for it unless you write a listener yourself, and Google Ads never sees it at all. A form that only sets the marker is invisible to your campaigns.

Add data-track-event to send a real analytics event alongside the marker:

<form id="ContactForm" data-track-event>

The event goes to gtag() if the page has it, otherwise to dataLayer for Google Tag Manager. It is never sent to both - on a page where Tag Manager loads gtag.js, that would count the same submission twice. Either way the form's id is sent as form_id, so you can tell a contact form apart from a signup form in your reports.

Markup

Successful submission

Failed submission

no attribute

no event

no event

data-track-event

docly_form_submit

docly_form_error

data-track-event="generate_lead"

generate_lead

generate_lead_error

Only mark the success event as a conversion. A failed submission is not a lead, and ?submit-error must never be counted as one.

Tracking is off unless you ask for it. Sites differ in what analytics they already run, and some have their own form tracking or a Tag Manager container with broad triggers. Sending events to every site by default would change what those sites measure without anyone asking for it.

Pick the name to match your setup: generate_lead is what Google Ads normally expects for lead conversions, while docly_form_submit stays clear of GA4's own built-in form_submit event from enhanced measurement.

Forms that redirect with data-message-url are tracked too - the event is sent before the browser navigates away, so a thank-you page does not hide the submission from your reports. The gtag() call sets transport_type: "beacon" so the event is handed to navigator.sendBeacon(), which the browser completes even after the page has been torn down by the redirect.

The dataLayer fallback has no equivalent guarantee. A dataLayer.push() is only a queue entry, and whether it reaches Google depends on the tag your Tag Manager container fires - it can still be lost when data-message-url redirects immediately. If you redirect after submitting and need the count to be reliable, make sure gtag.js is loaded on the page so the beacon path is used.

>

Listening from your own script

The script also dispatches a DOM event on document, so you can hook up analytics that is not a Google product:

document.addEventListener('docly:form-submit', function (e) {
    // e.detail.formId   - the form's id attribute
    // e.detail.success  - true or false
    // e.detail.event    - the event name that was sent
});

A missing or broken analytics library cannot interrupt a submission - the tracking call is isolated, and a failure is logged to the console and otherwise ignored.

>

Verifying it works

  1. Submit the form once on the live site.

  2. Check GA4 under Realtime -> Events - the event should appear within seconds.

  3. Mark it as a key event in GA4, then import it as a conversion in Google Ads.

Disable submit script handling

Use the data-nosend attribute to make the submit script ignore your form. Both jquery.submitform.js and vanilla.submitform.js honour it:

<form data-nosend ...>
....
</form>

This is needed if you use the <form> tag in your code and want to use your own submit handling / script.

Setting up backend validation in forms

Set up backend validation in forms using the data-validate attribute. Reference a backend validation function like this:

<form data-validate="FormValidator1" ...>
....
</form>

This will trigger the backend validator located in #/API/FormValidator1.js.

Code for FormValidator1.js:

export default () => {
    // The form values come in the "form" object as they are posted to the server
    
    let token = form["g-recaptcha-response"];
    if (!token) throw new Error("Validation failed: token not specified!");
        
    // Validates the token with Google reCAPTCHA.
    // Refer: https://developers.google.com/recaptcha/docs/verify
    // NOTE: This is a 3. party integration, it may be outdated at some point and require updates
    let googleUrl = "https://www.google.com/recaptcha/api/siteverify?response=" + token + "&secret=<ENTER-YOUR-SECRET-KEY-HERE>";
    let result = docly.httpPost(googleUrl, {});
    if (!result.success) throw new Error("Google validation failed!"); /* It didn't validate and code will exit here */
    
    
    return true; // Return true to confirm the validation
}

Read more about setting up reCAPTCHA here:
How to setup Google reCAPTCHA