Делаем фронтенд-сборку для верстки HTML-писем на MJML

от автора

Меня зовут Дима. Я Frontend разработчик в компании fuse8. В одном из проектов мы столкнулись с необходимостью упростить процесс верстки и тестирования HTML-писем. В итоге решили вынести шаблоны в отдельный репозиторий и собирать их с помощью MJML.

В этой статье разберём два ключевых этапа: сначала создадим репозиторий для верстки писем, а затем настроим локальную тестовую отправку через SMTP.

Вот ссылка на репозиторий, где можно взять готовый шаблон, запустить его и проверить. А о том, из чего он состоит и как формируется, читайте ниже.

Зачем всё это?

На проекте мы активно используем HTML-письма. Изначально шаблоны располагались прямо в микросервисах бэкенда — разрозненно, с повторяющимися частями. Это создавало сложности при сопровождении и тестировании писем. Захотелось упростить процесс разработки, а главное — сделать верстку и проверку писем удобной.

А верстать письма, как известно, больно. Это как будто вы верстаете под Internet Explorer — если вы, конечно, помните, что это такое.

Первый шаг: выносим письма в отдельный репозиторий

Мы решили создать отдельный репозиторий, в котором будут храниться все шаблоны писем. Бэкенд сможет подтягивать их централизованно.

Следующий шаг — выбор инструментов для верстки и тестирования. Рассматривались:

  • MJML

  • Maizzle

  • Foundation HTML

Хотелось писать на более высокоуровневом языке, используя предустановленные компоненты. В Maizzle и Foundation HTML показалось, что придется писать больше кода на чистом HTML.

В итоге остановились на MJML — языке разметки для email-писем, который компилируется в полноценный HTML, адаптированный под специфику email-клиентов.

Что такое MJML и чем он удобен?

MJML – это язык, предназначенный для верстки HTML писем, который компилируется в обычный HTML. Это значит, что можно писать в абстрактном синтаксисе, который затем превратится в HTML-код с поддержкой разных почтовых клиентов. Это облегчает адаптацию под разные почтовые клиенты и даёт возможность использовать:

  • адаптивную верстку;

  • предустановленные компоненты;

  • переиспользование кода с помощью mj-include.

Здесь можно посмотреть, как код mjml превращается в html.

Базовая настройка проекта

Разберём базовую настройку проекта — без углубления в синтаксис MJML.

Установим зависимости:

npm install mjml live-server concurrently

Что делает каждая из них:

  • mjml — компиляция MJML → HTML

  • live-server — запуск dev-сервера с live reload (можно использовать любой сервер)

  • concurrently — параллельный запуск команд

Настройка package.json

Добавим в scripts:

"scripts": {    "start": "mjml --watch ./src/templates/**/*.mjml --output ./templates",    "server": "live-server --host=localhost --watch=templates --open=templates --ignorePattern=\".*.mjml\"",    "dev": "concurrently \"npm run start\" \"npm run server\"",    "build": "mjml ./src/templates/**/*.mjml --output ./templates"  }

Что делает каждая команда:

  • start — следит за файлами *.mjml в src/templates, компилирует в ./templates

  • server — поднимает сервер и отслеживает изменения в ./templates

  • dev — запускает start и server параллельно

  • build — однократная сборка шаблонов без вотчера

Создаём первый шаблон

Создадим файл example.mjml по пути /src/templates/example.mjml, в папке /src/templates будут все шаблоны. В файл добавим следующий код:

<mjml>    <mj-body background-color="#f5f5f5">      <mj-section padding="40px 0 20px">        <mj-column>          <mj-text align="center" font-size="28px" font-weight="bold">Письмо на mjml</mj-text>        </mj-column>      </mj-section>      <mj-section background-color="#ffffff">        <mj-column>          <mj-image src="https://placehold.jp/300x200.png" />        </mj-column>        <mj-column>          <mj-text font-size="18px" font-weight="bold">Привет, мир!</mj-text>          <mj-text>Это письмо отправлено с помощью MJML.</mj-text>        </mj-column>      </mj-section>      <mj-section>        <mj-column>          <mj-button href="#" background-color="#4CAF50">Подписаться</mj-button>        </mj-column>      </mj-section>    </mj-body>  </mjml>

Запускаем npm run dev — и в браузере откроется HTML, сгенерированный из MJML. При каждом изменении шаблона страница будет автоматически обновляться. Также полезно создать папку ./src/parts для переиспользуемых частей верстки.

В результате получим десктопную и мобильную версии.

Десктопная версия

Десктопная версия
Мобильная версия

Мобильная версия

В папке ./src/parts создаём переиспользуемые части писем:

./src/parts/global-settings.mjml

<mj-style inline="inline">    body { background-color: #f0f4f6; font-family: Arial, sans-serif; }    a { color: #1d5cdb; text-decoration: none; }  </mj-style>  <mj-attributes>    <mj-text      font-size="17px"      line-height="24px"      color="#000"      padding-top="5px"      padding-bottom="5px"    />  </mj-attributes>

./src/parts/header.mjml

<mj-section>    <mj-column>      <mj-image        src="https://placehold.jp/82x47.png"        alt="Логотип"        width="82px"        height="47px"      />    </mj-column>  </mj-section>

Подключим их в example.mjml и удалим лишние стили:

<mjml>    <mj-head>      <mj-include path="../parts/global-settings.mjml" />      <mj-title>Пример</mj-title>    </mj-head>    <mj-body>      <mj-include path="../parts/header.mjml" />      <mj-section padding="40px 0 20px">        <mj-column>          <mj-text align="center" font-size="28px" font-weight="bold">Письмо на mjml</mj-text>        </mj-column>      </mj-section>      <mj-section background-color="#ffffff">        <mj-column>          <mj-image src="https://placehold.jp/300x200.png" />        </mj-column>        <mj-column>          <mj-text font-size="18px" font-weight="bold">Привет, мир!</mj-text>          <mj-text>Это письмо отправлено с помощью MJML.</mj-text>        </mj-column>      </mj-section>      <mj-section>        <mj-column>          <mj-button href="#" background-color="#4CAF50">Подписаться</mj-button>        </mj-column>      </mj-section>    </mj-body>  </mjml>

Получаем результат:

В итоге:

  • Можем глобально задавать общие стили и настройки для компонентов с помощью global-settings.

  • Можем выносить по аналогии части кода, как это сделано в header.

  • Разрабатываем и видим изменения в браузере в реальном времени за счет настроенного dev сервера.

Использование шаблонов на бэкенде (Go)

У нас на проекте бэкенд написан на Go. Собранные шаблоны писем попадают в папку ./templates. Оттуда бэкенд может их подтягивать и отправлять письма. В нашем случае подключение шаблонов выглядит так:

  1. В корне проекта — go.mod:

module gitlab.site.ru/front-html-email-templates  go 1.22.0
  1. В ./templates/templates.go:

package templates  import _ "embed"  //go:embed example.html  var Example string

При добавлении нового шаблона нужно добавить аналогичную переменную в templates.go.

Также в письмах есть переменные, которые подставляет бэкенд с помощью своего шаблонизатора. В нашем случае для переменных следующий синтаксис {{.variableName}}

<mjml>    <mj-body>      <mj-section padding="40px 0 20px">        <mj-column>          <mj-text align="center" font-size="28px" font-weight="bold">{{.title}}</mj-text>        </mj-column>      </mj-section>    </mj-body>  </mjml>

Чтобы понимать, какие переменные нужны в шаблоне, мы создали папку ./docs/templates, где хранятся md файлы с одноименным названием шаблона и описанием того, какие переменные используются, например:

## Candidate  Шаблон письма 'Отклик без вакансии'  ### Переменные  - ``{{.date}}`` Дата отклика  - ``{{.title}}`` Название вакансии  - ``{{.region}}`` Регион  - ``{{.name}}`` ФИО  - ``{{.phone}}`` Телефон  - ``{{.email}}`` Email  - ``{{.comment}}`` Комментарий  - ``{{.resume_link}}`` Ссылка на резюме  - ``{{.year}}`` Актуальный год отправки письма

Сами переменные в файлы может занести бэкендер, а фронтендеру остается расставить их в верстке по правильным местам.

Локальное тестирование по smtp

Напишем простой js скрипт, который будет отправлять наши шаблоны на реальную почту.

Для начала установим следующие пакеты:

npm install dotenv nodemailer
  • dotenv — пакет для загрузки .env файлов в js;

  • nodemailer— пакет для отправки писем с поддержкой SMTP.

После этого создадим файл ./send-test-email.js и добавим код:

import nodemailer from 'nodemailer'  import dotenv from 'dotenv'  import fs from 'fs/promises'  dotenv.config()  async function sendTestEmail() {  const transporter = nodemailer.createTransport({  host: process.env.SMTP_HOST,  port: process.env.SMTP_PORT,  secure: true, // true для 465, false для других портов  auth: {  user: process.env.SEND_FROM_EMAIL,  pass: process.env.SEND_FROM_EMAIL_PASSWORD,  },  })  const htmlEmailString = await fs.readFile(  ./templates/${process.env.TEMPLATE_NAME}.html,  'utf-8'  )  const mailOptions = {  from: "Test Sender" <${process.env.SEND_FROM_EMAIL}>,  to: process.env.SEND_TO_EMAIL,  subject: 'Test HTML Email',  html: htmlEmailString,  }  const info = await transporter.sendMail(mailOptions)  console.log('Message sent: %s', info.messageId)  }  sendTestEmail().catch(console.error)

Это простая реализация отправки письма по SMTP. Также нам нужно создать .env файл с нужными переменными:

TEMPLATE_NAME=email-confirmation  SEND_TO_EMAIL=test@fuse8.online  SMTP_HOST=smtp.mail.ru  SMTP_PORT=465  SEND_FROM_EMAIL=test@mail.ru  SEND_FROM_EMAIL_PASSWORD=BcsftTdfdsf
  • TEMPLATE_NAME — название шаблона, который будет отправлен;

  • SEND_TO_EMAIL— куда будет отправлено письмо;

  • SMTP_HOST— smtp хост;

  • SMTP_PORT— порт;

  • SEND_FROM_EMAIL— откуда будет уходить письмо.

  • SEND_FROM_EMAIL_PASSWORD— пароль от отправляемой почты.

В основном меняются переменные TEMPLATE_NAME и SEND_TO_EMAIL. Для тестирования разных шаблонов и разных почтовых клиентов.

Остальные переменные нужно настроить один раз. Например, как подключиться по SMTP в mail почте https://help.mail.ru/mail/login/mailer.

После всей настройки для отправки письма нужно вызвать скрипт:

node ./send-test-email.js

Для удобства можно добавить скрипт в package.json, также можно сделать поддержку массовой отправки шаблона на разные почтовые клиенты.

Заключение

Текущую концепцию можно применить и с другими инструментами. В результате у нас есть:

  • централизованный репозиторий для html писем;

  • верстка писем с лучшими практиками и переиспользуемыми частями;

  • локальное тестирование по smtp.

Нужно иметь в виду, что верстка все равно может разваливаться в старых outlook клиентах и с такими решениями как MJML, где даются эталонные шаблоны писем. Даже использование лучших практик не избавляет от этой проблемы. Чтобы избежать «разваливания», для старых решений можно использовать максимально тривиальную верстку: простые тексты и заголовки, отсутствие стилизаций и визуальных элементов. Если вы знаете, как иначе можно выйти из ситуации, расскажите в комментариях – будет полезно 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/937538/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *