{"id":44482347,"url":"https://github.com/multivoltage/marilena","last_synced_at":"2026-02-13T00:01:57.116Z","repository":{"id":152595161,"uuid":"620922151","full_name":"multivoltage/marilena","owner":"multivoltage","description":"Develop emails with \"opinioned\" stuffs like MJML, different template engines, YML variables and fast browser refresh","archived":false,"fork":false,"pushed_at":"2024-04-17T09:13:41.000Z","size":1413,"stargazers_count":2,"open_issues_count":12,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-17T08:04:21.479Z","etag":null,"topics":["email-development","eta","handlebars","mjml","mjml-email","template-engines"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/multivoltage.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-03-29T16:25:51.000Z","updated_at":"2023-11-10T07:53:48.000Z","dependencies_parsed_at":"2023-11-22T23:29:08.826Z","dependency_job_id":"b4d19595-f5b9-4f42-9d82-606c638140e0","html_url":"https://github.com/multivoltage/marilena","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/multivoltage/marilena","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multivoltage%2Fmarilena","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multivoltage%2Fmarilena/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multivoltage%2Fmarilena/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multivoltage%2Fmarilena/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/multivoltage","download_url":"https://codeload.github.com/multivoltage/marilena/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/multivoltage%2Fmarilena/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29387672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T22:07:52.078Z","status":"ssl_error","status_checked_at":"2026-02-12T22:07:49.026Z","response_time":55,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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-development","eta","handlebars","mjml","mjml-email","template-engines"],"created_at":"2026-02-13T00:00:45.686Z","updated_at":"2026-02-13T00:01:57.106Z","avatar_url":"https://github.com/multivoltage.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003emarilena\u003c/h1\u003e\n\u003cp align=\"center\"\u003ea tool to build emails with cool stuff like mjml and different template engine like handlebars or eta.js\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"version\" src=\"https://img.shields.io/npm/v/marilena.svg\" /\u003e\n\u003c/p\u003e\n\n## The problem\n\nWe know emails are `VERY HARD` to develop from scratch. Even if there are tools like Maiject, SendPulse, MailerSend, Stripo.email etc (maybe with a good drag-n-drop UI) sometimes you find yourself in one of the following cases:\n\n- you need to produce some html files using layouts and partials for different languages, passing them to a backend witch add maybe some data coming from itself (or another service) and fills your html files with its template engine.\n- backend of your company deploy email using with a custom service that use Amazon SES.\n- previous providers are not able to customize some parts. For example css rule. Or to do this is very complicated.\n- coming from [email-foundation-template](https://github.com/foundation/foundation-emails) and you want to try a different tool with advantages (please read below about features), or you need to use different template engine with more power (see Eta.js).\n- having complete control of development without use any Saas is mandatory.\n\n## This solution\n\n`marilena` wants to mix up MJML, an optional template engine for variables and a web server, to create a tool able to generate emails with a simple flow and one command. Please keep in mind that Maiject, SendPulse, MailerSend, Stripo.email etc maybe can be already perfect for your purpose.\n\n## 🚀 Usage\n\n### Install (require node \u003e=18)\n\n```sh\nnpm i marilena\n```\n\n### Setup\n\n`marilena` provides a command witch generate a small but working example with eta.js, variables, layout and partials. You can generate an example with:\n\n```\nnpx marilena --create-example\n```\n\nand change `package.json` like:\n\n```json\n\"type\": \"module\",\n\"scripts\": {\n  \"start\": \"marilena --server --project example/marilena.config.mjs\",\n  \"build\": \"marilena --build --project example/marilena.config.mjs\",\n},\n```\n\n### Setup (manual)\n\nIf you fails to generate the example or you want to build a project from 0 you need to crete `marilena.config.mjs` file in the root of your project. Please check below the fields since any of these are required.\n\n```js\nimport path from \"node:path\";\n// you can leverage your IDE's intellisense with jsdoc type hints\n/** @type {import('marilena/dist/src/types').UserConfig} */\nexport default {\n  inputFolder: \"./input\",\n  outputFolder: \"./output\",\n  textVersion: (emailName, locale) =\u003e `${emailName}_text_version-${locale}.txt`,\n  htmlVersion: (emailName, locale) =\u003e `${emailName}-custom.html`,\n  locales: [\"it\", \"en\"],\n  templateOptions: {\n    engine: \"eta\",\n    prepareEngine: (eta) =\u003e {\n      eta.configure({\n        views: path.join(process.cwd(), \"playground/input\"),\n      });\n    },\n  },\n  mjmlParsingOptions: {\n    keepComments: false,\n  },\n};\n```\n\nEdit you `package.json`. By default `marilena` try to find config in the root of your project. If you put the config in a different path, you need to pass `--project` argument in the scripts\n\n```json\n\"scripts\": {\n  \"start\": \"marilena --server\",\n  \"build\": \"marilena --build\",\n},\n```\n\ncreate a file structures based on your config. Please remember that each email template requires `index.html` as name, and variables are loaded only from `variables.json` or `variables.yml`. Yes you can use both (result will be an union of two).\n\n```\nproject\n| marilena.config.mjs\n│ package.json\n│ input\n│ └──common-en.json // common json variables for all en emails\n│ └──common-it.yaml // common yaml variables for all it emails\n│ └──buy // email name\n││││││└─── index.html\n││││││└─── en\n│││││││││││└── variables.json // json variables for en buy email\n││││││└─── it\n│││││││││││└── variables.yaml // yaml variables for it buy email\n```\n\n3 - fill your emails template with MJML syntax\n\n```html\n\u003cmjml\u003e\n  \u003cmj-body\u003e\n    \u003cmj-section\u003e\n      \u003cmj-column\u003e\n        \u003c!-- eta js example, read below about template engine --\u003e\n        \u003cmj-text\u003ehello \u003c%= it.user %\u003e\u003c/mj-text\u003e\n      \u003c/mj-column\u003e\n    \u003c/mj-section\u003e\n  \u003c/mj-body\u003e\n\u003c/mjml\u003e\n```\n\n4 - run one of these 2 commands\n\n```sh\n# open a server on http://localhost:8080\nnpm run start\n```\n\n```sh\n# build all emails based on config\nnpm run build\n```\n\n## Configuration\n\nUnder the hood a default configuration will be loaded but a file `marilena.config.mjs` allow us to set:\n| name | required | description | default |\n| ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |\n| inputFolder | | folder where email are in the project. Path is relative to `marilena.config.mjs` | ./input |\n| outputFolder | | folder used for generated email (when run build command). Path is relative to `marilena.config.mjs` | ./output |\n| locales | | array of languages used. If you need only spanish email use an array of single value | [\"en\"] |\n| templateOptions | | if you chose to use one of supported engines, this part is mandatory to setup custom partial and other settings for the template engine selected. Read below for some use cases | empty |\n| mjmlParsingOptions | | options passed to mjml render. See: [mjml options](https://www.npmjs.com/package/mjml) |\n| htmlVersion | | function of type `(emailName: string, locale: string) =\u003e string`. If set, this function allow to customize the output html filename. The function must return file name `es: ${emailName}-${locale}.html` | index.html |\n| textVersion | | function of type `(emailName: string, locale: string) =\u003e string`. If set, this function allow to generate text version of email stripping all html. The function must return file name `es: ${emailName}-${locale}-text-version.txt` |\n| sendTestOptions | | option in case you want to send the email to some account for testing. Setting this should add `send-email` button during development: Read below for some use cases |\n| fillFakeMetaData | | function of type `(outputHtml: string, fakeData: object) =\u003e string`. If set, this function allow to \"simulate\" a backend, parsing final output and replace with fake data. See dedicate section for details. |\n\n---\n\n## Load env variables\n\nMarilena uses `dotenv` out of the box. So if you create `.env` file (or it is created by runnning `create-example`) marilena will load variables from there.\n\n## About templateOptions\n\nThis project can producte output html from input template. But in a real word probably we store variables in some part and render some content multiple times (example a footer). In this case `templateOptions` can define:\n\n- `engine`: `eta` or `handlebars` are supported. These deps are peer-dependency so if you want to use ones please install that as dependency.\n- `prepareEngine`: define a callback where we can setup our engine. Basically you can define all things before the render. For example:\n\n```js\ntemplateOptions: {\n  engine:  \"eta\",\n  prepareEngine: (eta) =\u003e {\n    // eta is an istance of new Eta() so you need to set at least views options for templates/layout/partials\n    eta.configure({\n      views: path.join(process.cwd(), \"input\"),\n    });\n    // we can call all eta-js api like:\n    eta.loadTemplate(...);\n  },\n},\n```\n\n```js\ntemplateOptions: {\n  engine:  \"handlebars\",\n  prepareEngine: (h) =\u003e {\n    // we can register partial like:\n    // handlebars is same of var h = require(\"handlebars\");\n    h.registerPartial(\"myPartial\", \"partial with {{ user }}\");\n  },\n},\n```\n\n---\n\n## About sendTestOptions\n\nThis option provides a fast way to test email sending an email to real account. You shoud pass also `createTransport` function that return a `Transporter`.\nSee [nodemailer tutorial](https://nodemailer.com/smtp/)\n\nExample `marilena.config.mjs` to work with Aws SES:\n\n```js\nimport * as aws from \"@aws-sdk/client-ses\";\nimport nodemailer from \"nodemailer\";\n\nexport default {\n  ...config,\n  sendTestOptions: {\n    to: \"diego.tonini93@gmail.com\",\n    from: \"noreply@custom-domain.com\", // this is not random email, but should be registered in you provider\n    createTransport: () =\u003e\n      nodemailer.createTransport({\n        SES: {\n          ses: new aws.SES({\n            apiVersion: \"2010-12-01\",\n            region: \"us-east-1\",\n            credentials: {\n              accessKeyId: process.env.AWS_ACCESS_KEY || \"secret\",\n              secretAccessKey: process.env.AWS_SECRET || \"secret\",\n            },\n          }),\n          aws,\n        },\n      }),\n  },\n};\n```\n\nExample `marilena.config.mjs` to work with forwardemail or other custom setting;\n\n```js\nimport nodemailer from \"nodemailer\";\n\nexport default {\n  ...config,\n  sendTestOptions: {\n    to: \"diego.tonini93@gmail.com\",\n    from: \"noreply@custom-domain.com\", // this is not random email, but should be registered in you provider\n    createTransport: () =\u003e\n      nodemailer.createTransport({\n        host: \"smtp.forwardemail.net\",\n        port: 465,\n        secure: true,\n        auth: {\n          // TODO: replace `user` and `pass` values from \u003chttps://forwardemail.net\u003e\n          user: \"REPLACE-WITH-YOUR-ALIAS@YOURDOMAIN.COM\",\n          pass: \"REPLACE-WITH-YOUR-GENERATED-PASSWORD\",\n        },\n      }),\n  },\n};\n```\n\n## About fillFakeMetaData\n\nIn the real world the html produced by `marilena` is consumed by a backend. Probably this backend will fill email with real data using some template engine like jinja (php) or handlebars (php js) or blocks (go) etc. In some case we can mock a minimal behavior. This is useful for:\n\n- render email with custm data\n- test a send with some data\n\nFollow these step:\n\n- add `fillFakeMetaData` in `marilena.config.mjs`\n\n```js\nimport Handlebars from \"handlebars\";\n\nexport default {\n  /* this example is made with Handlebars but you can use any js template engine */\n  fillFakeMetaData: (outputHtml, fakeData) =\u003e {\n    const template = Handlebars.compile(outputHtml);\n    return template(fakeData);\n  },\n};\n```\n\n- create `metadata.json` or `metadata.yml` under `[inputFolder]/[emailName]/[locale]` path (same path of variables).\n\n```json\n{\n  \"user\": \"Diego Tonini\",\n  \"people\": [\n    {\n      \"name\": \"Luca Pavesi\",\n      \"age\": 23\n    }\n  ]\n}\n```\n\n## Add css/scss style\n\nYou have some options to apply styles on your email:\n\n- Use `\u003cmj-style\u003e` tag (read MJML documentatation)\n\n```\n\u003cmj-style inline=\"inline\"\u003e\n  .blue-text div {\n    color: blue !important;\n  }\n\u003c/mj-style\u003e\n```\n\n- create a `style.css` inside `inputFolder` and import in `mj-include` tag. Path start from root directory of the project (like `package.json`):\n\n```xml\n\u003cmjml\u003e\n  \u003cmj-include path=\"input/styles.css\" type=\"css\" css-inline=\"inline\"/\u003e\n  \u003cmj-body\u003e\n    \u003c!-- other mjml nodes --\u003e\n  \u003c/mj-body\u003e\n\u003c/mjml\u003e\n```\n\n- create a `style.scss` inside `inputFolder`. A compiled css will be added to the `\u003chead\u003e` of document.\n\n## 🚀 Features\n\n- [x] MJML support\n- [x] load variables with template engine\n- [x] multi language out of the box\n- [x] eta.js, handlebars (need to install if you use one of these engines)\n- [x] fast-refresh on variables changes\n- [x] fast-refresh on template change\n- [x] fast-refresh on css change\n- [x] load varibles from yaml/json format\n- [x] load common variables\n- [x] pass option to MJML render\n- [x] send test email (nodemailer, aws ses)\n\n## 🏗️ Roadmap (PRs are welcome 😀)\n\n- [ ] liquid, ejs, nunjucks, mustache, dot\n- [ ] config in typescript\n- [ ] fast-refresh on config change\n- [ ] snaphost test for each email out of the box\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultivoltage%2Fmarilena","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmultivoltage%2Fmarilena","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmultivoltage%2Fmarilena/lists"}