Electronic Mail, popularly known as email, is an electronic means of exchanging messages between people. The content of an email can be texts, images, videos, links, e.t.c, which makes it tricky when building for different screen sizes and making it responsive.
This post will discuss building a responsive email with images using MJML and Node.js. At the end of this tutorial, we will learn how to configure Node.js to serve email templates, use a templating engine to pass in custom variables, and use MJML to build responsive email.
MJML is a framework for building responsive email templates. It is responsive by default and does not require writing media queries to cater for different screen sizes. MJML uses an HTML-like semantic syntax as its building block that supports extensibility and code reuse. MJML also comes with an engine for transpiling MJML code into the desired format.
We completed this project in a CodeSandbox, and you can fork it to run the code.
Github link here.
The following steps in this post require JavaScript and Node.js experience. Experience with MJML isn’t a requirement, but it’s nice to have.
We need to create a Node.js starter project by navigating to the desired directory and running the command below in our terminal.
mkdir mail_temp && cd mail_temp && npm init -y
The command creates a mail_temp
directory, navigates into the directory, and creates a Node.js project with a package.json
file, a JSON file for configuring and monitoring application installed dependencies.
PS: -y
flag stands for yes, and it tells npm
to generate an empty project without going through an interactive process.
We proceed to install the required dependencies with:
npm i mjml express eta
mjml
is a framework for creating responsive email
express
is a Node.js framework for creating web and mobile applications.
eta
is a JavaScript templating engine. It lets us include logic in our markup.
It is essential to have a good folder structure for our project. It makes it easier for us and others to read our codebase.
To do this, we need to create src
and views
folder in our project directory.
src
is a folder for structuring our email template.
views
is a folder for structuring application views and generated outputs from MJML.
Application Configuration
Next, we need to setup up our project to transpile and run effectively on the browser. To do this, we need to modify the package.jon
file as shown below:
{
"name": "mail_temp",
"version": "1.0.0",
"description": "",
"main": "app.js",
"type": "module",
"scripts": {
"build": "mjml src/template.mjml --output views/output.eta",
"start": "npm run build && node app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"eta": "^1.12.3",
"express": "^4.17.2",
"mjml": "^4.12.0"
}
}
Code language: JSON / JSON with Comments (json)
The snippet above does the following:
-
Line 5: changed the application entry point to
app.js
. We will create this file in the next section. - Line 6: this line is needed to enable the ES6 module and support import syntax in our files.
-
Line 8-9: modified this to include our application’s
build
andstart
script option.- The
build
script uses themjml
engine to transpile thetemplate.mjml
file inside thesrc
folder output it in theviews
folder asoutput.eta
file..eta
is the file extension for theeta
templating engine we installed earlier. When we start building our email template, we will create thetemplate.mjml
. - The
start
script runs thebuild
script above first and then run the application.
- The
With that done, we need to configure a Node.js server to enable us to render our email template on a browser. To do this, we need to create an app.js
file in our project directory and add the snippet below:
import express from 'express';
import * as Eta from 'eta';
const app = express();
app.engine('eta', Eta.renderFile);
app.set('view engine', 'eta');
app.set('views', './views');
const port = 3000;
app.get('/', (req, res) => {
res.render('output', { name: 'Jane Doe', link: 'https://hackmamba.io/' });
});
app.listen(port, () => {
console.log(`App listening on port: ${port}`);
});
Code language: JavaScript (javascript)
The snippet above does the following:
- Import the required dependencies.
- Creates an
express
application instance. - Configure the instance to use
eta
as the rendering engine and set theviews
folder as the template files’ directory. - Creates a port
3000
that the application will run on. - Creates a
/
route to render theoutput
file and passed in custom variables. - Binds and listens for connections on the specified
port
.
We can start creating a responsive email template with the project structuring and setup..
We will be using the following already hosted images on Cloudinary to build our email template:
Logo image
Banner image
Section images
- https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037458/david-edkins-6cC7WKiwcGs-unsplash.jpg
- https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037453/timothy-eberly-yuiJO6bvHi4-unsplash.jpg
Next, we need to navigate to the src
folder, and in this folder, create a template.mjml
file. We will break down the creation into two main sections.
Section One (Head Section)
<mjml>
<!-- head section -->
<mj-head>
<mj-font name="Open Sans" href="https://fonts.googleapis.com/css2?family=Open+Sans" />
<mj-attributes>
<mj-text font-family="Open Sans" font-size="16px" line-height="26px" color="#637381" />
<mj-button background-color="#5666F6" font-size="14px" color="#ffffff"
font-family="Open Sans" text-transform="capitalize" height="45px" width="200px" />
<mj-class name="head-text" font-size="48px" text-transform="capitalize" padding-bottom="30px" line-height="48px"/>
<mj-class name="social-bg" background-color="#A1A0A0"/>
</mj-attributes>
</mj-head>
</mjml>
Code language: HTML, XML (xml)
An MJML file starts and close with an mjml
tag. The mj-head
section contains all the styles needed for this file. The head section also includes the following:
-
mj-font
configures the font to use for the file. -
mj-attributes
allows us to add custom attributes to the file. -
mj-text
adds defined properties to all the text in the file. -
mj-button
adds defined properties to all the buttons in the file. -
mj-class
creates reusable classes
Section Two (Body Section) Next, we can include the body of our email as shown below.
<mjml>
<!-- head section -->
<mj-head>
<!-- head code goes here -->
</mj-head>
<!-- body section -->
<mj-body>
<!-- header section -->
<mj-section>
<mj-column>
<mj-image width="80px" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1642367682/fire.png" />
<mj-divider border-color="#F45E43"></mj-divider>
</mj-column>
</mj-section>
<!-- Content section -->
<mj-wrapper padding-top="0" padding-bottom="0" css-class="body-section">
<mj-section background-color="#ffffff" padding-left="15px" padding-right="15px">
<mj-column width="100%">
<!-- Intro sectiom -->
<mj-text mj-class="head-text">
welcome onboard!
</mj-text>
<mj-text font-weight="bold">
Hi <%= it.name %>,
</mj-text>
<!-- banner section -->
<mj-image src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635416910/jed-owen-NPBnWE1o07I-unsplash.jpg" width="450px" alt="banner" padding="15px" />
<!-- content -->
<mj-text >
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quia a assumenda nulla in quisquam optio quibusdam fugiat perspiciatis nobis, ad tempora culpa porro labore. Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.
</mj-text>
<mj-text >
<ul>
<li style="padding-bottom: 20px"><strong>Lorem ipsum dolor:</strong> Sit amet consectetur adipisicing elit.</li>
<li style="padding-bottom: 20px"><strong>Quia a assumenda nulla:</strong> Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.</li>
<li><strong>Tempora culpa porro labore:</strong> In quisquam optio quibusdam fugiat perspiciatis nobis.</li>
</ul>
</mj-text>
<mj-text padding-bottom="30px">
Lorem ipsum dolor <a class="text-link" href="#">sit amet consectetur</a> adipisicing elit. Earum eaque sunt nulla in, id eveniet quae unde ad ipsam ut, harum autem porro reiciendis minus libero illo. Vero, fugiat reprehenderit.
</mj-text>
<mj-button href="<%= it.link %>">
verify account
</mj-button>
</mj-column>
</mj-section>
<!-- Image section -->
<mj-section padding-left="15px" padding-right="15px" padding-top="0">
<mj-column width="50%" padding="20px">
<mj-image border="" align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037458/david-edkins-6cC7WKiwcGs-unsplash.jpg" alt="section image" />
</mj-column>
<mj-column width="50%" padding="20px">
<mj-image align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037453/timothy-eberly-yuiJO6bvHi4-unsplash.jpg" alt="section image" />
</mj-column>
</mj-section>
<!-- footer section -->
<mj-section>
<mj-column width="100%" padding="0">
<mj-social font-size="15px" icon-size="30px" mode="horizontal" padding="0" align="center">
<mj-social-element name="facebook" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="google" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="twitter" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="linkedin" href="#" mj-class="social-bg">
</mj-social-element>
</mj-social>
<mj-text color="#445566" font-size="11px" font-weight="bold" align="center">
View this email in your browser
</mj-text>
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
You are receiving this email advertisement because you registered with Hackmamba Inc (Dubai) and agreed to receive emails from us regarding new features, events and special offers.
</mj-text>
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
© Hackmamba Inc., All Rights Reserved.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding-top="0">
<mj-group>
<mj-column width="100%" padding-right="0">
<mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
<a class="footer-link" href="#">Privacy</a>        <a class="footer-link" href="#">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-group>
</mj-section>
</mj-body>
</mjml>
Code language: HTML, XML (xml)
The mj-body
tag contains snippets for the following:
- A header section with a logo and a divider. The logo includes an image using the Cloudinary asset specified above.
- A content section with an introductory section, banner section and nested content section. The content section also contains:
- An introductory section that uses the
mj-class
to style the title andeta
syntax to render thename
variable passed to the route. - A banner section that used the banner image asset to render an image.
- A nested content section that contains body texts, a list and a button that uses
eta
syntax to pass in thelink
variable as thesrc
.
- An introductory section that uses the
- An image section that contains two images using the Cloudinary assets.
- Footer section that contains
mj-social-element
for rendering social media icons with custom class, disclaimer section, and inline links.
Complete template.mjml
<mjml>
<!-- head section -->
<mj-head>
<mj-font name="Open Sans" href="https://fonts.googleapis.com/css2?family=Open+Sans" />
<mj-attributes>
<mj-text font-family="Open Sans" font-size="16px" line-height="26px" color="#637381" />
<mj-button background-color="#5666F6" font-size="14px" color="#ffffff"
font-family="Open Sans" text-transform="capitalize" height="45px" width="200px" />
<mj-class name="head-text" font-size="48px" text-transform="capitalize" padding-bottom="30px" line-height="48px"/>
<mj-class name="social-bg" background-color="#A1A0A0"/>
</mj-attributes>
</mj-head>
<!-- body section -->
<mj-body>
<!-- header section -->
<mj-section>
<mj-column>
<mj-image width="80px" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1642367682/fire.png" />
<mj-divider border-color="#F45E43"></mj-divider>
</mj-column>
</mj-section>
<!-- Content section -->
<mj-wrapper padding-top="0" padding-bottom="0" css-class="body-section">
<mj-section background-color="#ffffff" padding-left="15px" padding-right="15px">
<mj-column width="100%">
<!-- Intro sectiom -->
<mj-text mj-class="head-text">
welcome onboard!
</mj-text>
<mj-text font-weight="bold">
Hi <%= it.name %>,
</mj-text>
<!-- banner section -->
<mj-image src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635416910/jed-owen-NPBnWE1o07I-unsplash.jpg" width="450px" alt="banner" padding="15px" />
<!-- content -->
<mj-text >
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quia a assumenda nulla in quisquam optio quibusdam fugiat perspiciatis nobis, ad tempora culpa porro labore. Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.
</mj-text>
<mj-text >
<ul>
<li style="padding-bottom: 20px"><strong>Lorem ipsum dolor:</strong> Sit amet consectetur adipisicing elit.</li>
<li style="padding-bottom: 20px"><strong>Quia a assumenda nulla:</strong> Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.</li>
<li><strong>Tempora culpa porro labore:</strong> In quisquam optio quibusdam fugiat perspiciatis nobis.</li>
</ul>
</mj-text>
<mj-text padding-bottom="30px">
Lorem ipsum dolor <a class="text-link" href="#">sit amet consectetur</a> adipisicing elit. Earum eaque sunt nulla in, id eveniet quae unde ad ipsam ut, harum autem porro reiciendis minus libero illo. Vero, fugiat reprehenderit.
</mj-text>
<mj-button href="<%= it.link %>">
verify account
</mj-button>
</mj-column>
</mj-section>
<!-- Image section -->
<mj-section padding-left="15px" padding-right="15px" padding-top="0">
<mj-column width="50%" padding="20px">
<mj-image border="" align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037458/david-edkins-6cC7WKiwcGs-unsplash.jpg" alt="section image" />
</mj-column>
<mj-column width="50%" padding="20px">
<mj-image align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037453/timothy-eberly-yuiJO6bvHi4-unsplash.jpg" alt="section image" />
</mj-column>
</mj-section>
<!-- footer section -->
<mj-section>
<mj-column width="100%" padding="0">
<mj-social font-size="15px" icon-size="30px" mode="horizontal" padding="0" align="center">
<mj-social-element name="facebook" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="google" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="twitter" href="#" mj-class="social-bg">
</mj-social-element>
<mj-social-element name="linkedin" href="#" mj-class="social-bg">
</mj-social-element>
</mj-social>
<mj-text color="#445566" font-size="11px" font-weight="bold" align="center">
View this email in your browser
</mj-text>
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
You are receiving this email advertisement because you registered with Hackmamba Inc (Dubai) and agreed to receive emails from us regarding new features, events and special offers.
</mj-text>
<mj-text color="#445566" font-size="11px" align="center" line-height="16px">
© Hackmamba Inc., All Rights Reserved.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding-top="0">
<mj-group>
<mj-column width="100%" padding-right="0">
<mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
<a class="footer-link" href="#">Privacy</a>        <a class="footer-link" href="#">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-group>
</mj-section>
</mj-body>
</mjml>
Code language: HTML, XML (xml)
With that done, we can start a development server using the command below:
npm start
We can test our template on different device sizes and see how responsive it is.
This post discussed how to build a responsive email with images using MJML and Node.js. MJML automatically takes care of the responsiveness.
You may find these resources helpful: