Building Your Startup: Refining Email Templates

Final product image
What You’ll Be Creating

This tutorial is part of the Building Your Startup With PHP series on Envato Tuts+. In this series, I’m guiding you through launching a startup from concept to reality using my Meeting Planner app as a real-life example. Every step along the way, I’ll release the Meeting Planner code as open-source examples you can learn from. I’ll also address startup-related business issues as they arise.

In this tutorial, I’ll be refining the responsive, HTML email templates that Meeting Planner uses to send out invitations, notifications, reminders, and account-related messages.

During the initial stage of Meeting Planner development, I’ve focused primarily on functionality and have not yet invested significantly in design or hired a designer. Today’s goal is to clean up the appearance of the existing HTML templates so that the basic emails are more readable and usable for people.

Likely half of people’s first experience with Meeting Planner will be via a Meeting Request email.

If you haven’t tried out Meeting Planner yet, go ahead and schedule your first meeting. I do participate in the comment threads below, so tell me what you think! You can also reach me on Twitter @reifman. I’m especially interested if you want to suggest new features or topics for future tutorials.

As a reminder, all of the code for Meeting Planner is written in the Yii2 Framework for PHP. If you’d like to learn more about Yii2, check out our parallel series Programming With Yii2.

Messages and Templates

The Early Templates

Initially, I used basic frameworks I found on the web to create early emails for Meeting Planner. They worked acceptably for the early development phase.

Here’s an example of our existing HTML emails; they’re functional but not very appealing. And, overall, I’ve decided that people don’t need this many options and links within their invitations. It was time to reduce the plethora of options for a simpler experience.

Meeting Planner Templates - Original HTML Invitation Template

Even with the templates I’d used, email tables often appeared corrupted without easy explanation:

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

I’d been preparing to migrate to more professional email templates, but it was definitely overdue.

The Variety of Messages 

Here’s a summary of the messages that Meeting Planner regularly sends today:

  • meeting requests (i.e. invitations)
  • meeting updates for changes (also known as notifications)
  • meeting confirmations with calendar file attachment
  • meeting reminders
  • requests for contact information for an upcoming meeting
  • password reset requests

For the alpha test, I’m hoping I can reach a reasonable aesthetic baseline by applying open-source templates to be found on the web. At a later date, we’ll hire a designer to invigorate the templates, theme and brand together.

I began scouring the web for the best email templates.

Open-Source Template Resources

There are a number of helpful guides like these:

And a number of providers offer open-source templates of their own:

Initially, I was drawn to the Mailgun templates as I felt they were thoroughly tested and I could build on them, but ultimately, I decided to go with Sendwithus’s Oxygen email templates. Sendwithus is a synergistic marketing platform for Mailgun (or potentially other email providers), but I don’t have experience with their overall service.

Oxygen offered a complete family of templates for useful scenarios. It seemed simple, organized, and easily extensible:

Meeting Planner Templates - The Send With Us Oxygen Templates

It’s kind of them to offer their open-source email templates without requiring you to be a paying user. Go open source!

Integrating the New Templates

As an MVC framework, Yii separates layouts from body content. So I needed to break down the templates and monitor differences between variations within a group.

You can download the Oxygen family of templates from the Sendwithus GitHub, but they’ve not clearly divided the standard style elements common to each template, so you have to do that yourself.

It took some time to choose which templates I preferred, which elements I liked in each, and which CSS should be integrated into the layout.

Separating the Layout

Ultimately, here’s the new HTML layout (I’ve truncated the styles here for readability):

<?php
use yii\helpers\Html;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */
?>
<?php $this->beginPage(); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=<?php echo Yii::$app->charset; ?>" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title><?php echo Html::encode($this->title); ?></title>
  <?php $this->head(); ?>
    <style type="text/css">
  ...
</style>
<link rel="stylesheet" media="screen" type="text/css" href="http://fonts.googleapis.com/css?family=Oxygen:400,700">
...
</head>
<body>
<?php $this->beginBody(); ?>
<body bgcolor="#f7f7f7">
<table align="center" cellpadding="0" cellspacing="0" class="container-for-gmail-android" width="100%">
  <tr>
    <td align="left" valign="top" width="100%" style="background:repeat-x url(https://meetingplanner.io/img/bg_top_02.jpg) #ffffff;">
      <center>
        <?= Html::img('https://meetingplanner.io/img/transparent.png', ['class'=>'force-width-gmail']);?>
        <table cellspacing="0" cellpadding="0" width="100%" bgcolor="#ffffff" background="https://meetingplanner.io/img/bg_top_02.jpg" style="background-color:transparent">
          <tr>
            <td width="100%" height="80" valign="top" style="text-align: center; vertical-align:middle;">
            <!--[if gte mso 9]>
            <v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="mso-width-percent:1000;height:80px; v-text-anchor:middle;">
              <v:fill type="tile" src="http://s3.amazonaws.com/swu-filepicker/4E687TRe69Ld95IDWyEg_bg_top_02.jpg" color="#ffffff" />
              <v:textbox inset="0,0,0,0">
            <![endif]-->
              <center>
                <table cellpadding="0" cellspacing="0" width="600" class="w320">
                  <tr>
                    <td class="pull-left mobile-header-padding-left" style="vertical-align: middle;">
                      <a href="https://meetingplanner.io"><?= Html::img('https://meetingplanner.io/img/email-logo.gif', ['alt'=>'Meeting Planner logo','height'=>'47','width'=>'137']);?></a>
                    </td>
                    <td class="pull-right mobile-header-padding-right" style="color: #4d4d4d;">
                      <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>
                      <!-- <a href=""><img width="38" height="47" src="http://s3.amazonaws.com/swu-filepicker/LMPMj7JSRoCWypAvzaN3_social_09.gif" alt="facebook" /></a>-->
                      <!-- <a href=""><img width="40" height="47" src="http://s3.amazonaws.com/swu-filepicker/hR33ye5FQXuDDarXCGIW_social_10.gif" alt="rss" /></a>-->
                    </td>
                  </tr>
                </table>
              </center>
              <!--[if gte mso 9]>
              </v:textbox>
            </v:rect>
            <![endif]-->
            </td>
          </tr>
        </table>
      </center>
    </td>
  </tr>
<?php echo $content; ?>
</table>
<?php $this->endBody(); ?>
</body>
</html>
<?php $this->endPage(); ?>

Replacing the Common Elements

Within the templates, I had to replace a number of elements:

  • Logo
  • Supporting images 
  • Links

I created a logo file that would work for now, and I statically hosted it and the helper images, e.g. for Twitter, on Meeting Planner’s server.

Meeting Planner Templates - Logo

I also replaced the default links within the email with code for our site links.

Building a Footer Section

To simplify reuse across the application, I separated the code for the footer:

<?php
use yii\helpers\Html;
use yii\helpers\Url;
use common\components\MiscHelpers;
?>
<tr>
  <td align="center" valign="top" width="100%" style="background-color: #f7f7f7; height: 100px;">
    <center>
      <table cellspacing="0" cellpadding="0" width="600" class="w320">
        <tr>
          <td style="padding: 25px 0 15px">
            <strong><?php echo Html::a(Yii::t('frontend','Meeting Planner'), $links['home']); ?></strong><br />
            Seattle, Washington<br />
          </td>
        </tr>
        <tr><td style="font-size:75%;"><em>
          <?php echo HTML::a(Yii::t('frontend','Email settings'),$links['footer_email']); ?>
          | <?php echo HTML::a(Yii::t('frontend','Block sender'),$links['footer_block']); ?>
          <?php //echo HTML::a(Yii::t('frontend','Block all'),$links['footer_block_all']); ?>
        </em>
        </td></tr>
      </table>
    </center>
  </td>
</tr>

Updating the Existing Templates

To integrate the templates, I wanted to start with the easiest one. Moving around complex, unfamiliar CSS and HTML is never simple.

I began with our Password Reset email template.

Password Reset

I chose Oxygen’s Welcome template shown below:

Meeting Planner Templates - Oxygen Welcome Template

Each of SendwithUs’s individual templates can be previewed and tested on their Litmus account.

Here’s our reset your password email now on an iPhone, much more aesthetically comfortable than before:

Meeting Planner Templates - Reset Your Password

I was a bit confused when the first Gmail attempts I received looked malformed to me. 

Meeting Planner Templates - Reset Your Password in Gmail

But reviewing their Litmus previews showed me that that’s what they look like in Gmail:

Meeting Planner Templates - Oxygen Welcome in Gmail

I later learned that Gmail requires more inlining of CSS than other services. I’ll guide you through repairing this in a future tutorial.

Here’s the passwordRequestToken.php code that helped generate the above:

<?php
use yii\helpers\Html;
/* @var $this yii\web\View */
/* @var $user common\models\User */
$resetLink = Yii::$app->urlManager->createAbsoluteUrl(['site/reset-password', 'token' => $user->password_reset_token]);
?>
<tr>
  <td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
    <center>
      <table cellspacing="0" cellpadding="0" width="600" class="w320">
        <tr>
          <td class="header-lg">
            Reset Your Password
          </td>
        </tr>
        <tr>
          <td class="free-text">
            Hello <?php echo Html::encode(\common\components\MiscHelpers::getDisplayName($user->id)); ?>,
            Click the button below to reset your Meeting Planner password:
          </td>
        </tr>
        <tr>
          <td class="button">
            <div><!--[if mso]>
              <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">
                <w:anchorlock/>
                <center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">My Account</center>
              </v:roundrect>
            <![endif]--><a class="button-mobile" href="<?php echo $resetLink ?>"
            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>
          </td>
        </tr>
      </table>
    </center>
  </td>
</tr>
<tr>
  <td align="center" valign="top" width="100%" style="background-color: #ffffff;  border-top: 1px solid #e5e5e5; border-bottom: 1px solid #e5e5e5;">
    <center>
      <br />
    </center>
  </td>
</tr>
<?php echo \Yii::$app->view->renderFile('@common/mail/section-footer-static.php') ?>

The Meeting Request

The invitation people receive as a request to meet is our most complex template. It’s sharing a brief introduction, possible places, possible times, and sometimes a note.

For this, I used the Oxygen Confirm template:

Meeting Planner Templates - Oxygen Confirm Template

I thought the Shipping and Date Shipped boxes could be used for sharing Place and Date Time options, and it’s worked fairly well.

Here’s what the invitation looks like now:

Meeting Planner Templates - Meeting Request Template

Certainly, the basic aesthetic appearance is much better. In the future, I may do some work to level and equalize the vertical heights of the places and date times boxes.

Here’s the invitation-html.php body code that helped generate the above:

<?php
use yii\helpers\Html;
use yii\helpers\Url;
use common\components\MiscHelpers;
use frontend\models\Meeting;
use frontend\models\MeetingNote;
use frontend\models\MeetingPlace;
use frontend\models\MeetingTime;
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\BaseMessage instance of newly created mail message */
?>
<tr>
  <td align="center" valign="top" width="100%" style="background-color: #f7f7f7;" class="content-padding">
    <center>
      <table cellspacing="0" cellpadding="0" width="600" class="w320">
        <tr>
          <td class="header-lg">
            Your Meeting Request
          </td>
        </tr>
        <tr>
          <td class="free-text">
            <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>
            <p><?php echo $intro; ?></p>
          </td>
        </tr>
        <tr>
          <td class="button">
            <div><!--[if mso]>
              <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">
                <w:anchorlock/>
                <center style="color:#ffffff;font-family:Helvetica, Arial, sans-serif;font-size:14px;font-weight:regular;">Track Order</center>
              </v:roundrect>
            <![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>
          </div>
          </td>
        </tr>
        <tr>
          <td class="w320">
            <table cellpadding="0" cellspacing="0" width="100%">
              <tr>
                <td class="mini-container-left">
                  <table cellpadding="0" cellspacing="0" width="100%">
                    <tr>
                      <td class="mini-block-padding">
                        <table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;">
                          <tr>
                            <td class="mini-block">
                              <span class="header-sm">Possible Times</span><br />
                              <?php
                                foreach($times as $t) {
                                  ?>
                                      <?php echo Meeting::friendlyDateFromTimestamp($t->start); ?><br />
                                      <?php
                                    }
                                ?>
                                <?php // echo HTML::a(Yii::t('frontend','accept all times'),$links['accepttimes']); ?>
                                <br />
                                <?php
                                if ($meetingSettings->participant_add_date_time) { ?>
                                <?php echo HTML::a(Yii::t('frontend','suggest a time'),$links['addtime']); ?><br />
                                <?php
                                }
                                ?>
                            </td>
                          </tr>
                        </table>
                      </td>
                    </tr>
                  </table>
                </td>
                <td class="mini-container-right">
                  <table cellpadding="0" cellspacing="0" width="100%">
                    <tr>
                      <td class="mini-block-padding">
                        <table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate !important;">
                          <tr>
                            <td class="mini-block">
                              <span class="header-sm">Possible Places</span><br />
                              <?php
                              if (!$noPlaces) {
                              ?>
                              <?php
                                foreach($places as $p) {
                                  ?>
                                      <?php echo $p->place->name.' '; ?>
                                      <span style="font-size:75%;"><?php echo $p->place->vicinity; ?> <?php echo HTML::a(Yii::t('frontend','map'),
                                      MiscHelpers::buildCommand($meeting_id,Meeting::COMMAND_VIEW_MAP,$p->id,$user_id,$auth_key)); ?></span><br />
                                <?php
                                    }
                                ?>
                                <br />
                                <?php // echo HTML::a(Yii::t('frontend','accept all places'),$links['acceptplaces']); ?><br />
                                <?php
                                if ($meetingSettings->participant_add_place) { ?>
                                <?php echo HTML::a(Yii::t('frontend','suggest a place'),$links['addplace']); ?><br />
                                <?php
                                }
                                ?>
                                <?php
                              } else {
                                  ?>
                                  Phone or video <br />
                              <?php
                                }
                              ?>
                            </td>
                          </tr>
                        </table>
                      </td>
                    </tr>
                  </table>
                </td>
              </tr>
            </table>
          </td>
        </tr>
        <tr>
          <td class="free-text">
            <?php
                if (!$noPlaces) {
                    echo HTML::a(Yii::t('frontend','Accept all places and times'),$links['acceptall']).' | ';
                }
             ?>
               <?php
               if ($meetingSettings->participant_finalize && count($places)==1 && count($times)==1) {
                echo HTML::a(Yii::t('frontend','Finalize meeting'),$links['finalize']).' | ';
               }
               ?>
               <?php echo HTML::a(Yii::t('frontend','Decline request'),$links['decline']); ?>
          </td>
        </tr>

        <?php echo \Yii::$app->view->renderFile('@common/mail/section-notes.php',['notes'=>$notes,'links'=>$links]) ?>
      </table>
    </center>
  </td>
</tr>
<?php echo \Yii::$app->view->renderFile('@common/mail/section-footer-dynamic.php',['links'=>$links]) ?>

What’s Next?

Now that the basic templates have been updated, I’ll begin working to repair their appearances in Gmail. And soon, we’ll implement notifications and reminders using them.

While you’re waiting for episodes about these features, schedule your first meeting and try out the new templates. Also, I’d appreciate it if you share your experience below in the comments, and I’m always interested in your suggestions. You can also reach me on Twitter @reifman directly.

I’m also beginning to experiment with WeFunder based on the implementation of the SEC’s new crowdfunding rules. Please consider following our profile. I may write about this more as part of our series.

Watch for upcoming tutorials in the Building Your Startup With PHP series. There are a few more big features coming up.

Related Links

Download Building Your Startup: Refining Email Templates

Leave a Reply

Your email address will not be published. Required fields are marked *