Как с легкостью создавать адаптивные электронные письма в формате HTML для вашего приложения Phoenix

Если вы когда-либо пытались создавать адаптивные электронные письма, вы, возможно, слышали о MJML - языке разметки Mailjet - предположительно единственной платформе, которая упрощает создание адаптивных электронных писем.

MJML предоставляет набор повторно используемых и расширяемых компонентов, которые позволяют создавать HTML-дизайн электронной почты, который по умолчанию адаптируется и отлично смотрится на всех типах устройств и почтовых клиентов. MJML поставляется в виде пакета npm и предоставляет интерфейс командной строки или может запускаться непосредственно из кода JavaScript.

Я использовал шаблоны электронной почты MJML с Ruby on Rails и интересовался, есть ли что-то похожее на гем mjml-rails, которое я мог бы использовать в приложении Elixir Phoenix.

И действительно, существует шестнадцатеричный пакет, который предоставляет механизм шаблонов Phoenix для MJML. Однако движок поток рендеринга инвертирован. Поэтому я не мог создавать динамические электронные письма, потому что мои шаблоны EEx не анализировались до тех пор, пока не была выполнена транспиляция MJML. Это означает, что весь встроенный Эликсир определяется как недопустимые элементы в компонентах MJML, и создание шаблона вообще не выполняется.

После того, как я некоторое время безуспешно возился с Phoenix Template Engines, я нашел другой способ создания динамических шаблонов электронной почты MJML в моем приложении.

Вот схема, которая сработала у меня при использовании Bamboo для отправки электронных писем:

  • добавить mjmldependency к проекту (npm или шестнадцатеричный пакет NIF)
  • отображать шаблоны электронной почты (EEx) как обычно
  • использовать обработанный шаблон электронной почты в качестве входных данных для дополнительного шага транспиляции MJML
  • заменить содержимое тела HTML результата рендеринга EEx транспилированным шаблоном MJML

На первом этапе я попытался использовать пакет mjml npm, который оставил у меня несколько вопросов о производительности. На следующем этапе я написал пакет mjml Hex, который оборачивает MJML реализацию Rust как NIF (собственные реализованные функции). Давайте подробно рассмотрим приведенные выше шаги и реализуем рабочий пример - начиная с настройки npm.

Добавление поддержки MJML через пакет npm

Сначала мы добавляем пакет mjml npm в package.json нашего приложения Phoenix, запустив npm install mjml (или yarn add mjml соответственно) в каталоге assets/:

Затем мы должны убедиться, что можем запустить транспилирование MJML из Elixir. Для этого мы создали служебный файл JavaScript, который мы будем запускать с исполняемым файлом node позже. Мы передадим наш визуализированный шаблон EEx (все еще включающий компоненты MJML), и мы ожидаем, что транспонированный окончательный HTML-код электронного письма будет возвращен из него.

Поместим mjml.js в каталог bin/ проекта Phoenix:

Сценарий берет строку MJML и выводит скомпилированный HTML в стандартный вывод. Нам нужен модуль mjml и вызываем его с заданной строкой MJML. В корневом каталоге нашего приложения Phoenix мы уже можем запустить транспиляцию MJML с помощью:

$ node bin/mjml.js "<mj-html></mj-html>"

Запуск транспиляции MJML из Elixir

Чтобы запустить транспиляцию MJML из Elixir, мы создаем MyApp.Mjml модуль, который будет использовать только что созданный bin/mjml.js:

В функции transpile/1 мы запускаем node bin/mjml.js как системную команду и возвращаем результат. Функция render/1 модуля выполняет некоторую обработку ошибок на этапе транспиляции, так что мы передаем и вызываем любую ошибку из вызова mjml.

Добавление рендеринга MJML в электронные письма Bamboo

Используя Bamboo, у вас будет MyAppWeb.Email модуль, в котором определены ваши почтовые функции, например:

Предположим, у нас есть email.html.eex макет шаблона, например:

С помощью функции welcome_email/0, описанной выше, мы получим последнее электронное письмо, которое по-прежнему включает компоненты MJML:

Итак, в качестве последнего шага нам нужно MJML-транспилировать визуализированный шаблон электронной почты. Вызов render(:welcome) в welcome_email/0 возвращает карту, содержащую HTML-содержимое электронного письма под ключом :html_body (см. Исходный код Bamboo). Чтобы получить окончательное содержимое электронного письма, мы можем заменить значение :html_body на перенесенное содержимое MJML:

Благодаря этому мы теперь можем создавать динамические и отзывчивые электронные письма в формате HTML, используя компоненты MJML. 🎉

Несколько слов о производительности

Транспортировка макетов MJML сама по себе не очень быстрая. Тот факт, что пакетный подход npm запускает MJML через системную команду Node из Elixir, также не делает его быстрее. Визуализация шаблонов электронной почты с помощью MJML может занять на пару секунд больше, чем отображение простых шаблонов EEx.

Если вам нужно отправить много писем, этот подход может стать огромным узким местом. Вам также может потребоваться увеличить тайм-ауты, например, отправка пакетов писем с помощью Task.async_stream.

В целом подход npm может быть слишком медленным для ваших нужд. Тем не менее, он показал себя хорошо - мы отправляли электронные письма MJML с таким подходом, используя Bamboo и MailGunAdapter в производстве на Adoptoposs.org.

Но мы еще можем улучшить ситуацию.

Ускорение передачи MJML с помощью NIF

Как упоминалось выше, есть еще один способ вызвать MJML из Elixir: через Rust NIF. Я нашел ящик mrml, повторную реализацию MJML в Rust, который, согласно их тестам, значительно превосходит реализацию JavaScript как по запросам в секунду, так и по требуемым ресурсам (использование ЦП / ОЗУ).

Недавно узнав о Erlang NIF и их реализации в Elixir с Rustler, я собрал небольшой mjml Hex package, используя mrml.

Установив этот пакет, мы можем избавиться от нашего модуля MyApp.Mjml, пакета mjml npm и bin/mjml.jsfile, которые мы определили выше. Единственные шаги, чтобы заставить работать транспиляцию MJML, - это добавить пакет mjml в наш mix.exs файл…

… И напрямую использовать модуль Mjml в MyApp.Emailmodule. Если транспиляция прошла успешно, Mjml.to_html/2 возвращает кортеж с результатом HTML: {:ok, html_body}. Таким образом, наш модуль меняется на это:

Заключение

Если вы хотите использовать MJML для создания адаптивного дизайна электронной почты для своего приложения Elixir Phoenix, вы можете сделать это, используя либо пакет mjml npm с небольшим количеством установочного кода, либо новый пакет mjml Hex.

Последнее дает вам значительное ускорение по сравнению с переносом ваших шаблонов с помощью node. С другой стороны, для компиляции NIF необходимы библиотека rustc и груз. Итак, если это не вариант для вас, подход узла будет работать для любого приложения Phoenix, которое уже использует узел.

Описанные подходы работают при использовании Bamboo в качестве библиотеки электронной почты, я не проверял, работает ли она аналогично для альтернативных пакетов электронной почты, таких как Swoosh.

Ресурсы

  • MJML (язык разметки Mailjet)


  • mrml - повторная реализация MJML в Rust:


  • mjml - шестнадцатеричный пакет, который обертывает mrml через NIF (собственные реализованные функции):


  • Пост в блоге «Rust NIFs in Elixir» с использованием Rustler: