{"id":13637007,"url":"https://github.com/jdrouet/mrml","last_synced_at":"2025-05-14T13:02:46.552Z","repository":{"id":39017165,"uuid":"263044305","full_name":"jdrouet/mrml","owner":"jdrouet","description":"Implementation of mjml in rust","archived":false,"fork":false,"pushed_at":"2025-05-12T12:38:52.000Z","size":3261,"stargazers_count":394,"open_issues_count":26,"forks_count":27,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-05-12T12:41:48.427Z","etag":null,"topics":["email","hacktoberfest","mjml","rust"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jdrouet.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":"contributing.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"code-of-conduct.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["jdrouet"]}},"created_at":"2020-05-11T13:03:55.000Z","updated_at":"2025-05-12T11:57:25.000Z","dependencies_parsed_at":"2023-09-30T22:31:58.644Z","dependency_job_id":"37cc81a6-a3bc-4069-ba19-49f8f348aacd","html_url":"https://github.com/jdrouet/mrml","commit_stats":{"total_commits":688,"total_committers":10,"mean_commits":68.8,"dds":"0.15406976744186052","last_synced_commit":"ef618e342550381379a2cfa2a17e23ab50ab5cdc"},"previous_names":[],"tags_count":126,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdrouet%2Fmrml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdrouet%2Fmrml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdrouet%2Fmrml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdrouet%2Fmrml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jdrouet","download_url":"https://codeload.github.com/jdrouet/mrml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253744564,"owners_count":21957309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["email","hacktoberfest","mjml","rust"],"created_at":"2024-08-02T00:01:09.223Z","updated_at":"2025-05-14T13:02:46.409Z","avatar_url":"https://github.com/jdrouet.png","language":"HTML","readme":"# MRML\n\n[![Crates.io](https://img.shields.io/crates/d/mrml)](https://crates.io/crates/mrml)\n![Crates.io](https://img.shields.io/crates/v/mrml)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fjdrouet%2Fmrml.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fjdrouet%2Fmrml?ref=badge_shield)\n\n[![.github/workflows/main.yml](https://github.com/jdrouet/mrml/actions/workflows/mrml-core-main.yml/badge.svg)](https://github.com/jdrouet/mrml/actions/workflows/mrml-core-main.yml)\n[![codecov](https://codecov.io/gh/jdrouet/mrml/branch/main/graph/badge.svg?token=SIOPR0YWZA)](https://codecov.io/gh/jdrouet/mrml)\n\n[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/jdrouet/mrml.svg)](http://isitmaintained.com/project/jdrouet/mrml \"Average time to resolve an issue\")\n[![Percentage of issues still open](http://isitmaintained.com/badge/open/jdrouet/mrml.svg)](http://isitmaintained.com/project/jdrouet/mrml \"Percentage of issues still open\")\n[![Maintainability](https://api.codeclimate.com/v1/badges/7ed23ef670d076ab69a4/maintainability)](https://codeclimate.com/github/jdrouet/mrml/maintainability)\n\n## Introduction\n\nThis project is a reimplementation of the nice [MJML markup language](https://documentation.mjml.io/) in Rust.\n\n## How to use it in my code\n\nUpdate your `cargo.toml`:\n\n```toml\n[dependencies]\nmrml = \"3\"\nserde = { version = \"1.0\", features = [\"derive\"] }\n```\n\nCreate your `main.rs`:\n\n```rust\nuse mrml::prelude::parser::http_loader::{HttpIncludeLoader, BlockingReqwestFetcher};\nuse mrml::prelude::parser::ParserOptions;\nuse mrml::prelude::render::RenderOptions;\nuse std::collections::HashSet;\n\n\nfn main() {\n  let resolver = HttpIncludeLoader::\u003cBlockingReqwestFetcher\u003e::new_allow(HashSet::from([\"http://localhost\".to_string()]));\n  let parser_options = ParserOptions {\n      include_loader: Box::new(resolver),\n  };\n  let render_options = RenderOptions::default();\n  let template = r#\"\u003cmjml\u003e\n  \u003cmj-body\u003e\n    \u003cmj-include path=\"http://localhost/partials/mj-body.mjml\" /\u003e\n  \u003c/mj-body\u003e\n\u003c/mjml\u003e\"#;\n  match mrml::parse_with_options(template, \u0026parser_options) {\n      Ok(mjml) =\u003e match mjml.render(\u0026render_options) {\n        Ok(html) =\u003e println!(\"{html}\"),\n        Err(err) =\u003e eprintln!(\"Couldn't render template: {err:?}\"),\n      },\n      Err(err) =\u003e eprintln!(\"Couldn't parse template: {err:?}\"),\n  }\n}\n```\n\nIt's also possible to use an async include loader\n\n```rust\nuse mrml::mj_include::body::MjIncludeBodyKind;\nuse mrml::prelude::parser::http_loader::{AsyncReqwestFetcher, HttpIncludeLoader};\nuse mrml::prelude::parser::local_loader::LocalIncludeLoader;\nuse mrml::prelude::parser::memory_loader::MemoryIncludeLoader;\nuse mrml::prelude::parser::multi_loader::{MultiIncludeLoader, MultiIncludeLoaderItem, MultiIncludeLoaderFilter};\nuse mrml::prelude::parser::noop_loader::NoopIncludeLoader;\nuse mrml::prelude::parser::loader::AsyncIncludeLoader;\nuse mrml::prelude::parser::AsyncParserOptions;\nuse mrml::prelude::render::RenderOptions;\n\n#[tokio::main]\nasync fn main() {\n  let resolver = MultiIncludeLoader::\u003cBox\u003cdyn AsyncIncludeLoader + Send + Sync + 'static\u003e\u003e::new()\n      .with_starts_with(\"file://\", Box::new(LocalIncludeLoader::new(PathBuf::default().join(\"resources\").join(\"compare\").join(\"success\"))))\n      .with_starts_with(\"https://\", Box::new(HttpIncludeLoader::\u003cAsyncReqwestFetcher\u003e::allow_all()))\n      .with_any(Box::\u003cNoopIncludeLoader\u003e::default());\n  let parser_options = AsyncParserOptions {\n      include_loader: Box::new(resolver),\n  };\n  let render_options = RenderOptions::default();\n  let json = r#\"\u003cmjml\u003e\n  \u003cmj-body\u003e\n    \u003cmj-include path=\"file://basic.mjml\" /\u003e\n  \u003c/mj-body\u003e\n\u003c/mjml\u003e\"#;\n  match mrml::async_parse_with_options(json, std::sync::Arc::new(parser_options)).await {\n      Ok(mjml) =\u003e match mjml.render(\u0026render_options) {\n        Ok(html) =\u003e println!(\"{html}\"),\n        Err(err) =\u003e eprintln!(\"Couldn't render template: {err:?}\"),\n      },\n      Err(err) =\u003e eprintln!(\"Couldn't parse template: {err:?}\"),\n  }\n}\n```\n\n## Why?\n\n- A Node.js server rendering an MJML template takes around 20 MB of RAM at startup and 130 MB under stress test. In Rust, less than 1.7 MB at startup and a bit less that 3 MB under stress test.\n- The JavaScript implementation cannot be run in the browser; the Rust one (and WebAssembly one) can be.\n\n## You want to contribute?\n\nFeel free to read our [contributing](./contributing.md) section and the [code of conduct](./code-of-conduct.md).\n\n## Performance\n\nWith the same Linux amd64 machine, to render the amario template using [hyperfine](https://github.com/sharkdp/hyperfine) (see the script in the `benchmarks` folder).\n\n```\nBenchmark 1: mjml /amario.mjml\n  Time (mean ± σ):     634.1 ms ±   5.2 ms    [User: 669.3 ms, System: 168.2 ms]\n  Range (min … max):   625.8 ms … 642.3 ms    10 runs\n\nBenchmark 2: /usr/bin/mrml /amario.mjml render\n  Time (mean ± σ):       5.6 ms ±   0.1 ms    [User: 2.8 ms, System: 2.9 ms]\n  Range (min … max):     5.5 ms …   7.1 ms    494 runs\n\nSummary\n  /usr/bin/mrml /amario.mjml render ran\n  112.83 ± 2.12 times faster than mjml /amario.mjml\n```\n\nFrom this, you can see that `mrml` is **more than 110 faster** than `mjml`.\n\n## Missing implementations\n\n- `mj-style[inline]`: not yet implemented. It requires parsing the generated html to apply the inline styles afterward (that's how it's done in mjml) which would kill the performances. Applying it at render time would improve the performance but it would still require to parse the CSS.\n\n## Who is using MRML?\n\n[\u003cimg src=\"https://www.blizzstatic.com/www/marketing/images/logo.svg\" height=\"22px\" /\u003e](https://www.blizzfull.com/)\n\n\u003ci\u003eIf you are using MRML and want to be added to this list, don't hesitate to create an issue or open a pull request.\u003c/i\u003e\n\n## What is using MRML?\n\n[mjml_nif](https://github.com/adoptoposs/mjml_nif) - Elixir library\n\n[mrml-ruby](https://github.com/hardpixel/mrml-ruby) - Ruby library\n\n[mjml-python](https://github.com/mgd020/mjml-python) - Python library\n\n[wagtail-newsletter](https://github.com/wagtail/wagtail-newsletter) - Wagtail extension\n\n\u003ci\u003eIf you are using MRML and want to be added to this list, don't hesitate to create an issue or open a pull request.\u003c/i\u003e\n\n## License\n\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fjdrouet%2Fmrml.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fjdrouet%2Fmrml?ref=badge_large)\n","funding_links":["https://github.com/sponsors/jdrouet"],"categories":["Libraries","库 Libraries","HTML","虚拟化"],"sub_categories":["Email","电子邮件 Email","邮件处理"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdrouet%2Fmrml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdrouet%2Fmrml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdrouet%2Fmrml/lists"}