Skip to main content
If you already build email templates in Marketo, HubSpot, or plain HTML, you know the problem allGood’s email tooling solves. A reusable shell leaves the right parts open for a marketer to fill in. The difference is the model. allGood builds the shell from MJML components instead of annotated or hand-written HTML. This article lines the tools up side by side. It assumes you have read How MJML works and know what an email component is.

How each tool builds a template

Marketo uses one HTML document, annotated in place. You write real table markup and mark it with Marketo classes, so the editor knows which parts a marketer can change. A template can hold only one mktoContainer, and a container can hold only mktoModule elements. Editable content inside a module, such as mktoText or mktoImg, needs a unique id and a mktoName.
HubSpot uses a module, which is a small bundle of files. module.html holds the markup, fields.json holds the editable fields, and meta.json holds the label and icon. You cite a field in the markup with {{ module.headline }}. Hand-coded email is one HTML file with no schema and no build step. Whatever you write is close to what ships. Personalization is whatever your sending tool’s merge tags provide, such as *|FNAME|*. allGood uses a bundle of two files. template.mjml is a complete MJML document built from components. template.json declares a copySchema, which lists the fields a marketer fills in. You cite a field inline, with no wrapper element or separate name:
An allGood Email Component, one level down, is a single .mjml file. Its editable copy is real placeholder text, marked with an EDIT or LOCKED comment. It has no schema file. The template that composes components adds the schema. HubL and allGood’s Liquid share a Jinja-style syntax, with {{ }} expressions, {% if %} blocks, and | filters. If you build in HubSpot, the citations will look familiar. Marketo’s ${variable_id} and Velocity come from a different lineage.

Building blocks

Marketo’s structure vocabulary is two words, Container and Module. HubSpot and hand-coded email have no fixed set at all, because the markup is plain HTML from the first line. MJML has a closed set of about thirty tags and one escape hatch, mj-raw. The practical difference is where the Outlook work happens. In Marketo, HubSpot, and hand-coded email, getting a two-column layout or a rounded button to survive Outlook is your job in every template. Marketo’s own advice is to “code like it’s 1999.” In allGood, the components compile to the markup Outlook needs. The only place you take that responsibility back is mj-raw. See What the compiler handles for you. MJML sections and columns also nest freely and sit side by side by default. There is no limit like Marketo’s single container.

Declaring what varies

Every tool needs a way to say which parts of a template change. Here is how the declarations compare. Three things to know when you switch:
  • Type mapping. HubSpot has more field types than JSON Schema. A color field becomes a string with a hex value. A repeatable group becomes an array. Describe the extra detail in the property’s title and description.
  • Separate files. Marketo variables live inside the HTML you are editing. An allGood schema is a separate file, and nothing merges it with template.mjml. You keep the two in sync by hand. Every property must be cited somewhere, and every citation needs a matching property. An orphan on either side renders blank or does nothing, and no error appears until validation catches it.
  • Typed fields help the AI. A property’s title and description do more than label a form. allGood’s AI reads them to learn what the field is for. For example, a headline has to earn an open, and a ctaLabel has to read as an action. A Marketo mktoName or a bare merge tag cannot carry that meaning. See Decide what writers fill in.

Who renders the HTML

In Marketo, HubSpot, and hand-coded email, the HTML you write is what gets rendered. HubSpot renders module.html and your field values together in one pass. Marketo reads your annotated markup directly. In hand-coded email, nothing sits between your file and the recipient’s inbox, so you test by sending yourself emails. In allGood, the MJML is never what gets sent. A render script compiles template.mjml into template.html first, with no personalization involved. Liquid citations are filled in afterward, at send time. A HubSpot module author never sees this split, but an allGood template author always does. See The compile step in allGood.

What happens when a template changes

allGood also records history for every version and every publish, and you can restore an earlier version directly. An email built from a template keeps a derived_from link to the version it was built from, and the system flags when the template has moved on. See Setting up and reviewing email components. Because every placement shares the same version, allGood has no equivalent of a standard HubSpot module instance with its own values. If one send needs different wording, use the template’s copySchema. It layers on top of the component and does not fork it.

Move your existing work over

You do not have to retype anything. allGood’s AI can convert an existing email into MJML. That includes a Marketo template’s mktoContainer and mktoModule markup, a HubSpot module.html, and a hand-coded HTML email. Conversion turns VML fallbacks and conditional comments into the tags that already handle them. Once the MJML exists, the schema, citations, and render step work the same way.
Treat converted output as a draft. A conversion maps markup mechanically. It will not know that a module your team stopped using should be dropped, or that two near-duplicate modules should become one component.

Quick map