1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-18 16:11:28 -05:00

[PM-22423] Add MJML (#5941)

Scaffolds MJML and adds some initial templates and components.

Of interest are:

* src/Core/MailTemplates/Mjml/components/hero.js demonstrates how to create a custom MJML component. In our case it's a hero component with our logo, a title, a call to action button and an image.
* src/Core/MailTemplates/Mjml/components/head.mjml defines some common styling.
* src/Core/MailTemplates/Mjml/components/footer.mjml social links and footer.
This commit is contained in:
Oscar Hinton
2025-07-15 15:53:29 +02:00
committed by GitHub
parent d3c0dca178
commit 42ff09b84f
13 changed files with 2468 additions and 0 deletions

3
.github/CODEOWNERS vendored
View File

@ -33,6 +33,9 @@ util/SqliteMigrations/** @bitwarden/dept-dbops
# Shared util projects
util/Setup/** @bitwarden/dept-bre @bitwarden/team-platform-dev
# UIF
src/Core/MailTemplates/Mjml @bitwarden/team-ui-foundation # Teams are expected to own sub-directories of this project
# Auth team
**/Auth @bitwarden/team-auth-dev
bitwarden_license/src/Sso @bitwarden/team-auth-dev

1
.gitignore vendored
View File

@ -214,6 +214,7 @@ bitwarden_license/src/Sso/wwwroot/assets
.idea/*
**/**.swp
.mono
src/Core/MailTemplates/Mjml/out
src/Admin/Admin.zip
src/Api/Api.zip

View File

@ -0,0 +1,5 @@
{
"packages": [
"components/hero"
]
}

View File

@ -0,0 +1,19 @@
# Email templates
This directory contains MJML templates for emails sent by the application. MJML is a markup language designed to reduce the pain of coding responsive email templates.
## Usage
```bash
npm ci
# Build once
npm run build
# To build on changes
npm run watch
```
## Development
MJML supports components and you can create your own components by adding them to `.mjmlconfig`.

View File

@ -0,0 +1,4 @@
# TODO: This should probably be replaced with a node script building every file in `emails/`
npx mjml emails/invite.mjml -o out/invite.html
npx mjml emails/two-factor.mjml -o out/two-factor.html

View File

@ -0,0 +1,53 @@
<mj-section>
<mj-column>
<mj-social icon-size="30px" inner-padding="10px" padding="0">
<mj-social-element
href="https://twitter.com/bitwarden"
src="https://bitwarden.com/images/mail-twitter.png"
></mj-social-element>
<mj-social-element
href="https://www.reddit.com/r/Bitwarden/"
src="https://bitwarden.com/images/mail-reddit.png"
></mj-social-element>
<mj-social-element
href="https://community.bitwarden.com/"
src="https://bitwarden.com/images/mail-discourse.png"
></mj-social-element>
<mj-social-element
href="https://github.com/bitwarden"
src="https://bitwarden.com/images/mail-github.png"
></mj-social-element>
<mj-social-element
href="https://www.youtube.com/channel/UCId9a_jQqvJre0_dE2lE_Rw"
src="https://bitwarden.com/images/mail-youtube.png"
></mj-social-element>
<mj-social-element
href="https://www.linkedin.com/company/bitwarden1/"
src="https://bitwarden.com/images/mail-linkedin.png"
></mj-social-element>
<mj-social-element
href="https://www.facebook.com/bitwarden/"
src="https://bitwarden.com/images/mail-facebook.png"
></mj-social-element>
</mj-social>
<mj-text align="center" font-size="12px" line-height="16px" color="#5A6D91">
<p style="margin-bottom: 5px">
© 2025 Bitwarden Inc. 1 N. Calle Cesar Chavez, Suite 102, Santa
Barbara, CA, USA
</p>
<p style="margin-top: 5px">
Always confirm you are on a trusted Bitwarden domain before logging
in:<br />
<a href="#">bitwarden.com</a> |
<a href="#">Learn why we include this</a>
</p>
</mj-text>
</mj-column>
</mj-section>

View File

@ -0,0 +1,16 @@
<mj-attributes>
<mj-all
font-family="'Helvetica Neue', Helvetica, Arial, sans-serif"
font-size="16px"
/>
<mj-button background-color="#175ddc" />
<mj-text color="#333" />
<mj-body background-color="#e6e9ef" width="660px" />
</mj-attributes>
<mj-style inline="inline">
.link { text-decoration: none; color: #175ddc; font-weight: 600 }
</mj-style>
<mj-style>
.border-fix > table { border-collapse:separate !important; } .border-fix >
table > tbody > tr > td { border-radius: 3px; }
</mj-style>

View File

@ -0,0 +1,64 @@
const { BodyComponent } = require("mjml-core");
class MjBwHero extends BodyComponent {
static dependencies = {
// Tell the validator which tags are allowed as our component's parent
"mj-column": ["mj-bw-hero"],
"mj-wrapper": ["mj-bw-hero"],
// Tell the validator which tags are allowed as our component's children
"mj-bw-hero": [],
};
static allowedAttributes = {
"img-src": "string",
title: "string",
"button-text": "string",
"button-url": "string",
};
static defaultAttributes = {};
render() {
return this.renderMJML(`
<mj-section
full-width="full-width"
background-color="#175ddc"
border-radius="4px 4px 0 0"
>
<mj-column width="70%">
<mj-image
align="left"
src="https://bitwarden.com/images/logo-horizontal-white.png"
width="150px"
height="30px"
></mj-image>
<mj-text color="#fff" padding-top="0" padding-bottom="0">
<h1 style="font-weight: normal; font-size: 24px; line-height: 32px">
${this.getAttribute("title")}
</h1>
</mj-text>
<mj-button
href="${this.getAttribute("button-url")}"
background-color="#fff"
color="#1A41AC"
border-radius="20px"
align="left"
>
${this.getAttribute("button-text")}
</mj-button
>
</mj-column>
<mj-column width="30%" vertical-align="bottom">
<mj-image
src="${this.getAttribute("img-src")}"
alt=""
width="140px"
height="140px"
padding="0"
/>
</mj-column>
</mj-section>
`);
}
}
module.exports = MjBwHero;

View File

@ -0,0 +1,11 @@
<mj-section>
<mj-column>
<mj-image
align="center"
padding="10px 25px"
src="https://bitwarden.com/images/logo-horizontal-blue.png"
width="250px"
height="39px"
></mj-image>
</mj-column>
</mj-section>

View File

@ -0,0 +1,49 @@
<mjml>
<mj-head>
<mj-include path="../components/head.mjml" />
</mj-head>
<mj-body>
<mj-wrapper css-class="border-fix" padding="20px 20px">
<mj-bw-hero
img-src="https://assets.bitwarden.com/email/v1/business.png"
title="A Bitwarden member has invited you to Bitwarden Password Manager"
button-text="Finish account setup"
button-url="#"
/>
<mj-section>
<mj-column>
<mj-button href="#">Join Organization Now</mj-button>
<mj-text>
This invitation expires on
<b>Tuesday, January 23, 2024 2:59PM UTC</b>.
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#fbfbfb">
<mj-column width="70%">
<mj-text line-height="24px">
<h3 style="font-size: 20px; margin: 0; line-height: 28px">
Were here for you!
</h3>
If you have any questions, search the Bitwarden
<a href="#" class="link">Help</a>
site or
<a href="#" class="link">contact us</a>.
</mj-text>
</mj-column>
<mj-column width="30%">
<mj-image
src="https://assets.bitwarden.com/email/v1/chat.png"
height="77px"
width="94px"
/>
</mj-column>
</mj-section>
</mj-wrapper>
<mj-include path="../components/footer.mjml" />
</mj-body>
</mjml>

View File

@ -0,0 +1,27 @@
<mjml>
<mj-head>
<mj-include path="../components/head.mjml" />
</mj-head>
<mj-body background-color="#f6f6f6">
<mj-include path="../components/logo.mjml" />
<mj-wrapper
background-color="#fff"
border="1px solid #e9e9e9"
css-class="border-fix"
padding="0"
>
<mj-section>
<mj-column>
<mj-text>
<p>Your two-step verification code is: <b>{{Token}}</b></p>
<p>Use this code to complete logging in with Bitwarden.</p>
</mj-text>
</mj-column>
</mj-section>
</mj-wrapper>
<mj-include path="../components/footer.mjml" />
</mj-body>
</mjml>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,30 @@
{
"name": "@bitwarden/mjml-emails",
"version": "1.0.0",
"description": "Email templates for Bitwarden",
"private": true,
"type": "commonjs",
"repository": {
"type": "git",
"url": "git+https://github.com/bitwarden/server.git"
},
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
"license": "SEE LICENSE IN LICENSE.txt",
"bugs": {
"url": "https://github.com/bitwarden/server/issues"
},
"homepage": "https://bitwarden.com",
"scripts": {
"build": "./build.sh",
"watch": "nodemon --exec ./build.sh --watch ./components --watch ./emails --ext js,mjml",
"prettier": "prettier --cache --write ."
},
"dependencies": {
"mjml": "4.15.3",
"mjml-core": "4.15.3"
},
"devDependencies": {
"nodemon": "3.1.10",
"prettier": "3.5.3"
}
}