Email renderer for Node.js: compile JSON designs to MJML & HTML

Turn a structured design into MJML or inbox-safe HTML on your backend, with a single function call, no headless Chrome, no screenshotting.

By the MailKaro team · Published 15 September 2026 · Updated

MJML is a great markup language for responsive email, but hand-writing it, and keeping non-developers out of the raw markup, is a chore. A better shape for most products is: let people design visually, store a structured JSON design, and render it to MJML or email-safe HTML on the server when it's time to send.

This guide uses the MailKaro Node SDK (email-craft-sdk) to do exactly that. Pair it with the embeddable editor and you have an MJML email template editor component plus a server renderer, without wiring MJML tooling and a headless browser yourself.

  1. 1Install the SDK

    Add the backend SDK to your Node service. It runs server-side and holds your secret server key.

    npm install email-craft-sdk
  2. 2Render a design to MJML or HTML

    Given a design (the JSON your users built in the editor, or one you store as a template), render it two ways: MJML for tooling that expects it, or ready-to-send email-safe HTML.

    Rendering is a pure function, no client to construct, no secret key, no headless browser, and no network round-trip per element.

    import { renderDesign, renderMjml } from "email-craft-sdk";
    
    const design = await loadDesignFromYourDb(id);
    
    const html = renderDesign(design, { fullDocument: true }); // inbox-safe HTML (inlined)
    const mjml = renderMjml(design);                           // MJML markup
  3. 3Compile the MJML with mjml2html

    If your pipeline already uses the mjml package, hand it the MJML from renderMjml and compile it with mjml2html as usual. The output is valid MJML: every starter template compiles with no validation errors in strict mode (checked with mjml 4.15.3).

    You only need this step if you want MJML in the middle. renderDesign already returns inbox-safe HTML on its own.

    import mjml2html from "mjml";
    import { renderMjml } from "email-craft-sdk";
    
    const { html, errors } = mjml2html(renderMjml(design), { validationLevel: "strict" });
  4. 4Personalize with merge tags

    Pass merge tags at render time and they're substituted into the output, so each recipient gets a personalized email from a single design.

    (Separately, the SDK's Mailkaro client, constructed with a secret sk_live_ key, resolves entitlements and handles asset uploads server-to-server, like firebase-admin. Pure rendering above needs none of that.)

    const html = renderDesign(design, {
      fullDocument: true, mergeTags: { first_name: "Ada", order_id: "#1024" },
    });
  5. 5Send with your provider

    The rendered HTML is already inlined and table-based, so any ESP can send it as-is. Use MJML output instead if your pipeline compiles MJML downstream.

    import { Resend } from "resend";
    const resend = new Resend(process.env.RESEND_API_KEY);
    
    await resend.emails.send({
      from: "you@yourapp.com", to: "customer@example.com", subject: "Your receipt", html, // from renderDesign(design, { fullDocument: true })
    });

Rendering server-side keeps your sending pipeline fast and predictable: no browser to spin up, no flaky screenshot step, and merge-tag personalization applied at render time.

Combine this with the embeddable editor so your team designs visually, and you have the full loop, a visual MJML/HTML email builder with a server renderer, in a few lines each.

Common questions

Does this need a headless browser?
No. The SDK renders the structured design directly to MJML or inline HTML in-process, so there's no Puppeteer/Playwright dependency and no screenshotting.
Can I use just the renderer without the editor?
Yes. If you already have designs (or build them programmatically), you can use email-craft-sdk purely as a Node renderer for MJML and email-safe HTML.
How is this different from mjml2html?
mjml2html compiles MJML markup you have written into HTML. renderMjml and renderDesign start one step earlier, from the JSON design your users built in the editor, and give you MJML or the finished HTML. Use them together when you want MJML in your pipeline.
How do I personalize emails?
Pass merge tags at render time (e.g. first_name, order_id). They're substituted into the HTML/MJML output so each recipient gets a personalized email from one design.

Give your users an email editor today.

Start on the free plan. Embed the editor, invite your team, and ship on-brand emails.