Testing Apple’s Pixelgeddon: How To Build Your Own Email “Open” Tracking Pixel

During this year’s Apple WWDC conference, Apple announced an upcoming feature for iOS 15 that will be available for iOS devices called Mail Privacy Protection. One of the major features of Mail Privacy Protection is that images will be downloaded in the background 一 even without the email being read by the recipient, rendering open tracking moot. This is because open tracking is based on having an image pixel being loaded when a recipient opens the email.

I wrote some observations on what I found out through this testing in this article: A Technical Take on iOS15 Mail Privacy Protection.

If you’re curious to find out yourself how Apple is implementing this feature, you’re in luck.

Update: The tracking pixel code has been modified to support generating an external CSS pixel. Read this article for more details.

Installing iOS 15 Beta

Although iOS 15 won’t launch until at least September, you can download the iOS 15 Beta on your phone if you’re curious. A note of warning: Installing Beta operating systems always carries a risk that it could mess up your device, so do not attempt this without first backing up your phone.

If you are going ahead with the Beta download, there are two ways to do this. The first is the official way: by signing up for Apple’s Beta program. The second, the unofficial but faster way, is to simply install the Beta profile into your phone by visiting and downloading the iOS 15 Beta profile from BetaProfiles.com. 

Once you have iOS 15 Beta installed, you should see the prompt to turn on Mail Privacy Protection when you open up your mail app. Turn it on and you’re good to go.

Pixel Trackers: Two Options

If you have access to a web server that runs PHP, you can run your own pixel tracker that will give you the most amount of information available. However, if you don’t have access to a web server, you can also create a simple tracker using Google Analytics. You lose some fidelity (such as no IP or user agent), but you can get the time the pixels were loaded.

PHP script:

Note: this is a “proof of concept” and is not meant to be used as an industrial pixel tracker for millions of emails, as it will probably crash your server.

Below is a PHP script that will retrieve 3 things from a request and write to a file.

  1. The query string sent to it (using a predetermined parameter)
  2. The IP address
  3. The user agent string

Copy this script under your webserver directory and test it by visiting the URL (replace “/path/to” with the actual path your script is in):

https://your-server.com/path/to/pixel.php

Depending on your server setup, you might need to pre-create the pixel.log file and grant write permissions to your web server for the script to write to it. (The git source has the latest version of this code)

<?php
date_default_timezone_set('America/Los_Angeles');

log_request();

/* 
  Get the p "parameter" from the url
  ie. https://myserver.com/path/to/pixel.php?p=mypixel

  If p is not set, print "The pixel is live" else output a gif pixel

  NOTE: Make sure to only use https for the pixel:
  
*/
$p = @$_REQUEST['p'];
if(!$p){
  die("The pixel is live!");
} else {
  // https://stackoverflow.com/questions/3203354/php-script-to-render-a-single-transparent-pixel-png-or-gif/3203394
  header('Content-type: image/gif');
  header('Cache-Control: no-cache, no-store, must-revalidate');
  die(hex2bin('47494638396101000100900000ff000000000021f90405100000002c00000000010001000002020401003b'));
}

// Log the request as CSV
function log_request(){
  // You may need to pre-create pixel.log and set writable permissions
  $logfile = "pixel.log";

  $p = @$_REQUEST['p'] ?: 'not_set';
  $date = new DateTime();
  $dateval =  $date->format('Y-m-d H:i:s');
  $ip = @$_SERVER['REMOTE_ADDR']; // Remove IP address
  $agent = @$_SERVER['HTTP_USER_AGENT']; // User Agent
  $referer = @$_SERVER['HTTP_REFERER']; // Referer

  $list = [$dateval, $p, $ip, $agent, $referer];

  $file = fopen($logfile,"a");
  fputcsv($file,$list);
  fclose($file);
}
?>

If you have the URL correct, it should show:   “The Pixel is live!” 

And the pixel.log file should log your visit, as well.

"2021-07-25 17:22:30", not_set, 73.70.24.110, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",

Once you get that, you are ready to set up your pixel by appending a “p” parameter:

    https://your-server.com/path/to/pixel.php?p=my_email

Now you should just see a pixel (a 1×1 gif image) when you load the URL in a browser – and your log should log the visit.

"2021-07-25 17:25:22", my_email, 73.70.24.110, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",

Adding the pixel to your email:

Once this works, you are ready to embed your pixel into your email with the following code:

<img src="https://your-server.com/path/to/script/pixel.php?p=my_email" width=1 height=1 style="display:block;">

Once you embed the code, send the email, open it in an email client, and you should see the results in the log!

The following are example user agent strings based on the kinds of email clients your email is opened in:

iOS 13 iPhone prior to Apple's privacy pixel:
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"


iOS 15/Apple Mail when images are proxied through Apple's servers:
"Mozilla/5.0"


Gmail:
"Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"

As you can see other providers such as Gmail, Yahoo! Mail and Outlook.com also proxy their images.

Google Analytics

With Google Analytics, you cannot get the IP address or User Agent string of the request, just the time the request was made. You might be saying, what’s the point of this? Well, it gives you the most important data: WHEN the pixel was retrieved.

This is a good way to tell when the Mail client loads your pixel in the background, even if you haven’t opened the email. Read this article for more details 一 but note it can take from a few minutes to several hours for the Mail client to download the pixel in the background, so be patient.

Get your Google Analytics account set up:

Read this article to see how you can get your basic Google Analytics pixel set up.

Configuring your pixel:

For this pixel example, set the parameters as follows:

tid=UX-XXXXX (replace with your analytics id)
ec=pixel (event category)
ea=open (event action)
el=my_email (event label)

Your link would look like the following:

https://www.google-analytics.com/collect?v=1&tid=UA-XXXXX&t=event&cid=test&cn=test&cs=email&ec=pixel&ea=open&el=my_email

You can check if your pixel is firing by putting the link into your browser and going to Google Analytics to view the “Realtime / Events” tab:

Adding the pixel to your email:

Add the following code to the bottom of your email.

<img src="https://www.google-analytics.com/collect?v=1&tid=UA-XXXXX&t=event&cid=test&cn=test&cs=email&ec=pixel&ea=open&el=my_email" width=1 height=1 style="display:block;">

Getting hourly pixel loading data:

Once you are capturing data, you’d want to know when the events are actually coming in, which is the point of this exercise.

You can determine, the hour the pixel was loaded by loading the “Behavior / Events / Top Events” page on Google Analytics.

Then select the “pixel” Event Category, followed by the “open” Event Action.

Finally, select the “Secondary dimension” dropdown, select “Time” and pick “Hour of Day”.

This will add a secondary column with values such as 2021072216 which translates to YYYYMMDDHH (Year Month Date Hour).
In this case, the pixel was loaded between 4 (16) and 5pm on the 22nd of July, 2021.

See the following screen capture for more details:

Leave a Reply

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