Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)



Этот учебник является частью серии статей Пишем свой стартап на PHP на Envato Tuts +. В этой серии я направляю вас через запуск приложения от концепции до готового продукта, используя приложение Планировщик встреч в качестве реального приложения. На каждом шаге я делаю релиз кода планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете узнать что-то новое. Я также рассмотрю связанные с запуском бизнес-вопросы по мере их возникновения.
В этом учебном пособии я буду очищять гибкие HTML шаблоны электронных писем, которые использует Планировщик собраний для рассылки приглашений, уведомлений, напоминаний и связанных с учетной записью сообщений.
На начальном этапе разработки Планировщика встреч я сосредоточился прежде всего на функциональности и еще не вложил значительных средств в дизайн и даже не нанял дизайнера. Сегодняшняя цель - очистить внешний вид существующих шаблонов HTML, чтобы основные электронные письма были более читабельными и удобными для людей.
Вероятно, половина первого опыта людей с Планировщиком собраний будет через электронную почту с запросом на собрание.
Если вы еще не опробовали Планировщик собраний, то обязательно запланируйте свою первую встречу. Я участвую в комментариях ниже, так что скажите мне, что вы думаете! Вы также можете связаться со мной в Twitter @reifman. Меня особенно интересует, если вы хотите предложить новые функции или темы для будущих учебников.
Напомним, что весь код Планировщика собраний написан в Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2.
Сообщения и шаблоны
Ранние шаблоны
Первоначально я использовал базовые фреймворки, которые я нашел в Интернете, чтобы создавать первые письма для Планировщика собраний. Они хорошо работали на ранней стадии разработки.
Вот пример наших существующих HTML-писем; они функциональны, но выглядят не очень привлекательно. И, в целом, я решил, что людям не нужны эти многочисленные варианты и ссылки в их приглашениях. Пришло время уменьшить множество вариантов для более простого опыта.



Даже с шаблонами, которые я использовал, таблицы электронной почты часто казались поврежденными без объяснения причин:



Я собирался перейти на более профессиональные шаблоны электронной почты, но я явно запоздал с этим.
Разнообразие сообщений
Ниже приведен список сообщений, которые регулярно отправляет Планировщик собраний:
- запросы на собрания (т. е. приглашения)
- обновление cобраний для изменений (также известны как уведомления)
- подтверждение встречи с прикреплением файла календаря
- напоминание о встрече
- запросы на контактную информацию для предстоящей встречи
- запросы на сброс пароля
Для альфа-теста я надеюсь, что смогу достичь разумной эстетической основы, применяя open-source шаблоны, которые можно найти в Интернете. Затем мы наймем дизайнера, чтобы оживить шаблоны, тему и бренд.
Я начал поиск хороших шаблонов для электронных писем.
Open-Source ресурсы с шаблонами
Существует ряд полезных руководств, таких как:
- 30 бесплатных писем и рассылок новостей (SpeckyBoy)
- 12 Полностью бесплатных шаблонов для электронных писем (Iterable)
И ряд провайдеров предлагают собственные шаблоны с открытым исходным кодом:
- Шаблоны электронной почты Mailgun Transactional
- Шаблоны электронной почты с открытым исходным кодом Sendwithus
- Шаблоны сообщества Litmus и бесплатные шаблоны
- Mailchimp/email-чертежи: макеты HTML от MailChimp
Первоначально меня привлекали шаблоны Mailgun, так как я чувствовал, что они были тщательно протестированы, и я мог бы опираться на них, но в конечном итоге я решил воспользоваться шаблонами электронных писем от Sendwithus Oxygen. Sendwithus - это синергетическая маркетинговая платформа для Mailgun (или, возможно, других поставщиков электронной почты), но у меня нет опыта работы с их общим сервисом.
Oxygen предложил полное семейство шаблонов для различных полезных сценариев. Они казалось простыми, организованными и легко расширяемыми:



Это очень приятно с их стороны предложить свои open-source шаблоны электронных писем, не требуя, чтобы вы были платным пользователем. Вперед open source!
Интеграция новых шаблонов
MVC фреймворк Yii отделяет макеты от содержимого тела. Поэтому мне нужно было разбить шаблоны и контролировать различия между вариантами внутри группы.
Вы можете загрузить семейство шаблонов Oxygen из Sendwithus GitHub, но они четко не разделяли стандартные элементы стиля, общие для каждого шаблона, поэтому вы должны сделать это сами.
Потребовалось некоторое время, чтобы выбрать, какие шаблоны мне нужны, какие элементы мне нравились в каждом, и какой CSS следует интегрировать в макет.
Разделение макета
В конечном счете, вот новый макет HTML (я вырезал стили здесь для удобочитаемости):
1 |
<?php
|
2 |
use yii\helpers\Html; |
3 |
/* @var $this \yii\web\View view component instance */
|
4 |
/* @var $message \yii\mail\MessageInterface the message being composed */
|
5 |
/* @var $content string main view render result */
|
6 |
?>
|
7 |
<?php $this->beginPage(); ?> |
8 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
9 |
<html xmlns="http://www.w3.org/1999/xhtml"> |
10 |
<head>
|
11 |
<meta http-equiv="Content-Type" content="text/html; charset=<?php echo Yii::$app->charset; ?>" /> |
12 |
<meta name="viewport" content="width=device-width, initial-scale=1" /> |
13 |
<title><?php echo Html::encode($this->title); ?></title> |
14 |
<?php $this->head(); ?> |
15 |
<style type="text/css"> |
16 |
...
|
17 |
</style>
|
18 |
<link rel="stylesheet" media="screen" type="text/css" href="http://fonts.googleapis.com/css?family=Oxygen:400,700"> |
19 |
... |
20 |
</head>
|
21 |
<body>
|
22 |
<?php $this->beginBody(); ?> |
23 |
<body bgcolor="#f7f7f7"> |
24 |
<table align="center" cellpadding="0" cellspacing="0" class="container-for-gmail-android" width="100%"> |
25 |
<tr>
|
26 |
<td align="left" valign="top" width="100%" style="background:repeat-x url(https://meetingplanner.io/img/bg_top_02.jpg) #ffffff;"> |
27 |
<center>
|
28 |
<?= Html::img('https://meetingplanner.io/img/transparent.png', ['class'=>'force-width-gmail']);?> |
29 |
<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" background="https://meetingplanner.io/img/bg_top_02.jpg" style="background-color:transparent"> |
30 |
<tr>
|
31 |
<td width="100%" height="80" valign="top" style="text-align: center; vertical-align:middle;"> |
32 |
<!--[if gte mso 9]>
|
33 |
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="mso-width-percent:1000;height:80px; v-text-anchor:middle;">
|
34 |
<v:fill type="tile" src="http://s3.amazonaws.com/swu-filepicker/4E687TRe69Ld95IDWyEg_bg_top_02.jpg" color="#ffffff" />
|
35 |
<v:textbox inset="0,0,0,0">
|
36 |
<![endif]-->
|
37 |
<center>
|
38 |
<table cellpadding="0" cellspacing="0" width="600" class="w320"> |
39 |
<tr>
|
40 |
<td class="pull-left mobile-header-padding-left" style="vertical-align: middle;"> |
41 |
<a href="https://meetingplanner.io"><?= Html::img('https://meetingplanner.io/img/email-logo.gif', ['alt'=>'Meeting Planner logo','height'=>'47','width'=>'137']);?></a> |
42 |
</td>
|
43 |
<td class="pull-right mobile-header-padding-right" style="color: #4d4d4d;"> |
44 |
<a href="https://twitter.com/intent/user?screen_name=meetingio"><?= Html::img('https://meetingplanner.io/img/social_twitter.gif', ['alt'=>'@meetingio on twitter','height'=>'47','width'=>'38']);?></a> |
45 |
<!-- <a href=""><img width="38" height="47" src="http://s3.amazonaws.com/swu-filepicker/LMPMj7JSRoCWypAvzaN3_social_09.gif" alt="facebook" /></a>-->
|
46 |
<!-- <a href=""><img width="40" height="47" src="http://s3.amazonaws.com/swu-filepicker/hR33ye5FQXuDDarXCGIW_social_10.gif" alt="rss" /></a>-->
|
47 |
</td>
|
48 |
</tr>
|
49 |
</table>
|
50 |
</center>
|
51 |
<!--[if gte mso 9]>
|
52 |
</v:textbox>
|
53 |
</v:rect>
|
54 |
<![endif]-->
|
55 |
</td>
|
56 |
</tr>
|
57 |
</table>
|
58 |
</center>
|
59 |
</td>
|
60 |
</tr>
|
61 |
<?php echo $content; ?> |
62 |
</table>
|
63 |
<?php $this->endBody(); ?> |
64 |
</body>
|
65 |
</html>
|
66 |
<?php $this->endPage(); ?> |
Замена общих элементов
Внутри шаблонов мне пришлось заменить несколько элементов:
- логотип
- Поддержка изображений
- Ссылки
Я создал файл логотипа, который на данный момент для нас подойдет, и я статически размещал его и вспомогательные изображения, например, для Twitter, на сервере Планировщика собраний.



Я также заменил ссылки по умолчанию в письме с кодом для ссылок на наш сайт.
Создание нижнего колонтитула
Чтобы упростить повторное использование в приложении, я разделил код нижнего колонтитула:
1 |
<?php
|
2 |
use yii\helpers\Html; |
3 |
use yii\helpers\Url; |
4 |
use common\components\MiscHelpers; |
5 |
?>
|
6 |
<tr>
|
7 |
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7; height: 100px;"> |
8 |
<center>
|
9 |
<table cellspacing="0" cellpadding="0" width="600" class="w320"> |
10 |
<tr>
|
11 |
<td style="padding: 25px 0 15px"> |
12 |
<strong><?php echo Html::a(Yii::t('frontend','Meeting Planner'), $links['home']); ?></strong><br /> |
13 |
Seattle, Washington<br /> |
14 |
</td>
|
15 |
</tr>
|
16 |
<tr><td style="font-size:75%;"><em> |
17 |
<?php echo HTML::a(Yii::t('frontend','Email settings'),$links['footer_email']); ?> |
18 |
| <?php echo HTML::a(Yii::t('frontend','Block sender'),$links['footer_block']); ?> |
19 |
<?php //echo HTML::a(Yii::t('frontend','Block all'),$links['footer_block_all']); ?> |
20 |
</em> |
21 |
</td></tr> |
22 |
</table> |
23 |
</center> |
24 |
</td> |
25 |
</tr> |
Обновление существующих шаблонов
Чтобы интегрировать шаблоны, я хотел начать с самого простого. Перемещение по сложным, незнакомым CSS и HTML никогда не бывает простым.
Я начал с нашего шаблона электронной почты «Сброс пароля».
Восстановление пароля
Я выбрал шаблон приветствия Oxygen, показанный ниже:



Каждый из отдельных шаблонов SendwithU может быть просмотрен и протестирован на их учетной записи Litmus.
Вот как сейчас выглядит письмо со сбросом нароля на iPhone, гораздо лучше, чем раньше:



Я был немного смущен, когда увидел первые письма в Gmail и они выглядели поврежденными.



Но просмотр их превью Litmus показал мне, как именно так они выглядят в Gmail:



Позже я узнал, что Gmail требует большей инкрустации CSS, чем другие сервисы. Я проведу вас через фикс этого бага в следующем учебнике.
Вот код passwordRequestToken.php:
1 |
<?php
|
2 |
use yii\helpers\Html; |
3 |
/* @var $this yii\web\View */
|
4 |
/* @var $user common\models\User */
|
5 |
$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]); |
6 |
?>
|
7 |
<tr>
|
8 |
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding"> |
9 |
<center>
|
10 |
<table cellspacing="0" cellpadding="0" width="600" class="w320"> |
11 |
<tr>
|
12 |
<td class="header-lg"> |
13 |
Reset Your Password |
14 |
</td>
|
15 |
</tr>
|
16 |
<tr>
|
17 |
<td class="free-text"> |
18 |
Hello <?php echo Html::encode(\common\components\MiscHelpers::getDisplayName($user->id)); ?>, |
19 |
Click the button below to reset your Meeting Planner password: |
20 |
</td>
|
21 |
</tr>
|
22 |
<tr>
|
23 |
<td class="button"> |
24 |
<div><!--[if mso]> |
25 |
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="http://" style="height:45px;v-text-anchor:middle;width:155px;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">
|
26 |
<w:anchorlock/>
|
27 |
<center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">My Account</center>
|
28 |
</v:roundrect>
|
29 |
<![endif]--><a class="button-mobile" href="<?php echo $resetLink ?>" |
30 |
style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;">Reset Your Password</a></div> |
31 |
</td>
|
32 |
</tr>
|
33 |
</table>
|
34 |
</center>
|
35 |
</td>
|
36 |
</tr>
|
37 |
<tr>
|
38 |
<td align="center" valign="top" width="100%" style="background-color: #ffffff; border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;"> |
39 |
<center>
|
40 |
<br /> |
41 |
</center>
|
42 |
</td>
|
43 |
</tr>
|
44 |
<?php echo \Yii::$app->view->renderFile('@common/mail/section-footer-static.php') ?> |
Запрос на собрание
Приглашение, которое люди получают в качестве запроса на встречу, является нашим самым сложным шаблоном. Он содержит краткое введение, возможные места, возможное время, а иногда и записку.
Для этого я использовал шаблон Oxygen Confirm:



Я думал, что Shipping и Date Shipped могут использоваться для совместного использования опций Place and Date Time, и это работает довольно хорошо.
Вот как выглядит приглашение:



Конечно, основной эстетический внешний вид намного лучше. В будущем я могу сделать некоторую работу, чтобы выровнять и сравнять вертикальные высоты мест и полей времени.
Вот код тела invitation-html.php:
1 |
<?php
|
2 |
use yii\helpers\Html; |
3 |
use yii\helpers\Url; |
4 |
use common\components\MiscHelpers; |
5 |
use frontend\models\Meeting; |
6 |
use frontend\models\MeetingNote; |
7 |
use frontend\models\MeetingPlace; |
8 |
use frontend\models\MeetingTime; |
9 |
/* @var $this \yii\web\View view component instance */
|
10 |
/* @var $message \yii\mail\BaseMessage instance of newly created mail message */
|
11 |
?>
|
12 |
<tr>
|
13 |
<td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding"> |
14 |
<center>
|
15 |
<table cellspacing="0" cellpadding="0" width="600" class="w320"> |
16 |
<tr>
|
17 |
<td class="header-lg"> |
18 |
Your Meeting Request |
19 |
</td>
|
20 |
</tr>
|
21 |
<tr>
|
22 |
<td class="free-text"> |
23 |
<p><em>Hi, <?php echo $owner; ?> is inviting you to an event using a new service called <?php echo HTML::a(Yii::t('frontend','Meeting Planner'),MiscHelpers::buildCommand($meeting_id,Meeting::COMMAND_HOME,0,$user_id,$auth_key)); ?>. The service makes it easy to plan meetings without the exhausting threads of repetitive emails. Please try it out below.</em></p> |
24 |
<p><?php echo $intro; ?></p> |
25 |
</td>
|
26 |
</tr>
|
27 |
<tr>
|
28 |
<td class="button"> |
29 |
<div><!--[if mso]> |
30 |
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="http://" style="height:45px;v-text-anchor:middle;width:155px;" arcsize="15%" strokecolor="#ffffff" fillcolor="#ff6f6f">
|
31 |
<w:anchorlock/>
|
32 |
<center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">Track Order</center>
|
33 |
</v:roundrect>
|
34 |
<![endif]--><a href="<?php echo $links['view']; ?>" style="background-color:#ff6f6f;border-radius:5px;color:#ffffff;display:inline-block;font-family:'Cabin', Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;line-height:45px;text-align:center;text-decoration:none;width:155px;-webkit-text-size-adjust:none;mso-hide:all;"><?php echo Yii::t('frontend','View Request')?></a> |
35 |
</div>
|
36 |
</td>
|
37 |
</tr>
|
38 |
<tr>
|
39 |
<td class="w320"> |
40 |
<table cellpadding="0" cellspacing="0" width="100%"> |
41 |
<tr>
|
42 |
<td class="mini-container-left"> |
43 |
<table cellpadding="0" cellspacing="0" width="100%"> |
44 |
<tr>
|
45 |
<td class="mini-block-padding"> |
46 |
<table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;"> |
47 |
<tr>
|
48 |
<td class="mini-block"> |
49 |
<span class="header-sm">Possible Times</span><br /> |
50 |
<?php
|
51 |
foreach($times as $t) { |
52 |
?>
|
53 |
<?php echo Meeting::friendlyDateFromTimestamp($t->start); ?><br /> |
54 |
<?php
|
55 |
}
|
56 |
?>
|
57 |
<?php // echo HTML::a(Yii::t('frontend','accept all times'),$links['accepttimes']); ?> |
58 |
<br /> |
59 |
<?php |
60 |
if ($meetingSettings->participant_add_date_time) { ?> |
61 |
<?php echo HTML::a(Yii::t('frontend','suggest a time'),$links['addtime']); ?><br /> |
62 |
<?php
|
63 |
}
|
64 |
?>
|
65 |
</td>
|
66 |
</tr>
|
67 |
</table>
|
68 |
</td>
|
69 |
</tr>
|
70 |
</table>
|
71 |
</td>
|
72 |
<td class="mini-container-right"> |
73 |
<table cellpadding="0" cellspacing="0" width="100%"> |
74 |
<tr>
|
75 |
<td class="mini-block-padding"> |
76 |
<table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;"> |
77 |
<tr>
|
78 |
<td class="mini-block"> |
79 |
<span class="header-sm">Possible Places</span><br /> |
80 |
<?php
|
81 |
if (!$noPlaces) { |
82 |
?>
|
83 |
<?php
|
84 |
foreach($places as $p) { |
85 |
?>
|
86 |
<?php echo $p->place->name.' '; ?> |
87 |
<span style="font-size:75%;"><?php echo $p->place->vicinity; ?> <?php echo HTML::a(Yii::t('frontend','map'), |
88 |
MiscHelpers::buildCommand($meeting_id,Meeting::COMMAND_VIEW_MAP,$p->id,$user_id,$auth_key)); ?></span><br /> |
89 |
<?php
|
90 |
}
|
91 |
?>
|
92 |
<br /> |
93 |
<?php // echo HTML::a(Yii::t('frontend','accept all places'),$links['acceptplaces']); ?><br /> |
94 |
<?php |
95 |
if ($meetingSettings->participant_add_place) { ?> |
96 |
<?php echo HTML::a(Yii::t('frontend','suggest a place'),$links['addplace']); ?><br /> |
97 |
<?php
|
98 |
}
|
99 |
?>
|
100 |
<?php
|
101 |
} else { |
102 |
?>
|
103 |
Phone or video <br /> |
104 |
<?php
|
105 |
}
|
106 |
?>
|
107 |
</td>
|
108 |
</tr>
|
109 |
</table>
|
110 |
</td>
|
111 |
</tr>
|
112 |
</table>
|
113 |
</td>
|
114 |
</tr>
|
115 |
</table>
|
116 |
</td>
|
117 |
</tr>
|
118 |
<tr>
|
119 |
<td class="free-text"> |
120 |
<?php
|
121 |
if (!$noPlaces) { |
122 |
echo HTML::a(Yii::t('frontend','Accept all places and times'),$links['acceptall']).' | '; |
123 |
}
|
124 |
?>
|
125 |
<?php
|
126 |
if ($meetingSettings->participant_finalize && count($places)==1 && count($times)==1) { |
127 |
echo HTML::a(Yii::t('frontend','Finalize meeting'),$links['finalize']).' | '; |
128 |
}
|
129 |
?>
|
130 |
<?php echo HTML::a(Yii::t('frontend','Decline request'),$links['decline']); ?> |
131 |
</td>
|
132 |
</tr>
|
133 |
|
134 |
<?php echo \Yii::$app->view->renderFile('@common/mail/section-notes.php',['notes'=>$notes,'links'=>$links]) ?> |
135 |
</table>
|
136 |
</center>
|
137 |
</td>
|
138 |
</tr>
|
139 |
<?php echo \Yii::$app->view->renderFile('@common/mail/section-footer-dynamic.php',['links'=>$links]) ?> |
Что дальше?
Теперь, когда базовые шаблоны были обновлены, я начну работу над исправлением их отображения в Gmail. И вскоре мы будем использовать уведомления и напоминания.
Пока вы ждете эпизодов об этих функциях, запланируйте свою первую встречу и попробуйте новые шаблоны. Кроме того, я был бы признателен, если вы поделитесь своим опытом ниже в комментариях, меня всегда интересуют ваши предложения. Вы также можете связаться со мной в Twitter @reifman напрямую.
Я также начинаю экспериментировать с WeFunder, основываясь на реализации новых правил SEC. Пожалуйста, подпишитесь на наш профиль. Я могу больше написать об этом как часть нашей серии.
Следите за предстоящими учебными пособиями в разделе серии Пишем свой стартап на PHP. Впереди у нас есть еще несколько больших функций.