Email tracking techniques

Sep 16, 2021 by Zacharia | 146 views

https://cylab.be/blog/173/email-tracking-techniques

Ever wondered how a company can track the opening of the emails it sends to its customers? Here you will learn to achieve that without using any third party email tracking service. All the techniques will be illustrated through Laravel code since all you need is a web server and a few scripts!

tobias-tullius-4dKy7d3lkKM-unsplash.jpg

1. Pixel tracking

Principle

The first technique consists in injecting an HTML image element into the tracked email. That image must have a src attribute containing a link that redirects to a specific URL on your web server. That URL must contain the information that tracks the user who loaded it through his/her email client. A very simple example of such a piece of information could be the ID of the user. The controller function associated to the specific URL could handle the tracking information it receives and then send back an image that will be shown in the email client. That image is typically a transparent image whose width and height are 1 pixel long, invisible and quick to load, which is why the technique is called pixel tracking. But you can also set it as the header image of your email or any other image.

Implementation

The implementation is illustrated through Laravel but all the solutions can be adapted to any other framework.

Let's assume we have a User model that has as tracked attribute and there is an image file called 1x1.png in our public/images/ folder.

The first component to define is a controller, let's call it TrackingController:

php artisan make:controller TrackingController

In the app/Http/Controllers/TrackingController.php file:

<?php

namespace App\Http\Controllers;

use App\Models\User;
// ... other imports

class PhishingEmailController extends Controller
{

// ... constructor

public function track(Request $request, User $user)
{
    // A simple way to handle the user tracking
    $user->tracked = true;
    $user->save();

    // A simple way to return an image back to the user's email client
    return redirect()->secure(env('APP_URL') . '/images/1x1.png');
}

} 

Then add a route in routes/web.php:

<?php

use App\Http\Controllers\TrackingController;
// ... other imports

Route::get('/track/user/{user}', [TrackingController::class, 'track']);
// ... other routes

Finally, you can construct the tracked email in the function you use to send it. This function can be present in any part of your code, depending on how your app works. Note that we'll use Swift to send the email.

<?php

// namespace

use App\Models\User;
// ... other imports

class MyEmailSender {

// ... constructor

public function sendTrackedEmail(User $user)
{
    $head = '<head>' . /* whatever */ . '</head>';
    $body = '<body'>;
    $body .= '<img alt="" src="' . env('APP_URL') . '/track/user/' . $user->id . '">' ;
    $body .= /* whatever */ . '</body>';
    $html = '<html>' . $head . $body . '</html>';

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('this-is@my.email')
        ->setPassword('7H!5_!5_my_p455w0rd');

    $mailer = new \Swift_Mailer($transport);

    $message = (new \Swift_Message('My Email Subject'))
        ->setFrom('this-is@my.email', 'This is my company name')
        ->setTo($user->email)
        ->setBody($html, 'text/html');

    $mailer->send($message);
}

}

And it is as simple as that! Here the user can only be tracked once, but you can add more data to the image path in order to load more models and customize the way you handle the tracking.

Going further

A way to improve the simple tracking logic showed in the above code is to create a table that contains, for each of its elements:

  • a unique, long enough, random string (or a hash) acting as an identifier (in fact, an attacker who tries to impersonate as your tracked user would have a much harder time to guess it than an integer ID),
  • the ID of the tracked user,
  • the ID of the opened email,
  • the time at which the email has been opened,
  • ...

Therefore the email you send must only contain the random string/hash as data in its tracking URL, instead of putting all the other IDs.

Another way to improve the efficiency of the tracking is to insert the tracking URL in an HTML element that is not an image. It is for example possible to add it in the background attribute of an HTML table element. Here is the code:

// $body .= '<img alt="" src="' . env('APP_URL') . '/track/user/' . $user->id . '">' ;
// replaced by
$body .= '<table background="' . env('APP_URL') . '/track/user/' . $user->id . '"></table>' ;

Like you can see, background is not a CSS style attribute of the table but it is an HTML attribute in itself, like stated above. This trick can be useful to counter email clients blocking remote images by default. Another benefit of using a table is to lower the number of images in the email, that number being an indicator of the spam probability of the email for some spam filtering software like SpamAssassin.

2. Click tracking

Principle

Click tracking works in a way very similar to pixel tracking. More than just knowing if someone opened the email, you can ensure that a user opened it and was interested enough to click on a link you placed in the email. The principle is thus to have the user use the same kind of tracking URL than in the previous technique, but this time the user will see your website page after clicking the URL link. However, you can do whatever you need before showing that website page. It is even possible to retrieve the tracking data from the URL, handle it and then redirect the user to another page of your website, or even to another website. This is what many services, like the URL shortener bit.ly, achieve when you click on the links they provide.

Implementation

Once again, the implementation is illustrated through Laravel but it can be implemented into any other framework.

This time let's assume that we send emails about products we sell. We can have a user_products table, linked to the UserProduct model that contains for each row a userID and a productID that respectively refer to a User model and a Product model. Every UserProduct that is created means that a specific user is interested into a specific product

In the app/Http/Controllers/TrackingController.php file:

<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\Models\Product;
use App\Models\UserProduct;
// ... other imports

class PhishingEmailController extends Controller
{

// ... constructor

public function track(Request $request, User $user)
{
    // function defined above
}

public function trackClick(Request $request, Product $product, User $user)
{
    // A simple way to handle the user click tracking
    UserProduct::create([
        'productID' => $product->id,
        'userID' => $user->id,
    ]);

    // A simple way to redirect the user to the product page
    return redirect()->route('products', ['id' => $product->id]);
}

} 

Let's also consider that we have a ProductController that has a showProduct function that shows a specific product page. Now we can add the click tracking and the product routes in routes/web.php:

<?php

use App\Http\Controllers\TrackingController;
// ... other imports

Route::get('/track/user/{user}', [TrackingController::class, 'track']);

// The two new routes
Route::get('/track/click/product/{product}/user/{user}', [TrackingController::class, 'trackClick']);
Route::get('/products/{product}', [ProductController::class, 'showProduct']);

// ... other routes

The only thing left to do is to add the click tracking link in the sendTrackedEmail(...) function previously defined, considering that it now also has a product argument. The link can be added wherever you want in the HTML body element. You can even let the image/table HTML element so that you can track the opening and the link clicking for the same email. Here is a simple link that you can add in the email:

$href = env('APP_URL') . '/track/click/product/' . $product->id . '/user/' . $user->id;
$body .= '<a href="' . $href . '">Click to see our special product!</a>' ;

You can now track the interests of your users and retrieve some data from it, like the opening/clicking rates, the relations between those two rates, etc.

Going further

A first improvement idea is the same as the one proposed for the pixel tracking technique: changing the data in the URL (here sent through the HTML link element), so that you only need a random string/hash to identify all the information you want to retrieve through the tracking link.

Another idea is to show to the user the URL he/she will be redirected to after having been tracked. Such links can look less suspicious than clickbait messages like "Click to see our special product!". In the example above, this would correspond to the following link:

$href = env('APP_URL') . '/track/click/product/' . $product->id . '/user/' . $user->id;
$redirectedUrl = env('APP_URL') . '/products/' . $product->id;
$body .= '<a href="' . $href . '">' . $redirectedUrl . '</a>' ;

Limitations

Pixel tracking is a passive technique that directly occurs when a user opens an email, which is the action we want to track. But since an email is opened by a user with a specific email client, we have no control about the way our HTML content will be loaded and interpreted by that client. That makes the pixel tracking technique unreliable for some of them, for example if they block remote images.

The click tracking technique requires an active action from the user, which diminishes the chances to track every user. Besides that, an email client can also block all the links contained in an email. But this is less commonly achieved than blocking the remote images.

Even though there exists some limitations, those two techniques remain two excellent ways to non-invasively track your users without requiring the need of any third party service.