Build an MJML confirmation email for Static Forms

Build an MJML confirmation email for Static Forms

7 min read
Static Forms Team

MJML is useful here, but you cannot paste MJML into an email-template field and expect it to render. Compile it first, then paste the generated HTML into Static Forms.

The result in this guide is a compact confirmation email with a real preview line, one clear heading, readable spacing, and room for the auto-reply message. The source stays small enough to edit by hand, while MJML handles the awkward table markup and responsive CSS that email clients still need.[1]

Know where each piece belongs

Static Forms keeps the reply message and its visual wrapper separate. Write the actual confirmation copy in the form's Auto-reply tab. Put the compiled HTML wrapper in Branding > Auto-reply and AI-reply emails > Custom HTML.[2][3]

The wrapper must contain {{message}}. Static Forms replaces that marker with the processed auto-reply body. You can also use field placeholders such as {{name}}; submitted values are HTML-escaped before insertion.[2]

This distinction matters. MJML is source code for the design, not a template language that Static Forms runs. The path is:

  1. edit confirmation.mjml locally;
  2. compile it to confirmation.html;
  3. check the output;
  4. paste the HTML output into Static Forms;
  5. send a real test submission.

Create the MJML confirmation template

Save this as confirmation.mjml:

XML
<mjml>
  <mj-head>
    <mj-title>
      Message received
    </mj-title>
    <mj-preview>
      We received your
      message and
      will reply soon.
    </mj-preview>
    <mj-attributes>
      <mj-all
        font-family="Arial"
      />
      <mj-text
        color="#243047"
        font-size="16px"
        line-height="1.6"
      />
    </mj-attributes>
  </mj-head>

  <mj-body
    background-color="#f4f7fb"
    width="600px"
  >
    <mj-section
      padding="32px 16px 12px"
    >
      <mj-column>
        <mj-text
          align="center"
          color="#5b21b6"
          font-size="14px"
          font-weight="700"
          letter-spacing="1px"
          padding="0"
        >
          NORTHSTAR STUDIO
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section
      background-color="#fff"
      border-radius="16px"
      padding="36px 36px 16px"
    >
      <mj-column>
        <mj-text
          color="#111827"
          font-size="28px"
          font-weight="700"
          line-height="1.25"
          padding="0 0 16px"
        >
          Thanks, {{name}}. We
          received your
          message.
        </mj-text>

        <mj-text
          padding="0 0 20px"
        >
          A person from our team
          will read it and reply
          within two
          business days.
        </mj-text>

        <mj-divider
          border-color="#ddd"
          border-width="1px"
          padding="0 0 20px"
        />

        <mj-text padding="0">
          {{message}}
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section
      background-color="#fff"
      border-radius="16px"
      padding="8px 36px 32px"
    >
      <mj-column>
        <mj-text
          color="#596579"
          font-size="14px"
          padding="0"
        >
          If you did not send
          this message, you can
          ignore this
          email.
        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section
      padding="18px 24px 32px"
    >
      <mj-column>
        <mj-text
          align="center"
          color="#667085"
          font-size="12px"
          padding="0"
        >
          Northstar Studio,
          14 Market Street,
          Example City
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Replace the company name, response time, and postal address. Keep {{message}} exactly as written. If your form does not require a name, replace the personalized heading with a fixed line so an empty field cannot produce an awkward greeting.

The example uses no remote image. That keeps the first version easier to test and still makes sense when images are blocked. If you add a logo later, host it on HTTPS, give it useful alternative text, set an explicit width, and check the email with remote images disabled.

Compile MJML into paste-ready HTML

MJML's command-line interface accepts an input file and writes compiled HTML with -o. It also supports strict validation and minification.[1] Pin the package version so a future release does not quietly change your output.

Shell
npx --yes mjml@5.4.1 \
  confirmation.mjml \
  -o confirmation.html \
  --config.validationLevel \
  strict \
  --config.minify true

Version 5.4.1 was the current npm release when this guide was checked on September 21, 2026.[4] Review the current release before adopting the command in a long-lived build pipeline.

Do not paste confirmation.mjml into Static Forms. Open confirmation.html and paste that generated document into the custom HTML editor.

Check the generated file before pasting it

A successful compile is only the first gate. Static Forms requires the output to contain {{message}}, limits each custom HTML template to 50 KB, and rejects <script> tags and inline event handlers such as onclick.[2]

This small Node script catches those mistakes:

JavaScript
import {
  readFile
} from 'node:fs/promises';

const html = await readFile(
  'confirmation.html',
  'utf8'
);
const bytes =
  Buffer.byteLength(html);
const problems = [];
const messageToken =
  '{{message}}';

if (!html.includes(messageToken)) {
  problems.push(
    'Message token is missing'
  );
}
if (bytes > 50 * 1024) {
  problems.push(
    `Too large: ${bytes} bytes`
  );
}
const hasScript =
  /<script\b/i.test(html);
if (hasScript) {
  problems.push(
    'Script tag found'
  );
}
const hasEventHandler =
  /\bon\w+\s*=/i.test(html);
if (hasEventHandler) {
  problems.push(
    'Event handler found'
  );
}

if (problems.length) {
  console.error(
    problems.join('\n')
  );
  process.exit(1);
}

console.log(
  `Passed: ${bytes} bytes`
);

Save it as check-template.mjs, then run:

Shell
node check-template.mjs

This check does not prove that Outlook, Gmail, Apple Mail, and every mobile client will render identically. It proves that the file meets the receiving template contract and avoids two blocked HTML patterns.

Add the message in Static Forms

Open the form editor and go to Auto-reply. Enable the reply, choose a sender name and subject, then write the confirmation copy. A short message works better than repeating the entire email wrapper:

Plain Text
We have your note about {{form}}.
Keep this email for your records.

Next, open Branding. In the section for auto-reply and AI-reply emails, choose Custom HTML and paste the contents of confirmation.html. The preview should show your wrapper with sample content in place of {{message}}.[2][3]

Custom HTML email templates are available from the Starter plan upward in the current product documentation.[2] Check the live branding documentation before relying on plan details in a client handoff.

Keep the email readable without its styling

Email CSS support is uneven, which is exactly why MJML generates more HTML than anyone wants to write manually. Still, the content order is your responsibility.

Use one descriptive heading. Put the confirmation before secondary details. Write link text that says where it goes, and keep sentences short enough to scan. Those choices follow the W3C's general guidance for headings, meaningful links, and clear writing.[5]

A few practical rules help:

  • Keep essential information as text rather than embedding it in a banner image.
  • Use dark text on a light background with enough contrast.
  • Do not communicate status by color alone.
  • Avoid a row of tiny links in the footer.
  • If you add a button, make the label specific, such as "View support hours" rather than "Click here."

The plain address in the sample is a placeholder, not legal advice. Use the sender details appropriate to your organization and the type of email you send.

Test the real delivery path

The editor preview is useful, but it is not the inbox. Send a submission through the same form a visitor will use.

Check these in the received email:

  1. The subject and preview text make sense together.
  2. The greeting works with a normal name, a long name, and no name.
  3. The inserted message appears once.
  4. The layout is readable at a narrow mobile width.
  5. Text can be selected and zoomed.
  6. Links, if you add any, use HTTPS and reach the intended page.
  7. The email still makes sense when remote images are disabled.

Then open it in the clients your audience actually uses. Static Forms documentation makes the same recommendation because HTML email rendering differs between clients.[2]

Fix the failures you are likely to see

Static Forms says the placeholder is missing

Confirm you pasted the compiled HTML and that the output still contains {{message}}. Do not change it to an MJML expression or remove the braces during minification.

The template is larger than 50 KB

Remove decorative sections and unused web fonts first. Minification can help, but a smaller design is usually easier to maintain. The byte limit applies to the compiled HTML, not the shorter MJML source.[2]

The greeting has a blank space

The form did not supply a name value. Use a fixed heading, or require a named field only when collecting a name is genuinely necessary.

The preview looks right but an inbox does not

Treat the inbox as the authority. Simplify the section, avoid clever positioning, and retest. MJML reduces client-specific work, but its own documentation does not promise identical rendering everywhere.[1]

A later edit breaks the template

Recompile from the .mjml source and rerun the check script. Do not hand-edit the generated HTML and then forget which file is authoritative.

Keep the source, not just the pasted output

Store confirmation.mjml, the pinned compile command, and check-template.mjs with the rest of your project. The HTML in Static Forms is a deployable artifact. The MJML file is the part a human should edit.

When the wording changes, update the Auto-reply tab. When the layout changes, update MJML, compile again, run the checks, paste the new HTML, and send another real submission. That separation makes later changes much less mysterious.

Sources

[1] MJML official documentation
[2] Static Forms branding documentation
[3] Static Forms auto-reply documentation
[4] MJML package on npm
[5] W3C WAI: Writing for Web Accessibility