Email Marketing valid opens and IP tracking via Images

Many people wonder why email tracking software doesn't always display images by default, or more importantly, why click tracking has become problematic in today's age of emails.  The short answer to this is caching.  With all of the web based email systems like Gmail and Yahoo, many people are concerned about privacy. These big email systems have all started caching images in the emails.

Here are a few tips on how you can at least ensure your email tracking pixels are loaded properly so you can determine the real "views" of your email marketing campaigns.

  • Make sure your images are using a .png (or .PNG) extension. This is true for tracking images, as well as normal images in your email
  • Don't link your images to a .php page or or anything other than the image itself.  Every URL should be linked to the actual image.

Correct example:   https://eltoro.com/pixels/yourid-yourcampaign.png

Incorrect example: http://yourdomain.com/pixels/tracking.php?track=1234

  • Ideally, any image you use should additionally have a unique identifier in the URL so every user doesn't load the same image. This makes every image unique for every customer, and hence only loads to the cache when it is requested.  If you need to, it is acceptable to put a querystring after the .png.

Correct examples:

https://eltoro.com/pixels/yourid-yourcampaign-somethingunique.png

https://eltoro.com/pixels/yourid-yourcampaign.png?name=somethingunique

  • If you need the image to do something, specific such as write a record to a database, below is an example of how to do that:

Here is a simple way to have a PHP page to do the actual work but have the requested URL be a .png (the example below is for an Apache webserver)

  • First, make sure your webserver allows overrides for a directory (this is usually in your httpd.conf or vhost.conf or similar)
    <Directory />
    Options FollowSymLinks
    AllowOverride All
    </Directory>
  • Add the following to your .htaccess for your site (or modify your existing .htaccess to include the obvious line)
    <IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^pixel/(.*).png$ /realscriptlocation/bannertracker.php?$matches[1] [QSA,L]
    </IfModule>
  • Make sure your PHP has GD configured in it (it's an image creation plugin for php)
  • Create the bannertracker.php page to include (this is the actual code to create a 1px transparent image based on a dynamic URL)
    <?php
    $imagetomake = $_SERVER['QUERY_STRING'];
    $pieces = explode('-', $imagetomake);$customer = $pieces[1];
    $campaign = $pieces[2];
    $unique   = $pieces[3];//$color = '#000000';putenv('GDFONTPATH=' . realpath('.'));
    Header("Content-type: image/png");
    //$color = hex2rgb($color);
    $width=1;
    $height=1;$image=@imagecreate($width,$height)
    or die("Cannot Initialize new GD image stream");
    $black = imagecolorallocate($image, 0, 0, 0);
    // Make the background transparent
    imagecolortransparent($image, $black);## Display the result, and purge from Ram
    ImagePNG ($image);
    ImageDestroy ($image);
    ## Do some database work here to record the click
    ?>

Now just call the URL www.yoursite.com/pixel/dynamic-dynamic-uniqueid.png (which redirects at the server to the php page, passes the variables, and you'll get an image returned)

The email systems just see it only as an image and you receive the click tracking on each open while getting around the caching systems. It may take a bit more code to really accomplish certain goals.  Most likely your web developer will need to be involved but it's relatively simple to accomplish and solves your goals of having the images uncached and tracked.

Keep in mind that with web based systems with complex caching solutions (such as Google, etc), this solution won't work.  But for all of those users who use outlook directly on their PC, the IP of the user is in the headers that are sent back from them when they browse the image.

Example:
$ipaddress = '';
if (getenv('HTTP_CLIENT_IP'))
$ipaddress = getenv('HTTP_CLIENT_IP');
else if(getenv('HTTP_X_FORWARDED_FOR'))
$ipaddress = getenv('HTTP_X_FORWARDED_FOR');
else if(getenv('HTTP_X_FORWARDED'))
$ipaddress = getenv('HTTP_X_FORWARDED');
else if(getenv('HTTP_FORWARDED_FOR'))
$ipaddress = getenv('HTTP_FORWARDED_FOR');
else if(getenv('HTTP_FORWARDED'))
$ipaddress = getenv('HTTP_FORWARDED');
else if(getenv('REMOTE_ADDR'))
$ipaddress = getenv('REMOTE_ADDR');
else
$ipaddress = 'UNKNOWN';

You absolutely should verify the $ipaddress variable looks like an IP by using some sort of regex comparing IPv4 and IPv6 or using PHP filter_var()
IPv4 address
^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$

IPv6 address
^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*

if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE)) {
// it's valid
}
else {
// it's not valid
}
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// it's valid
}
else {
// it's not valid
}
Then check you really got one or the other valid. :)

Now, your image / code should be able to capture the IP address, and use it as you see fit.