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
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 errorMJML (<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 errorA 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) → anyWhether 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.qweOr 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.mjmlThen, in your program, all of it or only what you call:
use mjml.qweruse mjml_to_html, mjml_render from mjml // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
mrml = { version = "4", default-features = false, features = ["parse", "render"] }Example
// 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 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('&', "&").replace('<', "<").replace('>', ">").replace('"', """).replace('\'', "'")}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 }), }}Related
css_inline.qwer · 1.0.0CSS inlined into HTML for email: style blocks and extra CSS written into each element's style attribute, with nothing ever fetched.7 views · 6 downloadsTemplates templates.qwer · 1.0.0Jinja templates your users can edit for emails, invoices and pages: sandboxed, escaped for HTML, with filters such as money and truncate, and checked before they are saved.7 views · 8 downloadsLink finder link_extract.qwer · 1.0.0URLs and emails found in plain text, with trailing punctuation handled, and the text turned into safe HTML with clickable, nofollow links.7 views · 6 downloadsHTML to text html_text.qwer · 1.0.0HTML as readable plain text for the text part of an email: headings, lists and tables laid out, links listed at the end, and one-line previews.7 views · 7 downloadsDNS dns.qwer · 1.0.0DNS from the server: A, AAAA, MX, TXT, CNAME, NS, SOA, SRV, CAA and PTR records looked up through the system resolver or one you name, host names resolved, and record text and zone files read into fields.7 views · 8 downloadsHTML rewriting html_rewrite.qwer · 1.0.0HTML rewritten as it streams, by CSS selector: links made absolute, attributes set or removed, elements removed or unwrapped, text replaced.8 views · 6 downloadsThread 0 posts
On MJML email: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.