Posts

  • How to Track Email Opens in Rails

    So you want to track email opens? Well, there’s a hack to get up and running with Google Analytics, but personally, I’d like to have more control over where the data is recorded (e.g. send it to Mixpanel or something). There are email marketing services that will track email opens automatically, but they can be pricey, and again, I’d like to have more control over that data. Then there are beacon images, a technique that I’ve found to be ill-documented, that I will describe in full in this post. First, we’ll cover the problems with a naive implementation, and by the end, you’ll have a robust Rails system that will use a 1x1 pixel beacon image to track email opens.

    A first cut will look something like this: you create an endpoint, e.g. https://www.example.com/beacon (some sites tell you that you need a .png extension, but I didn’t use one and it still worked). This endpoint will track the email open by recording an entry in the database, and returning a 1x1 pixel, transparent PNG.

    To begin, we’ll create a model that represents an email being opened, EmailOpen:

    $ bundle exec rails generate model EmailOpen email:string ip:string

    Then add the following to your config/routes.rb:

    get '/beacon' => 'tracking#beacon'

    Next, you’ll need to download your beacon image. I’d recommend 1x1px.me – save the file under public/beacon.png. Then create a controller, TrackingController, under app/controllers/tracking_controller.rb, with the following:

    class TrackingController < ApplicationController
      def beacon
        EmailOpen.create(email: params[:email], ip: request.ip())
        send_file 'public/beacon.png', type: 'image/png'
      end
    end

    Congrats! You have a basic email tracking system. Only one problem: when someone is composing their email in, say, Gmail, the image will be loaded while the user is composing their email. Oh no! How are we supposed to ignore these requests, while tracking legitimate email opens?

    Cookies to the rescue! I realized that when your browser makes a request for the beacon image, it’s sending along the cookies for your site. That means you can check whether the user is signed in. So, your controller will look something like this:

    class TrackingController < ApplicationController
      def beacon
        unless current_user
          EmailOpen.create(email: params[:email], ip: request.ip())
          send_file 'public/beacon.png', type: 'image/png'
        end
      end
    end

    But wait, that won’t quite work – if your email recipient is logged into your site, the controller will erroneously ignore that user. We can do better. In the case of YesInsights, we were experimenting with a Chrome extension that would include a beacon image in Gmail messages. We were able to add a survey_id parameter, that will allow us to check whether the user is the owner of the survey. In your HTML, you’ll want something like the following:

    <img src="https://www.example.com/beacon?survey_id=1">

    You’ll want to replace the survey_id parameter with whatever parameter you use to separate users that are sending emails from others, and provide that HTML as a copy-able snippet. Then, in your controller, you’ll need something like the following. Again, replace Survey with the model you’re actually using:

    class TrackingController < ApplicationController
      def beacon
        if current_user
          if params[:survey_id]
            survey = Survey.find(params[:survey_id])
            unless current_user == survey.user
              EmailOpen.create(email: params[:email], ip: request.ip())
            end
          else
            EmailOpen.create(email: params[:email], ip: request.ip())
          end
        else
          EmailOpen.create(email: params[:email], ip: request.ip())
        end
    
        send_file 'public/beacon.png', type: 'image/png'
      end
    end

    That should do it! Your app will now be able to differentiate between users who are composing emails, and users who are opening emails. From here, you could add more parameters to the beacon image request, to track even more info on the user.

    This code came from an experiment at YesInsights (all code here is freely available). If you’re sending out emails to gather feedback, and you’re not getting responses, you should seriously try a one-click survey. Your recipients will be able to respond to your survey by simply clicking a link in the email, and they can leave a comment too (from what we’ve seen, about 50% of respondents do). As a developer, it’s dead simple and incredibly useful. To start getting more feedback, try YesInsights today.

subscribe via RSS