EmailTimer.App

Countdown timers and personalized images for SendGrid email

Your subscribers see how long is left on the offer, counted from the moment they open the message, and it goes into the SendGrid campaign you have already built. One snippet, in the merge syntax SendGrid already uses.

Animated countdown in the digital style, reading 02 days 03 hours 34 minutes 12 seconds and counting down
A render from the engine at 600 by 200, the size that goes into a SendGrid image block.

SendGrid merge syntax

The tags you already use, around the image

The same merge fields that put a subscriber's name in your subject line go in the link and the alt text around the image, where SendGrid fills them in as it sends. They never go inside the image address, for the reason in the warning further down. A name or a deadline drawn inside the image itself comes from an address signed for each subscriber, held in a contact field.

First name
{{first_name}}
Coupon code
{{coupon_code}}
Deadline
{{deadline_date}}
Email address
{{email}}

Checked against SendGrid's own documentation: Adding Dynamic Content with Handlebars in Marketing Campaigns (Twilio SendGrid Docs) and Using Handlebars (Twilio SendGrid Docs), read on 17 September 2026.

The snippet to paste into SendGrid

Put it in sendgrid design editor code module or code editor. There are two shapes, and which one you want depends on whether every subscriber gets the same deadline or their own. The editor generates whichever you pick, already filled in with your ids.

One thing to know before you paste anything. A merge tag cannot go inside the image address. The address is signed, and the signature covers every part of it, so if SendGrid swaps a name into it after signing, the signature no longer matches and every subscriber gets the fallback graphic instead of your timer. It happens quietly, because the image service always answers with a valid picture rather than an error. Both snippets below put the merge tags in the link and the alt text, where they belong and where they work.

One image for the whole send

On every plan, including the free one. Everybody requests the same address, and the clock is worked out at the moment each person opens, which is what lets one address show a different time to every reader. The name and the code go in the link and the alt text.

<a href="https://yourstore.com/sale?code={{coupon_code}}" target="_blank" rel="noopener">
  <img src="https://img.emailtimer.app/i/k7mQ2pXd.gif?w=w_8ha3fq&v=3&sig=YOUR_SIGNATURE" alt="{{first_name}}, your 25% off ends at midnight tonight" width="600" height="200" border="0" style="display:block; width:100%; max-width:600px; height:auto; border:0;" />
</a>

A different image for each subscriber

On Growth and Agency. Generate one signed address per subscriber, import the column into SendGrid as a contact field called et_image_url, and point the snippet at that field. This is the only way a deadline that differs per subscriber can work, because the deadline is inside the signature too. A contact whose field is empty gets no image, so check the field is filled across the whole segment before you send.

<a href="https://yourstore.com/sale?code={{coupon_code}}" target="_blank" rel="noopener">
  <img src="{{et_image_url}}" alt="{{first_name}}, your 25% off ends at midnight tonight" width="600" height="200" border="0" style="display:block; width:100%; max-width:600px; height:auto; border:0;" />
</a>

The addresses come from the batch endpoint in the REST API, or from a CSV export if you would rather not write code.

Setting it up in SendGrid

Four steps, and the longest one is deciding what the timer should look like.

  1. 1

    Design timer in EmailTimer.App

    Select your preferred timer geometry, fonts, colors, and fallback image.

  2. 2

    Copy SendGrid HTML snippet

    Receive HTML with standard SendGrid Handlebars tags like {{first_name}} and {{email}}.

  3. 3

    Paste into SendGrid Design Editor

    Drag a Code module into your SendGrid layout and paste the embed HTML snippet.

  4. 4

    Preview and send test

    Use SendGrid Preview panel with recipient sample data to confirm rendering.

Things that only matter in SendGrid

SendGrid Marketing Campaigns uses Handlebars tags {{first_name}}. When sending via the SendGrid Mail Send REST API, supply the dynamic template data object with matching keys.

What your SendGrid subscribers will actually see

Outlook desktop will render the static first frame of the countdown GIF. EmailTimer.App renders the current remaining time on frame 1 to keep information accurate.

What each client is documented to do

Email client Fetch moment Cache lifetime GIF animation What EmailTimer.App does Basis
Apple Mail (iOS 15+, macOS Monterey+) Apple Mail Privacy Protection (MPP) Delivery time (pre-fetched by Apple server) Testing reports 2 to 3 days Full 30-second live animation The timer counts down from the time remaining at delivery. A fixed deadline, such as 23:59 UTC, is still the right deadline. Documented by Apple
Gmail (web, iOS, Android) Google image proxy First open by recipient Not published by Google Full 30-second live animation First open displays a real-time countdown. A re-open can be served from the copy the proxy already holds. Documented by Google
Outlook on Windows (2016 to 2024) Word rendering engine Open time Not documented First frame only (static image) Frame 1 is a complete image with the time left at the fetch, so a reader who sees only that frame sees a correct still countdown. Expected, not yet documented or tested
Outlook on the web and mobile (iOS, Android) Native browser or app engine Open time Not documented Full 30-second live animation Expected to play the animation, 30 frames at one frame per second by default. Expected, not yet documented or tested
Yahoo Mail and AOL Yahoo image proxy Open time Not documented Full 30-second live animation Expected to fetch the image when the message is opened and play the animation. Expected, not yet documented or tested

The Apple and Gmail rows rest on Apple's and Google's published documentation and on third-party testing. Neither company publishes a cache lifetime: Apple documents the prefetch, Google documents the proxy, and the two to three days is FreshInbox's 2021 testing. The Outlook and Yahoo rows are what we expect those clients to do, with no vendor document behind them. No row comes from a test by us yet, and we will publish dated screenshots when we have them. Sources: Apple on Mail Privacy Protection, Google on the Gmail image proxy, FreshInbox on Mail Privacy Protection image caching.

SendGrid in practice

SendGrid countdown timer setup guide

By Danish Mohammed, founder. 6 min read

In SendGrid, a countdown timer can go in a dynamic template sent through the Mail Send API, or in a Marketing Campaigns design. Either way, the work is in deciding where the image address comes from, and that depends on whether every recipient shares one deadline.

Does SendGrid have its own countdown timer

I could not find one. SendGrid’s documentation for its two email editors and for dynamic templates describes no timer module. A countdown in a SendGrid email is an image served from elsewhere.

Two ways to wire it

The same moment for everyone. Publish the timer, copy its signed image address, and put it straight into the dynamic template. No API call happens per send, and the free plan covers it. The time left is drawn when each mail client fetches the image, so a fixed order cut-off stays right all day. A transactional email sent at 09:00 and one sent at 17:00 both show the correct time left.

A different moment per recipient. Each recipient needs their own signed address, made before the send and passed in with the rest of their data. The address carries one of two things. An address exported from a recipient set carries an opaque recipient token, and the renderer looks the deadline up from it. An address minted by POST /urls/batch with a deadline param carries the date itself, signed and readable in the URL. Minting those addresses takes the REST API or a recipient set, and Growth is the first plan with either.

Where the address goes

SendGrid’s dynamic template guide says the Handlebars variables “point to the JSON keys in your dynamic_template_data object”. So the per-recipient address travels as one more key in each personalization:

{
  "from": { "email": "billing@yourapp.com" },
  "template_id": "d-your-template-id",
  "personalizations": [
    {
      "to": [{ "email": "sam@example.com" }],
      "dynamic_template_data": {
        "first_name": "Sam",
        "et_image_url": "https://img.emailtimer.app/i/k7mQ2pXd.gif?w=w_8ha3fq&v=3&r=tok_3fJq9xYw2LmPa8Rt&sig=YOUR_SIGNATURE"
      }
    }
  ]
}

The template then reads that key as its src:

<a href="https://yourapp.com/billing" target="_blank" rel="noopener">
  <img src="{{{et_image_url}}}" alt="{{first_name}}, your trial ends soon" width="600" height="200" border="0" style="display:block; width:100%; max-width:600px; height:auto; border:0;" />
</a>

Pass the address in finished, never as pieces the template joins together. An HMAC signature covers every parameter in it, so a template that assembles the address, or drops a date into it, produces one the image service will not accept. The service still answers every recipient, with a grey “image unavailable” card and HTTP 200, so nothing on either side reports a failure.

Double braces or triple

SendGrid’s Handlebars reference quotes the Handlebars documentation on the triple-stash, {{{ }}}, which outputs a value without escaping it. Its own example of a link built from data uses double braces, as <a href="{{ url }}">.

A signed address contains ampersands. Escaped inside an attribute, they are decoded again by the mail client’s HTML parser, so the double-brace form should resolve correctly. The triple-stash removes the question entirely, and for an image address it is what I would use. The alt text can stay in double braces.

Minting the addresses before the send

There are two routes, and both happen outside SendGrid.

The first starts from a recipient set. Upload a CSV with each recipient’s values through POST /recipient-sets/{setId}/upload, then stream one signed address per row back as CSV from POST /recipient-sets/{setId}/urls. The import error report is per row, so a file with three bad dates says which three.

The second is POST /urls/batch, which mints up to 10,000 signed addresses in one call. Minting changes nothing in the workspace, so a read-scoped key is enough. The full reference is on the REST API page.

Plan the numbers on both sides. Most API routes here allow 60 requests a minute per key, and the two bulk routes allow 600. SendGrid’s Mail Send reference caps the personalizations array at 1,000 items per request, so one batch call here covers ten full Mail Send requests.

What not to do is mint one address inside the request path of each transactional send. The single-URL route allows 60 requests a minute, and a signup spike can pass that in seconds. Mint ahead, store the address beside the user record, and read it when the email is built.

Marketing Campaigns

Marketing Campaigns reads contact fields rather than a JSON payload, and its tags use double braces too. The editor guide lists {{first_name}} and {{email}} among the reserved tags, and says custom fields appear in the Tags tab of either editor. Import the signed address into a custom field such as et_image_url and write the image as src="{{et_image_url}}".

Two things there are undocumented, so check them with a real send. The editor guide does not say whether a substitution tag resolves inside an img attribute. And the triple-brace form above comes from the Handlebars reference for dynamic templates, which I would not assume carries over to Marketing Campaigns.

SendGrid documents one way to set a default, {{insert first_name "default=Valued Customer"}}, and warns that “For contacts with no entry in a custom field, the substitution tag appears blank.” A blank src is a missing image, so check the field is filled for every contact in the segment. That default form carries double quotes, which would close a double-quoted attribute, so keep it out of alt and src.

On editors: the code editor, in SendGrid’s words, “doesn’t add excess code or modify your code”. The design editor builds from drag-and-drop modules, and SendGrid says you can edit their HTML. For a snippet you want delivered exactly as written, the code editor is the safer of the two.

What your recipients will see

Once SendGrid delivers the message, the mail client decides what happens. Microsoft’s support page says “If only the first frame of the animation appears, you have animations, or animated GIFs, disabled.” That first frame is drawn with the real time left in it, so an Outlook reader who never sees the animation still reads the right figure.

Google’s Workspace documentation says “Gmail uses Google’s secure proxy servers to serve images”, so a later open can be answered from a copy the proxy holds. How far that copy drifts depends on its age, which matters less for a cut-off counted in hours than for one counted in minutes. The email client support page covers the other clients, from their documentation rather than from an inbox test, which I have not run yet.

Test before you send

SendGrid’s editor guide says “Both editors provide a data preview feature”, so you can “use test data and view the result in the editor’s preview pane”. Put a real signed address into the test data as et_image_url, not a placeholder, so the preview has a real image to fetch.

Then send one message to yourself through Mail Send, with the same payload shape your code will use. The preview shows the template resolving. The delivered copy shows what a mail client’s proxy and cache do with the image, which is the part no preview can tell you.

Questions and answers

Does SendGrid have a built-in countdown timer?

Not that I could find in its editor or dynamic template documentation. A timer in a SendGrid email is an image served by a timer service.

Can I put a deadline variable into the timer image URL?

No. The address is signed, so a value substituted into it after signing breaks verification for every recipient. Pass a complete signed address as one key in dynamic_template_data.

Do I need the API for a SendGrid countdown timer?

Not for one deadline shared by every recipient. Paste the published address into the template, which works on the free plan. You need the API or recipient sets, on Growth or Agency, only when each recipient’s deadline differs. What each plan includes is on pricing.

How many signed URLs can I mint for one SendGrid send?

Up to 10,000 per batch call, at up to 600 calls a minute per key. SendGrid accepts up to 1,000 personalizations per Mail Send request.

Should the image source use double or triple braces?

In a dynamic template, triple braces, {{{et_image_url}}}, pass the address through unescaped, which settles any doubt about the ampersands. SendGrid’s Handlebars reference quotes the Handlebars documentation on the triple-stash. In Marketing Campaigns, use double braces and check a real send.

Sources

Related

Campaigns to build in SendGrid

Each playbook says which timing model the campaign needs, which snippet it takes, and what to get right before you send it.

Setting this up on another platform

The snippet is the same everywhere. Only the merge syntax changes, and each page below carries the exact syntax that platform expects.

All 21 platform setup guides

Put a timer in your next SendGrid send

Free for 5,000 opens a month, $19 for 250,000, and the snippet above is the whole integration.