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.
Even with the templates I’d used, email tables often appeared corrupted without easy explanation:
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:
- 30 Free Responsive Email and NewsletterTemplates (SpeckyBoy)
- 12 Completely Free Email Templates Resources (Iterable)
And a number of providers offer open-source templates of their own:
- Mailgun Transactional HTML Email Templates
- Sendwithus Open Source Email Templates
- Litmus Community Templates and Free Responsive Templates
- Mailchimp/email-blueprints: HTML Email Layouts by MailChimp
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:
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.
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:
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:
I was a bit confused when the first Gmail attempts I received looked malformed to me.
But reviewing their Litmus previews showed me that that’s what they look like 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:
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:
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.