← All essays

Engineering · Jun 2026 · 4 min read

Designing around the sixty second Gmail cache.

Gmail refetches dynamic endpoints roughly every minute. What that does to product grids, inventory badges and live pricing, and how to design a data shape that survives it.

Gmail refetches AMP endpoints roughly every sixty seconds within a session. That single number decides the shape of every dynamic email that goes through it.

Sixty seconds is fast enough to feel live. It is not fast enough to be live. And it is fast enough to eat an origin server that was not designed for it.

The interesting engineering happens in the space between those three facts.

What sixty seconds means for the render

When a Gmail recipient opens a dynamic email, the amp-list, amp-src and amp-form endpoints are called immediately. If they open the email again inside the same session, the endpoints are called again if more than sixty seconds have passed. The cache is per user, per session, per endpoint.

The practical effect is that a product grid can be stale by a minute at any moment. An inventory badge that says "3 left" can be based on a snapshot from just before the last refetch. A price can lag the site by up to a minute.

For most retail use cases this is fine. The design failure mode is the case where a minute of drift produces a wrong answer that the recipient acts on. A "reserved for you" widget that says the seat is still available when it was booked forty seconds ago is a bad moment for everyone.

The origin problem

A sent email is a synchronized event. The whole audience opens in a burst that follows a predictable curve, front loaded in the first ten minutes. If your dynamic endpoint is a naive database read, it will feel that burst at the moment the audience does.

The naive read pattern breaks first at scale. Ten thousand recipients times sixty second cache misses over the first hour is a hundred thousand hits on your endpoint. That number is polite compared to what a real send can produce.

The good news is that Gmail respects HTTP cache headers. Set a sensible Cache Control max age, and Gmail's edge honours it. That single header often removes ninety percent of the load. The ten that remains is the burst that arrives inside the cache window, and that one has to be handled at the origin.

TTL bucketing

A refinement that matters at large audiences: do not let every recipient's cache expire at the same second. Add a small jitter to the cache TTL, or bucket recipients into cohorts that hit slightly different endpoints. The origin sees a smoothed load curve instead of a set of sixty second spikes.

The pattern is simple. The endpoint responds with a Cache Control max age between fifty and seventy seconds, seeded from a hash of the subscriber key. Every recipient still sees fresh data within a minute. The origin sees a load pattern shaped like a hum instead of a cough.

A cache friendly data shape

The endpoint that scales is the one that returns a materialized view, not a live join across five tables. Build the materialized view on a schedule that matches your business tolerance for staleness. Refresh every minute if you need to. Refresh every fifteen if you can. The endpoint reads a single row keyed on the subscriber, and returns.

Deduplicate at the source. If a thousand recipients would hit the endpoint with the same product IDs, serve them the same payload. A cache in front of the origin, keyed on the payload signature, collapses that fan out into a handful of underlying reads.

The endpoint you do not want to build is the one that assembles the payload from scratch for every recipient. That design worked when the send was a broadcast that no one opened at once. It does not work when the send is a broadcast that everyone opens at once, and every open triggers a fresh request every minute.

Serve the stale case gracefully

Every dynamic endpoint should have a fallback state. If the origin is slow, if the data is stale beyond a reasonable window, if the endpoint returns anything other than a clean response, the AMP part should render a defensible fallback rather than a broken widget. amp-list has a fallback slot for exactly this. Use it.

The corollary is that the HTML fallback part of the multipart message should also carry a reasonable answer. If the AMP endpoint fails for a recipient in Gmail, they see the HTML. If the HTML has a stale price from send time, that is a better outcome than a spinner that never resolves.

“Design for the burst, respect the cache, and let the stale case fail loudly enough to notice.”

Keep reading

Engineering · Aug 2026

SSJS and external APIs: the token hygiene checklist.

Engineering · Aug 2026

AMPscript error handling: catching the silent failures.