Web

MJML email

MJML email templates rendered to responsive HTML email for Gmail, Outlook and Apple Mail, with placeholders filled in and escaped; no Node needed.

Version 1.0.0 · 3 functions · 1 crate

#email#templates#html#newsletters

Functions

What use mjml.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use mjml_to_html, mjml_render from mjml (Quillo 0.6).

mjml_to_html(template: str) → stror an error

MJML (<mjml><mj-body><mj-section><mj-column><mj-text>...) as the email's HTML, with inline styles and tables for old clients. <mj-include> is not followed: templates read no files.

mjml_render(template: str, vars: map) → anyor an error

A template with vars filled in, rendered: { html, title, preview } (title from <mj-title>, preview from <mj-preview>, the line inboxes show). {{name}} is escaped, {{{name}}} is not, and {{order.id}} reaches into maps.

mjml_check(template: str) → any

Whether a template parses: { ok, error }, for an editor that saves templates.

Get it

$ quillo install mjml               # mjml.qwer into this folder$ quillo install mjml --example     # and its example, mjml.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/mjml.qwer$ curl -O https://quillolang.org/plugins/files/mjml.qwe$ curl -O https://quillolang.org/plugins/files/mjml_sample.mjml

Then, in your program, all of it or only what you call:

main.qwe
use mjml.qweruse mjml_to_html, mjml_render from mjml      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
mrml = { version = "4", default-features = false, features = ["parse", "render"] }

Example

mjml.qwe
// A "your order has shipped" email from an MJML template, filled in per customer and shown in the browser.////   quillo run mjml.qwe      then open http://localhost:8080/emailuse "mjml.qwer"let TEMPLATE = read_file("mjml_sample.mjml")?fn rows(lines) = join(lines | map((l) => `<tr><td>${escape(l.item)}</td><td align="right">${l.qty} × €${l.price}</td></tr>`), "")fn shipped_email(name, order) = mjml_render(TEMPLATE, { name, order, rows: rows(order.lines) })let order = { id: "A-1042", arrives: "Friday", url: "https://shop.example/track/A-1042",              lines: [{ item: "Notebook", qty: 2, price: "4.99" }, { item: "Pen <blue>", qty: 3, price: "1.25" }] }route GET /email = raw(shipped_email("Ada & co", order)?.html)route GET /subject {    let e = shipped_email("Ada", order)?    { subject: e.title, preview: e.preview, bytes: len(e.html) > 1000 }}route GET /check = { good: mjml_check(TEMPLATE).ok, broken: mjml_check("<mjml><mj-body><mj-section></mj-body></mjml>").ok, missing: mjml_render(TEMPLATE, { name: "Ada" }).error }
$ quillo run mjml.qwe$ curl 'localhost:8080/email'$ curl 'localhost:8080/subject'$ curl 'localhost:8080/check'

Source

mjml.qwer
//! MJML email templates rendered to responsive HTML email that works in Gmail, Outlook and Apple Mail, with `{{name}}` placeholders filled in and escaped. Pure Rust (MRML), no Node.//! version: 1.0.0//!//! [dependencies]//! mrml = { version = "4", default-features = false, features = ["parse", "render"] }use serde_json::{json, Value};use std::collections::HashMap;const MAX_TEMPLATE: usize = 2 << 20;fn escape(s: &str) -> String {    s.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;").replace('"', "&quot;").replace('\'', "&#39;")}fn text(v: &Value) -> String {    match v {        Value::Null => String::new(),        Value::String(s) => s.clone(),        other => other.to_string(),    }}/// `{{name}}` replaced by the escaped value, `{{{name}}}` by the value as it is (for HTML you made), `{{a.b}}` reaching into maps; an unknown name is an error.fn fill(template: &str, vars: &HashMap<String, Value>) -> Result<String, String> {    let mut out = String::with_capacity(template.len());    let mut rest = template;    while let Some(i) = rest.find("{{") {        out.push_str(&rest[..i]);        let raw = rest[i..].starts_with("{{{");        let (open, close) = if raw { (3, "}}}") } else { (2, "}}") };        let after = &rest[i + open..];        let end = after.find(close).ok_or("a `{{` is not closed")?;        let name = after[..end].trim();        let mut parts = name.split('.');        let first = parts.next().unwrap_or("");        let mut v = vars.get(first).ok_or(format!("no value for {{{{{}}}}}", name))?;        for p in parts {            v = v.get(p).ok_or(format!("no value for {{{{{}}}}}", name))?;        }        out.push_str(&if raw { text(v) } else { escape(&text(v)) });        rest = &after[end + close.len()..];    }    out.push_str(rest);    Ok(out)}fn render(template: &str) -> Result<mrml::mjml::Mjml, String> {    if template.len() > MAX_TEMPLATE {        return Err("a template is at most 2 MB".into());    }    mrml::parse(template).map_err(|e| format!("the MJML does not parse: {}", e))}/// MJML (`<mjml><mj-body><mj-section><mj-column><mj-text>...`) as the email's HTML, with inline styles and tables for old clients./// `<mj-include>` is not followed: templates read no files.pub fn mjml_to_html(template: &str) -> Result<String, String> {    render(template)?.render(&Default::default()).map_err(|e| e.to_string())}/// A template with `vars` filled in, rendered: `{ html, title, preview }` (`title` from `<mj-title>`, `preview` from `<mj-preview>`, the line inboxes show)./// `{{name}}` is escaped, `{{{name}}}` is not, and `{{order.id}}` reaches into maps.pub fn mjml_render(template: &str, vars: std::collections::HashMap<String, serde_json::Value>) -> Result<Value, String> {    let filled = fill(template, &vars)?;    let m = render(&filled)?;    let html = m.render(&Default::default()).map_err(|e| e.to_string())?;    Ok(json!({ "html": html, "title": m.get_title(), "preview": m.get_preview() }))}/// Whether a template parses: `{ ok, error }`, for an editor that saves templates.pub fn mjml_check(template: &str) -> Value {    match render(template).and_then(|m| m.render(&Default::default()).map_err(|e| e.to_string())) {        Ok(_) => json!({ "ok": true, "error": null }),        Err(e) => json!({ "ok": false, "error": e }),    }}
Thread
89Useful? Like it, or add to its thread below.

Thread 0 posts

On MJML email: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.