mail

MJML

MJML templating for mail.

Reach for this subpackage when you want to compile a mail body from MJML rather than plain HTML or html/template.

import "github.com/gp-system/mail/mjml"

mail/mjml compiles MJML markup into responsive, email-client-friendly HTML, and is itself a mail.Body: it slots into Message.WithBody exactly like a plain text or HTML body. The source is first rendered as an html/template (interpolated data is escaped), then compiled from MJML. It runs in pure Go (Boostport/mjml-go, wazero WASM runtime). No Node.js needed for build or runtime.

Template and String

body := mjml.Template(tmpls, "templates/order_confirmation.mjml.tmpl", data).
    WithTextTemplate("templates/order_confirmation.txt.tmpl")

// or from an inline source:
body := mjml.String(`<mjml><mj-body>...</mj-body></mjml>`, data)

mjml.Template reads from an fs.FS (typically a //go:embed directory), the same signature mail.Template uses; mjml.String compiles an inline source string. Both can be extended with WithFuncs(template.FuncMap) before parsing, and WithTextTemplate attaches the plain-text alternative (there is no automatic text derivation, see Body types).

Worked example

A minimal order-confirmation template (templates/order_confirmation.mjml.tmpl):

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text font-size="20px" color="#333333">Hi {{.CustomerName}},</mj-text>
        <mj-text>Your order (#{{.OrderID}}) has arrived and is on its way soon.</mj-text>
        <mj-button href="{{.OrderURL}}" background-color="#2563eb">View your order</mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Calling mjml.Template(tmpls, "templates/order_confirmation.mjml.tmpl", data) first renders this file as an html/template: {{.CustomerName}}, {{.OrderID}}, and {{.OrderURL}} are filled in from data, escaped. Boostport/mjml-go then compiles the resulting MJML: <mj-section>/<mj-column> become a fixed-width, table-based layout, <mj-button> becomes a button with a VML fallback that renders correctly in Outlook, and <mj-text> becomes a paragraph with sane font size and line height, all without a single hand-written media query or layout table. The result, wrapped as a mail.Body, slots into Message.WithBody exactly like a plain HTML body (see the Overview for the full call chain).

Options

  • mjml.Minify(bool): minify the compiled HTML; default true.
  • mjml.ValidationLevel("strict" | "soft" | "skip"): MJML validation strictness; default soft.

The WASM cost

Importing mail/mjml links a WASM runtime into the binary, and the first render pays for loading the WASM module. Subsequent calls are cheaper. Only the binary that actually imports the package carries that cost: the mail root and the smtp driver know nothing about mjml. Typically only cmd/worker imports it.

With the kit

The shop sample app's sendOrderConfirmation listener renders an order confirmation this way; see the worked example on the overview page. Generating a module-level notifier around a template like this is what add mail is for; see its reference for the exact generated shape.

  • Overview: the Mailer interface, Message builder, and the other body types.
  • SMTP: the driver that actually sends the HTML this page compiles.
Copyright © 2026