Anthony McLin

Busting Gmail image caching with build scripts

Recently Google added image caching to Gmail.

This is a good thing for email service providers, because it will dramatically reduce their hosting costs as an image will only need to be downloaded once to be used by all Gmail users. For email developers though, this is Bad with a capital B.

You test your email, you replace an image, and send the final blast. If you opened your test email in Gmail (which you really should be doing for testing purposes) then you've populated the cache with your original image. Gmail users won't see the new image you inserted as a replacement.

"So what? I'll just rename the image to image_v2.jpg and update my HTML."

Well you could. And keep doing that every time you change an image, for every email, for the rest of your now shortened life. Sounds tedious doesn't it?

"Isn't there a better way?"


You can add a cache-busting query string to the source URL of your image, so that you don't have to rename the image every time. For example:

Instead of renaming the image each time you just add ?vXXX where XXX is a version number. This way, each revision of your image has a unique URL and you bypass the Gmail caching. Note that each revision will still be cached for everyone that receives that revision.

"Ugh, that's slightly easier.
But I still have to modify my email each time. Is there a lazier way?"

Glad you asked.

Time to introduce a preflight script to your project. The most reusable flexible way to do this is with a build tool like Ant, Grunt, or Maven. I'm going to use Ant because it's pretty straightforward, is included on OSX, and doesn't require any additional dependencies.

Consider the following project structure:

  • My Project Folder
    • source
      • email1.html
      • email2.html
      • email3.html
      • image1.jpg
      • image2.jpg
      • image3.jpg
    • build.xml

All you have to do is open up terminal, cd to the directory of your project and type "ant compile" to automatically run a cleanup script for all the files in your project.

The following ANT build.xml file will copy all the files from source into a new folder called "build" and will append a cache-busting string to any png, jpg, or gif references in the source.



		Deleting output directory

		Copy files from ${dir.source} to ${dir.output}

   		Appending cache-busting string ${cachebust} to files in ${src}

Let's step through what's going on here when I run ant compile from the command line:

  1. The compile target starts and immediately fires be the clean target because it depends on that.
  2. The clean target deletes the build directory
  3. The compile target then runs the prepare target
  4. The prepare target copies all files from source to build
  5. The compile target then runs the bustcache target, providing the build directory and the cachebusting string as parameters.
  6. The bustcache target appends the cachebusting string v3 onto every reference of .jpg, .jpeg, .png, and .gif in the HTML in the build directory.

That's it. Pretty simple and clean right?

"But I still have to change the version number, v3, every time I run this. Isn't there a lazier way?"

Now you're thinking!

But what's the best way to do this?

We could use random values. It would work, but doesn't give you much diagnostic information when you look at the output emails later. We could use a date string or timestamp, but that doesn't tell you what revision your file was at when you did the compile. A better option would be to use the revision number from your version control system. You are using version control like Git, Mercurial, or SVN, right? Right??

So here's how we can get the version number from Mercurial (Hg) and use it in the build script.

   		Current Hg revision is ${}

We also remove the property from the top of the script, and add this new target as a dependency for the compile target. Here's the resulting build.xml:



		Deleting output directory

		Copy files from ${dir.source} to ${dir.output}

   		Appending cache-busting string ${cachebust} to files in ${src}

   		Current Hg revision is ${}

"But wait! I don't use Hg; I'm using SVN or Git"

No problem. Here's the final integrated script with support for Hg, Git, and SVN. All you have to do is set the source.control property at the top of the file to indicate which version control system you are using.



		Deleting output directory

		Copy files from ${dir.source} to ${dir.output}

   		Appending cache-busting string ${cachebust} to files in ${src}

		Matching source control setting. Set to ${source.control}

   		Current Hg revision is ${}

   		Current Git revision is ${}

   		Current SVN revision is ${}

"This is amazing! Can the build system do more?"

Take a look at my email-builder project on GitHub. It includes this cache-busting as well as some other cool helpers to make email production less tedious.

Download Files: 

Add new comment