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 our earlier episode, Delivering Invitations, I introduced responsive, HTML email templates which we built on some open-source MailChimp templates. Then, in our previous episode, Refining Email Templates, we migrated to the Sendwithus Oxygen templates. The new templates looked great:
But there were big problems with the templates in Gmail. Here's the Oxygen reference template in Gmail:
In this tutorial, I'll tell the story of how I cleaned up the appearance of the new HTML templates within Gmail. It has to do with inlining the CSS because the geniuses at Google don't support style definitions like everyone else.
Since likely half of people's first experience with Meeting Planner will be via a Meeting Request email and probably 10 to 25 percent of those may be with Gmail, this was important to fix.
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.
A Quick Refresh
For email templates, I decided to go with Sendwithus's Oxygen email templates. Oxygen offered a complete family of templates for useful scenarios. It seemed simple, organized, and easily extensible:
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 (which they've since updated and repaired), showed me that that's just what they look like in Gmail:
I later learned that Gmail requires more inlining of CSS than other services. My task was to solve this problem for Meeting Planner users.
Solving the Gmail Rendering Problem
Initially, I thought we would need to manage the CSS inlining for Gmail ourselves. There were two paths to take, neither easy.
Either a) I could have processed every outbound message through a self-hosted, Python premailer after it was fully constructed, or, b) I could apply inlining to the templates within the source tree.
For simplicity, I chose to apply inlining to the templates manually, thinking I might opt for live inlining in the future. I'm not as familiar with running Python tasks from PHP and monitoring their performance on a critical feature such as emailed meeting invitations, so I decided not to go this route initially. Similarly, if I need to send an email to all 10,000 users, that's 10,000 inlining events, which could slow the server down.
Still, the approach I chose was not easy and turned into a bit of a procedural nightmare. There wasn't an easy way to programmatically generate HTML that could be easily processed and then returned to PHP for dynamic email generation.
Peter Bengtsson's Premailer inlines CSS for you via Python. He also helpfully pointed me to Premailer.io, which made things easier.
Basically, you copy and paste your email template into the Textarea tab and click the Convert button (not shown):
Then you can copy and paste the resulting email template with CSS inlined:
Here's a better view of HTML with CSS inlined—notice embedded styles with every HTML tag, the way Gmail engineers like it:
<tr> <td align="center" valign="top" width="100%" style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; padding:20px 0 30px; background-color:#f7f7f7" bgcolor="#f7f7f7"> <center> <table cellspacing="0" cellpadding="0" width="600" style="border-collapse:collapse"> <tr> <td style="color:#4d4d4d; font-family:Helvetica, Arial, sans-serif; font-size:32px; line-height:normal; text-align:center; border-collapse:collapse; font-weight:700; padding:35px 0 0" align="center"> Changes to Your Meeting </td> </tr> <tr> <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:10px 60px 0; width:100%" align="center" width="100%"> <p>Hi <?php echo Html::encode(MiscHelpers::getDisplayName($user_id)); ?>, changes have been made to your meeting.</p> <p><?php echo $history; ?></p> <p>Please click the button below to view the meeting page.</p> </td> </tr>
You can even see the result in the Preview tab—I had just processed the layout header in these screenshots:
Also, it sort of had to be all done together. I could have created an old and new layout and modified template code one by one, but that would have created more areas of changes. Ultimately, I had about seven full templates and their shared component pieces to process.
Processing the Templates
Because Meeting Planner's built within Yii2's MVC architecture, emails have an outer layout template and an inner template with a lot of intermingled PHP for data generation. I also had sub-templates for common, reusable elements such as meeting notes and footers.
You can't just process PHP-laden view code. You have to remove PHP elements, run the template through Premailer, and then reintegrate the PHP code. I'm fortunate that most of my PHP code doesn't heavily use the styles and was fairly simple to cut and later reintegrate.
Taken together, this made the process of generating inlined templates quite difficult and time-consuming. It also made the idea of maintaining and building new templates challenging.
Basically, I had to create temporary files with the entire style block above and the HTML that I wanted to inline below, remove the PHP, process the template, and then reintegrate the PHP.
I processed the Mail HTML layout first. Then, I processed each individual sub-template and template.
But it worked; here's a screenshot of the Reset Your Password template in web-based Gmail:
Another More Promising Solution
At some point near the end of all this grunt work and growing realization of how hard it would be to maintain my templates in the near future, I wondered if Mailgun provided CSS inlining and began to search online.
Mailgun did not, but SwiftMailer, which Yii uses for mail SMTP delivery, does. It took another half hour to clean up my earlier work (a.k.a. mess) and sort everything out, installing the CSS Inlining plugin for SwiftMailer.
I used the PHP-based Open Buildings CSS Inliner plugin which I added to composer.json:
"minimum-stability": "stable", "require": { "php": ">=5.4.0", "yiisoft/yii2": ">=2.0.7", "yiisoft/yii2-bootstrap": "*", "yiisoft/yii2-swiftmailer": "*", "openbuildings/swiftmailer-css-inliner":"*", "2amigos/yii2-google-maps-library": "*",
Here's the composer update results:
$ composer update Loading composer repositories with package information Updating dependencies (including require-dev) - Installing openbuildings/swiftmailer-css-inliner (0.3.0) Downloading: 100% - Installing symfony/css-selector (v3.1.0) Downloading: 100% - Installing tijsverkoyen/css-to-inline-styles (1.5.5) Downloading: 100% Writing lock file Generating autoload files
I also needed to customize the mailer configuration to use the plugin within /common/config/main-local.php (in local and production):
], 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', 'viewPath' => '@common/mail', //comment the following array to send mail using php's mail function 'transport' => [ 'class' => 'Swift_SmtpTransport', 'host' => 'smtp.mailgun.org', 'username' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'password' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'port' => '587', 'encryption' => 'tls', 'plugins' => [ [ 'class' => 'Openbuildings\Swiftmailer\CssInlinerPlugin', ], ], ], // send all mails to a file by default. You have to set // 'useFileTransport' to false and configure a transport // for the mailer to send real emails. 'useFileTransport' => false, ], ],
Things began working pretty quickly, though there was still a major visual defect in Gmail:
The Open Building's CSS inliner written in PHP did not perform as successfully as the Python-based Premailer, at least out of the box with default settings.
Rather than spend time debugging the PHP-based inliner and the templates right now, I decided to switch back to the Premailer-processed templates. It works well for now.
At some point, I will need to revisit whether there are simple ways to fix this or to configure the PHP-based inlining plugin. Alternately, I may need to switch to the Python inliner.
When building a startup, you have to choose your battles and prioritize everything. At the moment, this is lower priority and fairly easy to work around.
In the code tree, you'll find /common/mail/inlining with a tree of various folders and a mail-readme.txt file to explain what's there. Basically, there are copies of the Oxygen templates, the working PHP versions, the edited pre-processed code, and the final processed code.
What's Next?
Currently, I'm working to prepare Meeting Planner for alpha release. I am primarily focused on improvements and features to make the alpha release go smoothly. I'm tracking everything in Asana now, which I'll write about in a future tutorial. There are also some interesting new features still on their way.
Finally, I'm beginning to experiment with WeFunder based on the implementation of the SEC's new crowdfunding rules. Please consider following our profile. I will also write more about this in a future tutorial.
While you're waiting for more episodes, schedule your first meeting and try out the templates with your friends with Gmail mailboxes. 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.
Watch for upcoming tutorials in the Building Your Startup With PHP series.
Comments