Mugo Web main content.

Personalized page previews for e-mail marketing with wkhtmltoimage

By: Thiago Campos Viana | August 24, 2017 | eZ Publish add-ons, Web solutions, wkhtmltopdf, and htmltoimage

One way to attract visitors to your site is through e-mail marketing campaigns, but sending thousands of generic e-mails might not generate the kind of returns you're hoping for. One of our clients, FindaTopDoc, needed a customized approach for its e-mail campaign targeting potential new content contributor doctors. The goal was to generate an image that would show the recipient exactly what their personal blog would look like, including their profile picture and personal details.

Here is an example of the e-mail end result:

The preview picture is the image in the centre displaying how the blog would look when it's live. Recipients wouldn't just be seeing a generic doctor's information, but they'd be seeing their own name, picture, and information. This made the marketing campaign much more impactful.

In this case, including the entire site's HTML and CSS to show a customized blog preview image in the e-mail was out of the question because it was impossible to get consistent results using e-mail-safe HTML. We needed to come up with another solution. We had already used wkhtmltopdf to generate highly customized PDFs, so we decided to try wkhtmltoimage, which is part of wkhtmltopdf. In short, our solution was to use wkhtmltoimage to take screenshots of what each doctor's page would look like.

Here's a simple proof of concept:

# installing all the necessary libraries on Ubuntu
sudo apt-get install libxrender1 xfonts-utils xfonts-base xfonts-75dpi libfontenc1 x11-common xfonts-encodings libxfont1 fontconfig

# installing wkhtmltoimage
wget https://downloads.wkhtmltopdf.org/0.12/0.12.4/wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
tar xf wkhtmltox-0.12.4_linux-generic-amd64.tar.xz
sudo mv wkhtmltox/bin/wkhtmltoimage /usr/bin/
sudo chmod a+x /usr/bin/wkhtmltoimage
sudo mv wkhtmltox/bin/wkhtmltopdf /usr/bin/
sudo chmod a+x /usr/bin/wkhtmltopdf

# Testing
wkhtmltoimage --height 900 --width 1280 https://www.previewsite.com/preview_page_path test.png

# Access: https://www.previewsite.com/public_folder_path/images/test.png

For the production code, we defined a custom (and private) URL route in our Symfony-based CMS eZ Publish that used doctor IDs that both the e-mail marketing template (in Bronto) and the CMS can look up. That way, the CMS could dynamically generate each doctor's profile page based on their ID. wkhtmltoimage would then be able to take a screenshot of each page.

# start defining a route where ID is the user id, we append preview.png so the user thinks it is a path to an image
fatd_expert_profile_preview:
    path: /BlogExpertProfilePreview/{id}/preview.jpg
    defaults: { _controller: SomeBaseBundle:Preview:profilePreview }

Here's the controller action that generates the image. We make use of PHP WkHtmlToPdf, which is a PHP wrapper for wkhtmltopdf.

/**
 * Action to return the blog preview image given an user id
 *
 * @param type $id the user id
 * @return Response the preview image binary data
 */
public function profilePreviewAction( $id )
{
    $this->setDefaults();
    $legacyKernel = $this->container->get('ezpublish_legacy.kernel');
    $image = $legacyKernel()->runCallback(
        function () use ( $id )
        {
            // first check if the preview image for this user already exists
            $previewImagePath = "public_folder_path/{$id}.jpg";
            if( file_exists( $previewImagePath ) )
            {
                // return the existing image
                return $previewImagePath;
            }
            // if not, try to generate an image
            // there is an existing route which shows a preview version of a blog page
            // take the screenshot of that page and save the image
            $ini = \eZINI::instance('fatd.ini');
            $siteURL = $ini->variable('Site', 'URL');
            $image = new \mikehaertl\wkhtmlto\Image( "{$siteURL}/BlogExpertProfilePreview/{$id}");
            $options = array(
                'user-style-sheet' => 'extension_css_path/wkhtml_override.css'
                , 'width' => '1280'
                , 'height' => '935'
            );
            $image->setOptions( $options );
            $image->type = 'jpg';
            $image->saveAs( $previewImagePath );
            \eZLog::write( "Created: {$siteURL}/{$previewImagePath}", "preview.log" );
            $url = "{$siteURL}/{$previewImagePath}";
            return $previewImagePath;
        }
    );
    if( $image && file_exists($image) )
    {
        $response = new BinaryFileResponse($image);
        return $response;
    }

    // if something goes wrong, return the default image
    response = new BinaryFileResponse('public_folder_path/default.jpg');
    return $response;
}

The e-mail template would load an image URL such as https://www.previewsite.com/BlogExpertProfilePreview/83920/preview.jpg (where "83920" is the doctor's ID), and when the recipient opened the e-mail, the system would generate the preview image. Now that's what we call a personalized e-mail!