Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Templating needs be a first class feature of mjml #1630

Closed
justinmchase opened this issue Jun 25, 2019 · 33 comments
Closed

Templating needs be a first class feature of mjml #1630

justinmchase opened this issue Jun 25, 2019 · 33 comments

Comments

@justinmchase
Copy link

justinmchase commented Jun 25, 2019

Overview

I believe that it is a mistake for this project to maintain an unopinionated position about templating. 99% of systems using mjml will have some form of data binding in conjunction with mjml. Correct me if I'm wrong but I don't think that creating completely static email by hand is a super common scenario.

Problem

Currently mjml has tools created for it to view the rendered html in various simulated test environments (mobile or desktop, etc.) This is great but if those tools have no idea about the templating transformations applied to it and the data that goes into it then you have one of two options:

  1. View the original mjml files which won't look right or might not work at all because they have invalid templating syntax mixed in
  2. View the generated mjml, which is the product of running it through a custom transformation pipeline and composed with data but now has the downside of not being the original files in code. Therefore if you try to edit that output it will be lost and somehow needs to be translated back into the original source.

For example this vscode-plugin:
https://marketplace.visualstudio.com/items?itemName=attilabuti.vscode-mjml

It won't work on files with handlebars syntax mixed in and if I'm looking at the files my build step produces I won't be able to edit it in the left because its not the right code. Same for the desktop tool.

Currently the examples appear to be geared towards people creating completely static templates with hand rolled sample data and you cannot simply use that as is and now you have a disconnect between people making email templates and the files that actually need to go into the file system, resulting in manual translations.

Is your feature request related to a problem? Please describe.
There are no built in semantics for data binding, conditionals or loops therefore tools can't be made to view templated mjml files easily and all the current tools are designed to edit or view the final product not the actual source code in the project.

Describe the solution you'd like
Either just pick handlebars as a first class citizen or implement your own syntax to allow data binding.

Add a tag which be used to describe sample data for use by tools when rendering but is ignored at runtime.

Off the top of my head prototype:

<mj-data type="lorem-ipsum">
  <mj-sentence name="title" />
  <mj-list min="1" max="10" name="items">
    <mj-phrase name="name" />
    <mj-paragraph name="description" />
  </mj-list>
</mj-data

Then the display tools could use a lorem-ipsum generator to generate objects which can be used to dynamically expand what is visualized into a fully static content.

Describe alternatives you've considered
Using handlebars and then creating a html server which serves up the transformed mjml then try to point the dev tool at a url instead of the file system, use source maps to get the editor to map back to the original source files.

Additional Context
I'm just saying that this was the path html went down a long time ago and it has the drawbacks mentioned above but its too late for html. Since then there have been some improved techniques invented which is essentially to have integrated databinding semantics. Take a look at DataBinding in xaml for example.

I used to work on design tools for Microsoft in both visual studio and on the "new" IE developer tools and I can tell you that this is an important aspect to have in the base language for all future languages. Your tooling will always be limited or disconnected from the original source code if you go down this path and you'll need the same problematic workarounds that html suffers from where you have to use source maps and you have to have developers who can translate between the templates and the mjml that designers create manually.

@iRyusa
Copy link
Member

iRyusa commented Jun 25, 2019

99% of systems using mjml will have some form of data binding in conjunction with mjml.

I don't think that "99%" of people are using a templating lang conjoining to MJML, but it's still a good amount of people. And for today I think the latest improvment over mj-raw on MJML4 are really well received and ease the "plug and play" side to your current framework/solution.

About a "live" preview with data, I think with gulp or any task runner it's easy enough to handle templating before OR after mjml transpilation

I'm just saying that this was the path html went down a long time ago and it has the drawbacks mentioned above but its too late for html. Since then there have been some improved techniques invented which is essentially to have integrated databinding semantics. Take a look at DataBinding in xaml for example.

I think you're overthinking the problem here. MJML aim to reduce the pain behind email clients specificities, not to solve an endless quest for the all-in-one solution/workflow. Being open is nice, we use today an external project for HTML minification, and same for CSS Inlining, both project encounter some of problems we have too (avoid messing with non standard xml/html in valid html/xml document).
Adding a templating language in top of current mjml-core would require to answer again some of the problems that are already solved by almost every template language today.

View the generated mjml, which is the product of running it through a custom transformation pipeline and composed with data but now has the downside of not being the original files in code. Therefore if you try to edit that output it will be lost and somehow needs to be translated back into the original source.

Or just adapt the workflow to inject data only for preview, we have this working with premailer on Rails, it's even simpler on Nodejs env, and I don't think it would be too hard to implement in any framework :)

This has been discussed a number of time and the conclusion will still be the same : Adding templating lang/directive would bloat the langage itself and the learning curve too. There's already enough templating language out there to cover everyone usage.

Last quote :

Your tooling will always be limited or disconnected from the original source code

For us it's a good thing, MJML is just a transpiler that remove the complexity behind HTML email, in some way we're adding some, but I think after 3+ years of activty, I think it's safe to assume that we also solve a good amount of issues in HTML development

@justinmchase
Copy link
Author

justinmchase commented Jun 25, 2019

I knew you were going to say this but I wanted to write this up anyway because I think a lot of people coming here are experiencing the same dissonance and have this same expectation. If you didn't put it into this layer that would be fine but it seems like the layer should exist and the various tools need it to be useful.

Its just confusing when you see the two developer tools that exist but its not clear how they could ever be used since your actual mjml files end up being templated.

I think it's safe to assume that we also solve a good amount of issues in HTML development.

Yeah I mean this is great, the complexity of email html and style inlining is really gross and this is a majorly helpful tool. Based on some of these recurring questions here though it does seem like an issue that people encounter, as we are right now.

A designer hands me massive hand rolled mjml files full of lorem-ipsum and now you have to turn them into something that renders from actual data. Ok handlebars to the rescue. Ok now a new contractor wants to update them all. So we hand them our template files but they have no idea what to do with them because the mjml tool can't visualize them since they're all handlebars. So they basically struggle to make it work by removing all the templates and inlining their own fake data and then much later then hand me the templates back and now I have to manually figure out what they're doing again and recovert it back into a templated thing for data binding.

Meanwhile developers are tweaking the templates over time so they originals are no longer valid and there are no good tools for viewing the end result without rolling our own solution for rendering email previews and live data sampling... I mean I'm just saying everyone using this is having the same problems and all coming up with their own half-baked solutions I am pretty sure. I've done it before with even worse tools so when I saw this I was happy and it sounded great ... but its just missing the point just a little bit.

Its true this addresses one gross problem about emails but the other important problem about emails is visual feedback at design time, which I can see there has been an attempt to address, but the solution misses the very big picture related to data binding which is a crucial aspect of emails.

@iRyusa
Copy link
Member

iRyusa commented Jun 25, 2019

Ok so, if your workflow is bad, is it safe to assume that MJML is the culprit ? I don't want to sounds like an ass here, but let's be realistic. Even if we provide a built-in way to iterate/condition over data, how will you feed those data into template then, again via a script, and at a scale of a company ?

Meanwhile developers are tweaking the templates over time so they originals are no longer valid

Here it's another problem, and we can't really do something about it, MJML to HTML is a one way trip. If I use typescript/babel and then I edit the output of it, would I blame typescript to give me a chance of put those changes back into my code base ? I don't think so. Education is key here, don't touch any of the final html if it's not part of an automated process as we do today for any transpiled language.

I think, here, what you need to do is simply to reconsider how you build MJML templates and provide builds with data. It's like not really hard to do a small backend or even some convention with a simple yaml/json/xml/... whatever format and build a final HTML with it.

Handlebars itself won't fit your need, MJML itself neither. You can buy any ingredients of a recipe, you still have to cook it at the end of the day to have a meal, it's basically the same here.

but the solution misses the very big picture related to data binding which is a crucial aspect of emails.

No one could end up with a solution that satisfy everyone, so I think, build your own and capitalize on it. If you'd feel that anyone could have the same issue as you, you can just also build a thing on top of MJML on your own and use/release it.

It's already hard to find time to keep an open source project active, so let's not go too far of the initial scope.

Again sorry if I sound really aggressive in this reply, but there's no bad intention behind it.

I'm closing the issue because I think the subject is way to big. The discussion would lead to a dead end anyway, where there's no real consensus or it would be tied to the way your company work at the end

@iRyusa iRyusa closed this as completed Jun 25, 2019
@justinmchase
Copy link
Author

how will you feed those data into template then, again via a script, and at a scale of an company ?

I'd say two ways. Dynamically at render time and also have a way to declare it so that the dev tools can dynamically generate sample data during viewing time.

Take this modified canonical snippet from your README as an example:

import mjml2html from 'mjml'

const data = {
  title = 'Example Title',
  items: [
   { name: 'foo', description: 'this is an item' },
  { name: 'bar', description: 'this is another item' }
}

/*
  Compile an mjml string
*/
const htmlOutput = mjml2html(`
  <mjml>
    <mj-body>
      <mj-section>
        <mj-column>
          <mj-text field="title">
            Hello World!
          </mj-text>
        </mj-column>
      </mj-section>
      <mj-section>
        <mj-row field="items">
          <mj-column>
            <mj-text field="name">
              Unnamed
            </mj-text>
          </mj-column>
          <mj-column>
            <mj-text field="description">...</mj-text>
          <mj-column>
        </mj-row>
      </mj-section>
    </mj-body>
  </mjml>
`, { ...options, context })

/*
  Print the responsive HTML generated and MJML errors if any
*/
console.log(htmlOutput)

Next I would also support declaring an element which does not render as html but can be used by tools to create the dynamic data. So if your file is example.mjml:

  <mjml>

    <mj-data type="lorem-ipsum">
      <mj-data-phrase field="title" />
      <mj-data-list field="items" min="1" max="10">
        <mj-data-phrase field="name" />
        <mj-data-paragraph field="description" />
      </mj-data-list>
    </mj-data>

    <mj-body>
      <mj-section>
        <mj-column>
          <mj-text field="title">
            Hello World!
          </mj-text>
        </mj-column>
      </mj-section>
      <mj-section>
        <mj-row field="items">
          <mj-column>
            <mj-text field="name">
              Unnamed
            </mj-text>
          </mj-column>
          <mj-column>
            <mj-text field="description">...</mj-text>
          <mj-column>
        </mj-row>
      </mj-section>
    </mj-body>
  </mjml>

This would render to identical html as the previous example, the <mj-data> section would just get ignored, but tools could use that metadata to be able to open this file as is and render it with sample data. Designers will be able to edit the original source files and application code is also rendering from the exact same code only with production data. The key is that this puts mjml in the front of the transform pipeline instead of the middle, which allows the code to be edited and viewed as-is without needing manual translations after the designer hand off. The paradigm of the existing tools will work this way, whereas they will not necessarily work once you have templating.

MJML to HTML is a one way trip.

Right but Handlebars to MJML is also a one way trip for the same reason. That's the disconnect the two design tools are not accounting for. They are both showing the MJML on the left and the html on the right, which is encouring people to write the mjml in those tools but yet its being generated by a different tool... you've got to edit the source of truth not some intermediate form down the line. If mjml isn't that then you get into this situation where people come to your github issues page and ask the same dumb question over and over :)

If I use typescript/babel and then I edit the output of it, would I blame typescript to give me a chance of put those changes back into my code base ?

No, I wouldn't but if the typescript team told me to go use handlebars to generate typescript first then I'd have a serious problem.

...don't touch any of the final html if it's not part of an automated process as we do today for any transpiled language.

Exactly, just to be clear here though by taking the position that people should use handlebars to generate mjml then you are putting mjml itself in the position of being a transpiled language that is generated as part of an automated process that shouldn't be touched... which is where the dissonance is coming from and thus this discussion.

Am I making sense? You're taking the position that mjml is output from a different tool. Like html is output of mjml, like js is output from typescript. You said it yourself, output of a tool should not be edited directly because its a one way trip. Yet mjml is very commonly output of a different tool.

No one could end up with a solution that satisfy everyone, so I think, build your own and capitalize on it.

I don't think you need to make everyone happy, everyone is already not happy 😆

It's already hard to find time to keep an open source project active, so let's not go too far of the initial scope.

Going too far on scope is bad... so is not going far enough. I'd recommend weighing the idea on its merits independent of other considerations first.

I think this is a pretty modest proposal and a pretty commonly expected feature that would improve the usability.

If you though this should be in a different layer I totally get that, mjml-data or whatever. Thats a technical details. But bless that and make the tools point at it. There are ways to solve it.

@iRyusa
Copy link
Member

iRyusa commented Jun 26, 2019

Designers will be able to edit the original source files and application code is also rendering from the exact same code only with production data.

They are both showing the MJML on the left and the html on the right, which is encouring people to write the mjml in those tools but yet its being generated by a different tool...

Making a gulp task on your own will make this available with even live reload, and without any addition to the langage and it can be shared & customized accordingly to your need.

MJML App is an electron app, open source, you can clone it add a tab window with json data put handlebars (or whatever templating language in js or in any lang) and then you have it... Problem solved, without even toutching a single line of code of mjml-core.

The solution you provide is just ugly, with limited set of input data structure, and you're not able to scale it for multiple source if you want to test with multiple cases. I can see so many drawback with only one given implementation and so many things that people would want and won't be available. We don't want to close doors to interoperability too, so let's keep those concerns away, it's already complex enough.

Am I making sense? You're taking the position that mjml is output from a different tool. Like html is output of mjml, like js is output from typescript. You said it yourself, output of a tool should not be edited directly because its a one way trip. Yet mjml is very commonly output of a different tool.

Most of templating langages doesn't care if it output HTML, XML, or whatever markup langage they're just abstracting concept of templating. It's a simple I/O interface that you can plug how many time you want to.

Here it's a matter of how applying/manipulating data on a given document. Our stance is to let competent libraries to do the manipulation part and just let MJML to render static piece of content nothing more.

No, I wouldn't but if the typescript team told me to go use handlebars to generate typescript first then I'd have a serious problem.

I don't think you've clearly understand what was the analogy here.

The key is that this puts mjml in the front of the transform pipeline instead of the middle,

But It is in your mind, not in ours. We want to keep MJML as a markup lang, not a templating one.

MJML is a markup language [...] designed to reduce the pain of coding a responsive email.

It's clearly the first phrase of the readme nothing more, nothing less as said earlier we're not an all in one solution.

Going too far on scope is bad... so is not going far enough

I prefer using a good screwdriver instead of a cheap swiss knife that will break as soon as I start to use it.

I think this is a pretty modest proposal and a pretty commonly expected feature that would improve the usability.

It's not "modest", it's a really big thing to do, parsing template, replacing stuff, adding conditionnal & loop & many more to come. It adds a whole new level of complexity in the code base, and a new level of logic that you have to design to be working with custom components & maybe a LOT more new interface developer friendly.

Maybe our position isn't the best but at least you can use MJML today with any lang/framework or product. Like it was done first by Mailjet and you can still use it on different ESP templating lang like Mailchimp, and many templating language Mustache, Handlebard, PugJS, ... Most of them are already used, battle tested, robust and cover every use case you'd want to.

Contributions here are really low, and you would expect us to build a gigantic mammoth. It's free & open source, so you're free to fork MJML and do those features if you want to.

@justinmchase
Copy link
Author

MJML is a markup language [...] designed to reduce the pain of coding a responsive email.

I'm just saying this is a pain point, it would reduce pain to add templating support at some layer. If this is the goal of this tool then I am asking you to please reconsider your position.

@iRyusa
Copy link
Member

iRyusa commented Jun 26, 2019

Well this discussion isn't really going anywhere, I think I've already explain ourselves on why we won't do such thing, if you want so, as said it's an open source project so feel free to fork it.

@justinmchase
Copy link
Author

Related to #1457

@connecteev
Copy link

+1 on this...a very barebones if-else conditional (like vue.js's v-if and v-else) would be really great.

@rsweeneydev
Copy link

The maintainers of mjml seem to be missing the point here. Nearly all automated email that is sent is dynamic - it will have the recipients name or some other personalized element.

The problem developers have is that it is hard to send dynamic HTML emails that will display properly in most email clients. MJML only solves part of the problem.

If you don't want to put templating into MJML, you don't have to, you are the maintainers. You could at least provide an example in the docs of how one could correctly set up a build pipeline that actually solves the entire problem.

@iRyusa
Copy link
Member

iRyusa commented Nov 12, 2019

There's dozen of package/blog post on how to achieve that. There like hundred of templating language, and we won't be able to cover everything in documentation. A small google search about your loved framework/templating lang will probably cover your need :

And some companies like Mailjet did integrate fully MJML in their ecosystem. And we even done an free API https://mjml.io/api (even if registration is a bit broken #1751 but it will be back soon)

If you want to start adding something in the doc because you find it useful for the community go for it. It's open source, not everything need to be done by "maintainers". Every MD files are here https://github.com/mjmlio/mjml/tree/master/doc

@GarryFlemings
Copy link
Contributor

GarryFlemings commented Nov 12, 2019 via email

@justinmchase
Copy link
Author

justinmchase commented Nov 13, 2019

I did look into creating a plugin at one point to help me achieve my goals. It does appear that mjml could actually support the goal I was going for, with regards to adding some new kinds of elements with dynamic behaviors. I appears that I could simply create plugins for all the desired elements but the mjml rendering tool may need some work to really enable plugin work of that complexity. It was having issues where making changes to the plugins weren't picked up without some complex steps to get it to refresh, display errors, and debugging your plugin were complex. It didn't seem like an enormous amount of work was needed to make some improvements to the tool that would help a lot, I just don't have the time at the moment to dig into it.

We ended up building our own pipeline, as recommended, which involved converting all of our mjml templates into react pages which we transpile into mjml and then down to html. I don't love it but it is working. The main problem is that I had to basically throw away the mjml templates that our designer gave us and convert them to something else manually and I can't see the templates without rendering them through our site.

Thanks all.

@andrewperry
Copy link

There's dozen of package/blog post on how to achieve that. There like hundred of templating language, and we won't be able to cover everything in documentation. A small google search about your loved framework/templating lang will probably cover your need :

And some companies like Mailjet did integrate fully MJML in their ecosystem. And we even done an free API https://mjml.io/api (even if registration is a bit broken #1751 but it will be back soon)

If you want to start adding something in the doc because you find it useful for the community go for it. It's open source, not everything need to be done by "maintainers". Every MD files are here https://github.com/mjmlio/mjml/tree/master/doc

I don't know if this blog post is contributing to calls for this to be addressed because it seems to encourage use of MJML without another templating system like Handlebars, Twig etc... https://www.mailjet.com/blog/news/templating-language-tutorials-welcome-email/

I guess it is not surprising that the article and related Github one at https://github.com/mailjet/mailjet-apiv3-templating-samples/tree/master/tutorials/welcome would focus on using the Mailjet API rather than combining with a local templating system.

Coming from that article to here, your suggestion here for templating integrations could perhaps be given more prominence in the README for the project with an Integrations section, just above or below the Section on the API, rather than just being somewhat discoverable from the link provided in the sentence "For more tools, check the Community page"

It might save you some time rehashing the philosophy being taken, and encourage people to share their implementation of MJML using such an integration, along with their documentation of it.

I know that this is my biggest barrier to jumping in and getting started with Handlebars / MJML.

Thanks for sharing all you do @iRyusa

@iRyusa
Copy link
Member

iRyusa commented May 6, 2020

We're discussing about a new page in the doc where we show a minimal setup with gulp+handlebars and point to those libs too @andrewperry you're right about this. 👍

@justinmchase
Copy link
Author

We ended up using mjml-react instead of handlebars. And then we added *.tsx files into our app which contain mjml elements in a react component.

The downsides to it are that our source files are not mjml and so we cannot view the file in the mjml developer tool. Instead we have a route that just lets us render to html and view as html in the browser in local develop mode.

@andrewperry
Copy link

We're discussing about a new page in the doc where we show a minimal setup with gulp+handlebars and point to those libs too @andrewperry you're right about this. 👍

That sounds great, perhaps along the lines of https://medium.com/@toru_aine/how-to-get-more-out-out-of-mandrills-html-email-templates-by-using-handlebars-7c51feff3764 ?

@mercteil
Copy link

Emails without dynamic contents are 1997.

Definitely agree that MJML needs at least a proper handlebars/moustache integration. I have a workaround how I use handlebars in my emails, but again I have to build the MJML first and then apply hbs logic. Thus I cannot use MJML within dynamic content which forces me to write plain html. And as far as I understand MJML is the markup abstraction of obsolete and painful email html.

@iRyusa
Copy link
Member

iRyusa commented Jan 14, 2021

Thus I cannot use MJML within dynamic content which forces me to write plain html.

You can, just take some times to setup a proper workflow with your favorite templating language and MJML

@ngarnier
Copy link
Member

ngarnier commented Jan 14, 2021

We've replied to this plenty of times already. @mercteil what you're saying is basically like saying:

Web pages without easy DOM manipulation is 1995.

HTML needs built-in DOM manipulation.

There are a lot of libraries like jQuery making DOM manipulation easy that you can include in your web page. Similarly, there are a lot of templating languages that you can use in combination with MJML. The answer is certainly not to have template language features as part of MJML like it is not to have DOM manipulation as part of HTML.

We'll publish soon a tutorial on how to set up a workflow with any templating language and MJML, which should hopefully help get started.

Note that there are already a lot of resources from the community on how to set this up, including articles (example: Using MJML with Twig) and client tools (example: email generator). A simple Github search for "mjml + your favorite template language" will also return a lot of results (example: "mjml handlebars").

@LB22
Copy link

LB22 commented Jun 30, 2021

@justinmchase

We ended up using mjml-react

So did I. Highly recommended.

Instead we have a route that just lets us render to html and view as html in the browser in local develop mode.

Are you able to give me an example of how you do this using React/Next.js?

Much appreciated!

@justinmchase
Copy link
Author

justinmchase commented Jun 30, 2021

@LB22 Sorry I don't have access to that code anymore but it was basically just a simple express route which essentially loaded the react component and used it to render down to html.

Possible psuedocode:

import mjml2html from 'mjml'
import { MjmlExample } from './email/templates/example'
import ReactDOMServer from 'react-dom/server';

app.get('/test-email', (req, res) => {
 
   const data = db.example.sample() // get random data from the db
   const mjml = ReactDOMServer.renderToString(MjmlExample({ data }))
   const html = mjml2html(mjml)
   
   res.send(html).status(200)
})

@coolaj86
Copy link

coolaj86 commented Sep 17, 2021

I'm definitely of the opinion that it's best to leave templating to template engines and let MJML focus on being good at solving the email problem.

For anyone using node, ETA is hands-down the simplest, best solution. Example:
https://github.com/coolaj86/node-eta-demo/tree/mjml

In essense:

render.js:

'use strict';

let mjml2html = require('mjml');

let Eta = require('eta');
Eta.configure({ tags: ['{{', '}}'], views: ['.'] });

let tmplText = Fs.readFileSync('welcome.eta.mjml.html', 'utf8');
let vars = require('./vars.json');
let mjmlText = Eta.render(tmplText, vars);

let html = mjml2html(mjmlText, {
  beautify: true,
  keepComments: true,
  validationLevel: 'strict',
}).html;

console.log(html);

welcome.eta.mjml.html:

{{ layout("layouts/transactional-email.eta.mjml.html") }}
<mj-section>
  <mj-column>
    <mj-text>Hello, {{= it.user.given_name }}!</mj-text>
  </mj-column>
</mj-section>

layouts/transactional-email.eta.mjml.html:

<mjml>
  <mj-head>
    <mj-preview>Preview: {{= it.preview }}</mj-preview>
    <mj-title>Title: {{= it.title }}</mj-title>
  </mj-head>
  <mj-body>{{~ it.body }}</mj-body>
</mjml>

vars.json:

{
  "preview": "Read \"friend\" and enter...",
  "title": "Hello from the Elves",
  "user": {
    "given_name": "John",
    "family_name": "Doe"
  }
}

@GarryFlemings
Copy link
Contributor

(@coolaj86): I'm definitely of the opinion that it's best to leave templating to template engines and let MJML focus on being good at solving the email problem.

💯

@Akxe
Copy link

Akxe commented Jan 24, 2022

I think that the docs should be expanded with the templating options. It will alert the user of the fact that templating is not a first-class citizen in this.

@matthew-dean
Copy link

Wait.... so because MJML offers no templating, how do we actually integrate it safely?

Right now, our (Kotlin) backend has been taking generated .html, adding templating tags, and then integrating data. In our (frontend) view, that's not a maintainable solution. We want to define data binding at the MJML level, and be able to test / bind data in both Node.js and Kotlin environments.

Does the MJML team at least have some guidance into a... typical best-practice workflow?

@GarryFlemings
Copy link
Contributor

So far as I know, the MJML team has left that material available for authors of web articles and for documentation in the various templating engines.

I understand many developers who use both a templating engine and MJML embed templating code or data-field names in their MJML. Perhaps there are two ways they could go from here.

If the templating engine is happy to accept and create MJML files, developers could use the templating engine on an MJML file and create a set of new MJML files, one for each desired email. Then, they would run those files through MJML to get HTML for each email.

On the other hand, if the templating engine presumes an HTML file input, they'd run MJML first, to generate an HTML file still containing the templating language. Then, they'd run that file through the templating engine to create the HTML files for sending.

Those processes need details corresponding to the templating engine. We presume the documenters for those engines are best equipped to document the preferred processes.

So, as I said earlier here, the MJML engine converts from MJML to HTML and does it very well. Templating engines produce individualized emails from a template and a set of data-field values. The MJML team would hope you find a templating engine that most exactly your needs and does it very well.

@sgrimm
Copy link

sgrimm commented Apr 20, 2022

@matthew-dean I too have a Kotlin backend that's sending MJML-based email, and I use the process @GarryFlemings described in his comment. The MJML source has template language expressions/directives embedded in it (Apache FreeMarker in my case) and it gets compiled to HTML as a build step. The "HTML" isn't actually the final HTML output, though; it's a FreeMarker HTML template file that then gets rendered at runtime.

At the risk of going a bit into the weeds with my specific setup, which of course would vary if you use a different runtime or a different template engine:

  • The MJML files have .ftlh.mjml extensions.
  • The build turns those into .ftlh files (the file extension that tells FreeMarker it's dealing with HTML). I use the gradle-node-plugin to run the MJML renderer in the build.
  • From the application code's point of view, it's just rendering FreeMarker template files using the normal FreeMarker API. It has no knowledge of MJML.
  • In my IDE (IntelliJ), I have a file type pattern *.ftlh.mjml that is configured as file type FreeMarker with data language MJML. I thus get syntax highlighting + autocomplete for both FreeMarker and MJML (via the MJML plugin) at the same time.

This setup has been working nicely for all our outgoing email messages.

@kenjim83
Copy link

I ended up using the same solution as described here https://stackoverflow.com/questions/43111628/mjml-template-interpolation-dynamic-data-context:

  1. mjml with {{handlebars}} -> populated mjml
  2. mjml -> html

Yes, it has to parse the content twice but it hasn't made a difference in a practical sense. Handlebars is a tried and true templating solution that takes care of many security issues by default.

import { compile } from 'handlebars';
import { mjml2html } from 'mjml';

const template = compile(`
<mjml>
  <mj-body>
    <mj-container>
      <mj-section>
        <mj-column>
          <mj-text>{{message}}</mj-text>
        </mj-column>
      </mj-section>
    </mj-container>
  </mj-body>
</mjml>
`);
const context = {
    message: 'Hello World'
};
const mjml = template(context);
const html = mjml2html(mjml);

@nbouvrette
Copy link

For what it's worth, I just started using mjml this week and I can say the overall DX (developer experience) was not very good. I agree that the templating engine should live outside mjml but here are a few things that for me didn't help:

  • The documentation on mjml itself is quite good but there is no example of any code and searching on Google led me nowhere useful
  • When you search on how to use mjml-raw why does this package even exist? https://www.npmjs.com/package/mjml-raw. Apparently packages for all mjml tags exists.. this is very unusual. Also, the only code example I found is actually in this thread which is a closed Github issue...
  • There are many VSCode plugins and it's hard to even know which one to use. When I finally tried the one provided here, I ended up with exceptions in my console output (related to Prettier) and I had to use the plugin from a 3rd party which seems more advance than this one

I don't know if anyone is working on improving these pain points but otherwise once implemented, the solution is quite good.

@mifi
Copy link
Contributor

mifi commented Jan 15, 2023

Having gone through the same process of finding the best way to add dynamic content to my MJML files while still being able to preview the templates using existing tooling, initially I just used handlebars, but after adding more complex content structures it started to get messy.

But it turns out that MJML is just simple XML and maps quite well to JSON. Rendering from JSON is already supported in MJML, but not parts of the document.

I came up with a technique that allows rendering parts of a .mjml file programmatically, by manipulating the XML(JSON) structure (so no need for manual escaping etc): and so mjml-dynamic was born!

Example:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>

        <mj-button mj-replace-id="myId">
          Some text
        </mj-button>

      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
import mjml2html from 'mjml-dynamic';

const replacers = {
  myId: {
    content: 'new text content',
    attributes: { color: 'red' },
  },
};

const { html } = mjml2html(mjml, { replacers });

This will output the equivalent of the following MJML document:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>

        <mj-button color="red">
          new text content
        </mj-button>

      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

This also allows copying a beatufiul template from https://mjml.io/templates, then modify it to your needs, then replace parts of it with your own programmatic structure, or even a mjml-react sub-tree!

See more examples here :)

See also discussion from #2619

@nbouvrette
Copy link

I came up with a technique that allows rendering parts of a .mjml file programmatically, by manipulating the XML(JSON) structure (so no need for manual escaping etc): and so mjml-dynamic was born!

Unless I misunderstood what you are proposing, you won't be able to have conditional blocks with this strategy.

@mifi
Copy link
Contributor

mifi commented Jan 16, 2023

Unless I misunderstood what you are proposing, you won't be able to have conditional blocks with this strategy.

Yes, you can just use javascript:

const replacers = {
  myId: {
    children: [
      ...(shouldShowText ? [{ tagName: 'mj-text', content: 'some conditional text' }] : []),
      {
        tagName: 'mj-table',
        children: list.map((item) => ({
          tagName: 'tr',
          content: { tagName: 'td', content: item.name },
        }]),
      },
    ],
  },
};

mjml2html(mjml, { replacers });

see examples here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests