Responsive emails with Images in MJML in Nodejs

Demola Malomo

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.

Sandbox

We completed this project in a CodeSandbox, and you can fork it to run the code.

Github link here.

Prerequisites

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.

Getting Started

We need to create a Node.js starter project by navigating to the desired directory and running the command below in our terminal.

1mkdir 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:

1npm 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.

Project structuring and setup

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:

1{
2 "name": "mail_temp",
3 "version": "1.0.0",
4 "description": "",
5 "main": "app.js",
6 "type": "module",
7 "scripts": {
8 "build": "mjml src/template.mjml --output views/output.eta",
9 "start": "npm run build && node app.js"
10 },
11 "keywords": [],
12 "author": "",
13 "license": "ISC",
14 "dependencies": {
15 "eta": "^1.12.3",
16 "express": "^4.17.2",
17 "mjml": "^4.12.0"
18 }
19 }

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 and start script option.
    • The build script uses the mjml engine to transpile the template.mjml file inside the src folder output it in the views folder as output.eta file. .eta is the file extension for the eta templating engine we installed earlier. When we start building our email template, we will create the template.mjml.
    • The start script runs the build script above first and then run the application.

Creating the application entry point

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:

1import express from 'express';
2import * as Eta from 'eta';
3
4const app = express();
5app.engine('eta', Eta.renderFile);
6app.set('view engine', 'eta');
7app.set('views', './views');
8const port = 3000;
9
10app.get('/', (req, res) => {
11 res.render('output', { name: 'Jane Doe', link: 'https://hackmamba.io/' });
12});
13
14app.listen(port, () => {
15 console.log(`App listening on port: ${port}`);
16});

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 the views folder as the template files' directory.
  • Creates a port 3000 that the application will run on.
  • Creates a / route to render the output file and passed in custom variables.
  • Binds and listens for connections on the specified port.

Creating a responsive email template

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

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)

1<mjml>
2 <!-- head section -->
3 <mj-head>
4 <mj-font name="Open Sans" href="https://fonts.googleapis.com/css2?family=Open+Sans" />
5 <mj-attributes>
6 <mj-text font-family="Open Sans" font-size="16px" line-height="26px" color="#637381" />
7 <mj-button background-color="#5666F6" font-size="14px" color="#ffffff"
8 font-family="Open Sans" text-transform="capitalize" height="45px" width="200px" />
9 <mj-class name="head-text" font-size="48px" text-transform="capitalize" padding-bottom="30px" line-height="48px"/>
10 <mj-class name="social-bg" background-color="#A1A0A0"/>
11 </mj-attributes>
12 </mj-head>
13</mjml>

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.

1<mjml>
2 <!-- head section -->
3 <mj-head>
4 <!-- head code goes here -->
5 </mj-head>
6
7 <!-- body section -->
8 <mj-body>
9 <!-- header section -->
10 <mj-section>
11 <mj-column>
12 <mj-image width="80px" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1642367682/fire.png" />
13 <mj-divider border-color="#F45E43"></mj-divider>
14 </mj-column>
15 </mj-section>
16 <!-- Content section -->
17 <mj-wrapper padding-top="0" padding-bottom="0" css-class="body-section">
18 <mj-section background-color="#ffffff" padding-left="15px" padding-right="15px">
19 <mj-column width="100%">
20 <!-- Intro sectiom -->
21 <mj-text mj-class="head-text">
22 welcome onboard!
23 </mj-text>
24 <mj-text font-weight="bold">
25 Hi <%= it.name %>,
26 </mj-text>
27 <!-- banner section -->
28 <mj-image src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635416910/jed-owen-NPBnWE1o07I-unsplash.jpg" width="450px" alt="banner" padding="15px" />
29 <!-- content -->
30 <mj-text >
31 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.
32 </mj-text>
33 <mj-text >
34 <ul>
35 <li style="padding-bottom: 20px"><strong>Lorem ipsum dolor:</strong> Sit amet consectetur adipisicing elit.</li>
36 <li style="padding-bottom: 20px"><strong>Quia a assumenda nulla:</strong> Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.</li>
37 <li><strong>Tempora culpa porro labore:</strong> In quisquam optio quibusdam fugiat perspiciatis nobis.</li>
38 </ul>
39 </mj-text>
40 <mj-text padding-bottom="30px">
41 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.
42 </mj-text>
43 <mj-button href="<%= it.link %>">
44 verify account
45 </mj-button>
46 </mj-column>
47 </mj-section>
48 <!-- Image section -->
49 <mj-section padding-left="15px" padding-right="15px" padding-top="0">
50 <mj-column width="50%" padding="20px">
51 <mj-image border="" align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037458/david-edkins-6cC7WKiwcGs-unsplash.jpg" alt="section image" />
52 </mj-column>
53 <mj-column width="50%" padding="20px">
54 <mj-image align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037453/timothy-eberly-yuiJO6bvHi4-unsplash.jpg" alt="section image" />
55 </mj-column>
56 </mj-section>
57 <!-- footer section -->
58 <mj-section>
59 <mj-column width="100%" padding="0">
60 <mj-social font-size="15px" icon-size="30px" mode="horizontal" padding="0" align="center">
61 <mj-social-element name="facebook" href="#" mj-class="social-bg">
62 </mj-social-element>
63 <mj-social-element name="google" href="#" mj-class="social-bg">
64 </mj-social-element>
65 <mj-social-element name="twitter" href="#" mj-class="social-bg">
66 </mj-social-element>
67 <mj-social-element name="linkedin" href="#" mj-class="social-bg">
68 </mj-social-element>
69 </mj-social>
70 <mj-text color="#445566" font-size="11px" font-weight="bold" align="center">
71 View this email in your browser
72 </mj-text>
73 <mj-text color="#445566" font-size="11px" align="center" line-height="16px">
74 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.
75 </mj-text>
76 <mj-text color="#445566" font-size="11px" align="center" line-height="16px">
77 &copy; Hackmamba Inc., All Rights Reserved.
78 </mj-text>
79 </mj-column>
80 </mj-section>
81 <mj-section padding-top="0">
82 <mj-group>
83 <mj-column width="100%" padding-right="0">
84 <mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
85 <a class="footer-link" href="#">Privacy</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<a class="footer-link" href="#">Unsubscribe</a>
86 </mj-text>
87 </mj-column>
88 </mj-group>
89 </mj-section>
90
91 </mj-body>
92</mjml>

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 and eta syntax to render the name 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 the link variable as the src.
  • 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

1<mjml>
2 <!-- head section -->
3 <mj-head>
4 <mj-font name="Open Sans" href="https://fonts.googleapis.com/css2?family=Open+Sans" />
5 <mj-attributes>
6 <mj-text font-family="Open Sans" font-size="16px" line-height="26px" color="#637381" />
7 <mj-button background-color="#5666F6" font-size="14px" color="#ffffff"
8 font-family="Open Sans" text-transform="capitalize" height="45px" width="200px" />
9 <mj-class name="head-text" font-size="48px" text-transform="capitalize" padding-bottom="30px" line-height="48px"/>
10 <mj-class name="social-bg" background-color="#A1A0A0"/>
11 </mj-attributes>
12 </mj-head>
13
14 <!-- body section -->
15 <mj-body>
16 <!-- header section -->
17 <mj-section>
18 <mj-column>
19 <mj-image width="80px" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1642367682/fire.png" />
20 <mj-divider border-color="#F45E43"></mj-divider>
21 </mj-column>
22 </mj-section>
23 <!-- Content section -->
24 <mj-wrapper padding-top="0" padding-bottom="0" css-class="body-section">
25 <mj-section background-color="#ffffff" padding-left="15px" padding-right="15px">
26 <mj-column width="100%">
27 <!-- Intro sectiom -->
28 <mj-text mj-class="head-text">
29 welcome onboard!
30 </mj-text>
31 <mj-text font-weight="bold">
32 Hi <%= it.name %>,
33 </mj-text>
34 <!-- banner section -->
35 <mj-image src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635416910/jed-owen-NPBnWE1o07I-unsplash.jpg" width="450px" alt="banner" padding="15px" />
36 <!-- content -->
37 <mj-text >
38 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.
39 </mj-text>
40 <mj-text >
41 <ul>
42 <li style="padding-bottom: 20px"><strong>Lorem ipsum dolor:</strong> Sit amet consectetur adipisicing elit.</li>
43 <li style="padding-bottom: 20px"><strong>Quia a assumenda nulla:</strong> Repudiandae accusamus obcaecati voluptatibus accusantium perspiciatis.</li>
44 <li><strong>Tempora culpa porro labore:</strong> In quisquam optio quibusdam fugiat perspiciatis nobis.</li>
45 </ul>
46 </mj-text>
47 <mj-text padding-bottom="30px">
48 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.
49 </mj-text>
50 <mj-button href="<%= it.link %>">
51 verify account
52 </mj-button>
53 </mj-column>
54 </mj-section>
55 <!-- Image section -->
56 <mj-section padding-left="15px" padding-right="15px" padding-top="0">
57 <mj-column width="50%" padding="20px">
58 <mj-image border="" align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037458/david-edkins-6cC7WKiwcGs-unsplash.jpg" alt="section image" />
59 </mj-column>
60 <mj-column width="50%" padding="20px">
61 <mj-image align="center" src="https://res.cloudinary.com/dtgbzmpca/image/upload/v1635037453/timothy-eberly-yuiJO6bvHi4-unsplash.jpg" alt="section image" />
62 </mj-column>
63 </mj-section>
64 <!-- footer section -->
65 <mj-section>
66 <mj-column width="100%" padding="0">
67 <mj-social font-size="15px" icon-size="30px" mode="horizontal" padding="0" align="center">
68 <mj-social-element name="facebook" href="#" mj-class="social-bg">
69 </mj-social-element>
70 <mj-social-element name="google" href="#" mj-class="social-bg">
71 </mj-social-element>
72 <mj-social-element name="twitter" href="#" mj-class="social-bg">
73 </mj-social-element>
74 <mj-social-element name="linkedin" href="#" mj-class="social-bg">
75 </mj-social-element>
76 </mj-social>
77 <mj-text color="#445566" font-size="11px" font-weight="bold" align="center">
78 View this email in your browser
79 </mj-text>
80 <mj-text color="#445566" font-size="11px" align="center" line-height="16px">
81 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.
82 </mj-text>
83 <mj-text color="#445566" font-size="11px" align="center" line-height="16px">
84 &copy; Hackmamba Inc., All Rights Reserved.
85 </mj-text>
86 </mj-column>
87 </mj-section>
88 <mj-section padding-top="0">
89 <mj-group>
90 <mj-column width="100%" padding-right="0">
91 <mj-text color="#445566" font-size="11px" align="center" line-height="16px" font-weight="bold">
92 <a class="footer-link" href="#">Privacy</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<a class="footer-link" href="#">Unsubscribe</a>
93 </mj-text>
94 </mj-column>
95 </mj-group>
96 </mj-section>
97
98 </mj-body>
99</mjml>

With that done, we can start a development server using the command below:

1npm start

We can test our template on different device sizes and see how responsive it is.

Conclusion

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:

Demola Malomo

Software Engineer & Technical Writer

Demola is a software developer, technical writer, and product designer. He has a passion for designing and building scalable applications.