Advertisement
  1. Code
  2. Yii

Пишем свой стартап: очистка шаблонов электронных писем

Scroll to top
Read Time: 10 min
This post is part of a series called Building Your Startup With PHP.
Building Your Startup: The Open-Source Foundation Behind Meeting Planner
Building Your Startup: Responsive Email for Gmail

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

Final product imageFinal product imageFinal product image
What You'll Be Creating

Этот учебник является частью серии статей Пишем свой стартап на PHP на Envato Tuts +. В этой серии я направляю вас через запуск приложения от концепции до готового продукта, используя приложение Планировщик встреч в качестве реального приложения. На каждом шаге я делаю релиз кода планировщика собраний в качестве примеров с открытым исходным кодом, из которых вы можете узнать что-то новое. Я также рассмотрю связанные с запуском бизнес-вопросы по мере их возникновения.

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

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

Вероятно, половина первого опыта людей с Планировщиком собраний будет через электронную почту с запросом на собрание.

Если вы еще не опробовали Планировщик собраний, то обязательно запланируйте свою первую встречу. Я участвую в комментариях ниже, так что скажите мне, что вы думаете! Вы также можете связаться со мной в Twitter @reifman. Меня особенно интересует, если вы хотите предложить новые функции или темы для будущих учебников.

Напомним, что весь код Планировщика собраний написан в Yii2 Framework для PHP. Если вы хотите узнать больше о Yii2, ознакомьтесь с нашей параллельной серией Программирование с Yii2.

Сообщения и шаблоны

Ранние шаблоны

Первоначально я использовал базовые фреймворки, которые я нашел в Интернете, чтобы создавать первые письма для Планировщика собраний. Они хорошо работали на ранней стадии разработки.

Вот пример наших существующих HTML-писем; они функциональны, но выглядят не очень привлекательно. И, в целом, я решил, что людям не нужны эти многочисленные варианты и ссылки в их приглашениях. Пришло время уменьшить множество вариантов для более простого опыта.

Meeting Planner Templates - Original HTML Invitation TemplateMeeting Planner Templates - Original HTML Invitation TemplateMeeting Planner Templates - Original HTML Invitation Template

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

Meeting Planner Templates - Example of Corrupted View of Invitation in GmailMeeting Planner Templates - Example of Corrupted View of Invitation in GmailMeeting Planner Templates - Example of Corrupted View of Invitation in Gmail

Я собирался перейти на более профессиональные шаблоны электронной почты, но я явно запоздал с этим.

Разнообразие сообщений

Ниже приведен список сообщений, которые регулярно отправляет Планировщик собраний:

  • запросы на собрания (т. е. приглашения)
  • обновление cобраний для изменений (также известны как уведомления)
  • подтверждение встречи с прикреплением файла календаря
  • напоминание о встрече
  • запросы на контактную информацию для предстоящей встречи
  • запросы на сброс пароля

Для альфа-теста я надеюсь, что смогу достичь разумной эстетической основы, применяя open-source шаблоны, которые можно найти в Интернете. Затем мы наймем дизайнера, чтобы оживить шаблоны, тему и бренд.

Я начал поиск хороших шаблонов для электронных писем.

Open-Source ресурсы с шаблонами

Существует ряд полезных руководств, таких как:

И ряд провайдеров предлагают собственные шаблоны с открытым исходным кодом:

Первоначально меня привлекали шаблоны Mailgun, так как я чувствовал, что они были тщательно протестированы, и я мог бы опираться на них, но в конечном итоге я решил воспользоваться шаблонами электронных писем от Sendwithus Oxygen. Sendwithus - это синергетическая маркетинговая платформа для Mailgun (или, возможно, других поставщиков электронной почты), но у меня нет опыта работы с их общим сервисом.

Oxygen предложил полное семейство шаблонов для различных полезных сценариев. Они казалось простыми, организованными и легко расширяемыми:

Meeting Planner Templates - The Send With Us Oxygen TemplatesMeeting Planner Templates - The Send With Us Oxygen TemplatesMeeting Planner Templates - The Send With Us Oxygen Templates

Это очень приятно с их стороны предложить свои 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, на сервере Планировщика собраний.

Meeting Planner Templates - LogoMeeting Planner Templates - LogoMeeting Planner Templates - Logo

Я также заменил ссылки по умолчанию в письме с кодом для ссылок на наш сайт.

Создание нижнего колонтитула

Чтобы упростить повторное использование в приложении, я разделил код нижнего колонтитула:

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, показанный ниже:

Meeting Planner Templates - Oxygen Welcome TemplateMeeting Planner Templates - Oxygen Welcome TemplateMeeting Planner Templates - Oxygen Welcome Template

Каждый из отдельных шаблонов SendwithU может быть просмотрен и протестирован на их учетной записи Litmus.

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

Meeting Planner Templates - Reset Your PasswordMeeting Planner Templates - Reset Your PasswordMeeting Planner Templates - Reset Your Password

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

Meeting Planner Templates - Reset Your Password in GmailMeeting Planner Templates - Reset Your Password in GmailMeeting Planner Templates - Reset Your Password in Gmail

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

Meeting Planner Templates - Oxygen Welcome in GmailMeeting Planner Templates - Oxygen Welcome in GmailMeeting Planner Templates - Oxygen Welcome in 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:

Meeting Planner Templates - Oxygen Confirm TemplateMeeting Planner Templates - Oxygen Confirm TemplateMeeting Planner Templates - Oxygen Confirm Template

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

Вот как выглядит приглашение:

Meeting Planner Templates - Meeting Request TemplateMeeting Planner Templates - Meeting Request TemplateMeeting Planner Templates - Meeting Request Template

Конечно, основной эстетический внешний вид намного лучше. В будущем я могу сделать некоторую работу, чтобы выровнять и сравнять вертикальные высоты мест и полей времени.

Вот код тела 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. Впереди у нас есть еще несколько больших функций.

Ссылки по теме

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.