Введение
Почти в каждом проекте приходится думать об отправке писем по электронной почте. Основными требованиями при этом являются, помимо надежности доставки, привлекательность и удобство электронных писем.
Основные нюансы при формировании таких писем:
- Все стили должны встраиваться (inline) в виде атрибута
style
для конкретного HTML-элемента. - Все изображения должны встраиваться, либо как отдельные вложения в в письме, либо в виде base64-кодированных данных (второе банально удобнее).
- Письмо должно поддерживать DKIM (настройка мэйлера), а домен отправителя — содержать SPF-запись.
Ранее я использовал для формирования HTML-писем проект Premailer, созданный на Ruby. Пришлось даже заняться поддержкой проекта (сейчас времени на это нет, мэйнтейнеры приветствуются).
Сейчас же хотелось избежать внедрения Ruby, в то время, как Node проник везде.
Juice
К счастью, современная экосистема Node предоставляет богатые возможности по формированию электронных писем. Мы выбрали цепочку по формированию электронной почты в виде pug-шаблонов, преобразованию оных с помощью juice и подстановки конкретных данных на бэкэнде (у нас это Perl).
Предполагается, что Вы используете node 6+
, babel
(es2015, es2016, es2017, stage-0 presets).
Установка
npm install gulp-cli -g
npm install gulp --save-dev
npm install del --save-dev
npm install gulp-rename --save-dev
npm install gulp-pug --save-dev
npm install premailer-gulp-juice --save-dev
npm install gulp-postcss --save-dev
npm install autoprefixer --save-dev
npm install gulp-less --save-dev
gulpfile.babel.js:
'use strict';
import gulp from 'gulp';
import mail from './builder/tasks/mail';
gulp.task('mail', mail);
builder/tasks/mail.js:
'use strict';
import gulp from 'gulp';
import stylesheets from './mail/stylesheets';
import templates from './mail/templates';
import clean from './mail/clean';
const mail = gulp.series(clean, stylesheets, templates);
export default mail;
builder/tasks/mail/stylesheets.js
'use strict';
import gulp from 'gulp';
import config from 'config';
import rename from 'gulp-rename';
import postcss from 'gulp-postcss';
import autoprefixer from 'autoprefixer';
import less from 'gulp-less';
const stylesheetsPath = config.get('srcPath') + '/mail/stylesheets';
const stylesheetsGlob = stylesheetsPath + '/**/*.less';
const mailStylesheets = () => {
return gulp.src(stylesheetsGlob)
.pipe(less())
.pipe(postcss([
autoprefixer({browsers: ['last 2 versions']}),
]))
.pipe(gulp.dest(stylesheetsPath));
};
export default mailStylesheets;
builder/tasks/mail/templates.js:
'use strict';
import gulp from 'gulp';
import config from 'config';
import pug from 'gulp-pug';
import rename from 'gulp-rename';
import juice from 'premailer-gulp-juice';
const templatesPath = config.get('srcPath') + '/mail';
const mailPath = config.get('mailPath');
const templatesGlob = templatesPath + '/**/*.pug';
const mailTemplates = () => {
return gulp.src(templatesGlob)
.pipe(rename(path => {
path.extname = '.html';
}))
.pipe(pug({
client: false
}))
.pipe(juice({
webResources: {
relativeTo: templatesPath,
images: 100,
strict: true
}
}))
.pipe(gulp.dest(mailPath));
};
export default mailTemplates;
builder/tasks/mail/clean.js:
'use strict';
import del from 'del';
import gutil from 'gulp-util';
const clean = done => {
return del([
'mail/*.html',
'src/mail/stylesheets/*.css'
]).then(() => {
gutil.log(gutil.colors.green('Delete src/mail/stylesheets/*.css and mail/*.html'));
done();
});
};
export default clean;
Типичный шаблон выглядит так (generic.pug):
include base.pug
+base
tr(height='74')
td.b-mail__table-row--heading(align='left', valign='top') Привет,
tr
td(align='left', valign='top')
| <%== $html %>
Где base.pug:
mixin base(icon, alreadyEncoded)
doctype html
head
meta(charset="utf8")
link(rel="stylesheet", href="/stylesheets/mail.css")
body
table(width='100%', border='0', cellspacing='0', cellpadding='0')
tbody
tr
td.b-mail(align='center', valign='top', bgcolor='#ffffff')
br
br
table(width='750', border='0', cellspacing='0', cellpadding='0')
tbody.b-mail__table
tr.b-mail__table-row(height='89')
tr.b-mail__table-row
td(align='left', valign='top', width='70')
img(src='/images/logo.jpg')
td(align='left', valign='top')
table(width='480', border='0', cellspacing='0', cellpadding='0')
tbody
if block
block
td(align='right', valign='top')
if alreadyEncoded
img.fixed(src!=icon, data-inline-ignore)
else if icon
img.fixed(src!=icon)
br
br
tr
td(align='center', valign='top')
Собственно, болванка готова, шаблоны компилируются. Формирование модуля config тривиально и необязательно.
Готовая болванка репозитория здесь: https://github.com/premailer/gulp-juice-demo
gulp mail
ViewAction
Многие почтовые клиенты, такие, как GMail/Inbox, поддерживают специальные действия в режиме просмотра сообщений. Внедрить их проще простого, добавив в содержимое сообщения следующие тэги:
div(itemscope, itemtype="http://schema.org/EmailMessage")
div(itemprop="action", itemscope, itemtype="http://schema.org/ViewAction")
link(itemprop="url", href="https://github.com/imlucas/gulp-juice/pull/9")
meta(itemprop="name", content="View Pull Request")
meta(itemprop="description", content="View this Pull Request on GitHub")
Ну и немного интеграции с (выберите свой язык, тут нужен был Perl)
sub prepare_mail_params {
my %params = %{ shift() };
my @keys = keys %params;
# Camelize params
for my $param ( @keys ) {
my $new_param = $param;
$new_param =~ s/^(w)/U$1E/;
next if $new_param eq $param;
$params{$new_param} = delete $params{$param};
}
%params = (
Type => 'multipart/mixed; charset=UTF-8',
From => 'support@ourcompany.co.uk',
Subject => '',
%params,
);
# Mime params
for my $param ( keys %params ) {
$params{$param} = encode( 'MIME-Header', $params{$param} );
}
return %params;
}
sub _template_processor {
state $instance = Mojo::Template->new(
vars => 1,
auto_escape => 1,
);
return $instance;
}
sub send_mail {
my %params = %{ shift() };
my $html = (delete $params{message}) // '';
my $template = delete $params{template};
my $stash = (delete $params{stash}) // {};
unless ( $template ) {
$template = 'generic';
$stash->{html} = $html;
}
$html = _template_processor()->render_file(
Config->directories->{mail}. "/$template.html",
$stash,
);
$html = encode_utf8( $html );
my $msg = MIME::Lite->new(
%{ prepare_mail_params( %params ) }
);
$msg->attach(
Type => 'HTML',
Data => $html,
);
if ( $mail_settings->{method} eq 'sendmail' ) {
return $msg->send();
}
if ( $mail_settings->{method} eq 'smtp' ) {
return $msg->send('smtp', $mail_settings->{host}, Timeout => $mail_settings->{timeout});
}
croak "Unknown Config mail.method: ". $mail_settings->{method};
}
Полезные ссылки
- Исходный код проекта по данной статье.
- Подборка: 40+ полезных инструментов, ресурсов и исследований о работе с email
P.S.: Спасибо pstn за доработки шаблонов писем.
Автор: akzhan