Email Analytics for Multi-Store Shopify: The Two Windows We Attribute On
Email analytics multi-store Shopify: why we attribute revenue on a 5-day last-touch and a 14-day click window, and why open rate decides nothing.
Key points — AI summary
- We attribute email revenue on two separate windows — 5 days for last-touch (an engaged email before the order) and 14 days for a deterministic on-site click token — because collapsing both into one number hides which signal earned the credit
- Open rate is not a decision metric since Apple's Mail Privacy Protection auto-opens mail; we still render it, but flagged as inflated, and route every decision to clicks and revenue-per-recipient instead
- The benchmark lines we shipped come from Klaviyo's 2026 ecommerce set plus portfolio studies, not from our own stores: flow revenue-per-recipient ~$1.58 vs campaign ~$0.08, roughly a 20× gap that argues for fixing flows before writing another campaign
- Two days of building this surfaced the boring failures that actually cost time — a BigQuery reserved word breaking an RFM query, and a date upper bound that silently dropped the last day of every range
- Aggregation runs for every store while the read UI stays gated to one account, so the data accrues from day one and the rollout risk stays on reads
Summarized from this article by our writing pipeline; reviewed by the author.
On this page
We spent 14 and 15 July 2026 building email analytics into the system we use to run our Shopify stores. Not a spike — the Overview redesign, money metrics, link-level click analytics, RFM segments, and a benchmark layer, shipped in phases over two days. This post is about the one decision that took the longest to get right, and it wasn't a chart: it was deciding which order gets credited to which email, and for how long.
If you run one store, your email platform answers that for you and you never think about it. Run several and the question turns sharp, because each platform answers it differently and none of them agree. Our Gmail and Outlook management setup already pooled the sending side across stores; attribution is where pooling gets genuinely hard.
Two windows, because one number lies
We ended up with two attribution windows, deliberately not merged:
ATTRIBUTION_WINDOW_DAYS = 5 // last-touch: an engaged email before the order
ONSITE_ATTRIBUTION_WINDOW_DAYS = 14 // deterministic: a click token that survives to checkout
The 5-day window is last-touch. An order is attributed to the most recent engaged email sent to that address within five days before the order. It's a correlation, not a proof: the buyer opened or clicked something, then bought. Five days is short on purpose — it has to stay well under the retention of the email log itself, or the matching record is gone before the order lands and the credit silently disappears.
The 14-day window is different in kind. It's a deterministic click→order signal: a token (sf_e) set when someone clicks through from an email, read back at checkout. That's a real chain of custody, not a coincidence, which is why it earns a longer window. But it's still bounded, and the reason is unglamorous — a stale token sitting in localStorage will happily credit a purchase weeks later to an email that had nothing to do with it. Fourteen days is our answer to "how long before this stops being evidence."
The temptation is to add them into one Email Revenue number. We didn't, because the two numbers fail in opposite directions: last-touch over-credits email that happened to be nearby, deterministic under-credits anyone who clicked on their phone and bought on a laptop. Showing both, separately, is the honest version. A single blended figure would be more comfortable and less true.
Open rate is not a signal, and we shipped it anyway
Our benchmark set flags open rate as inflated rather than hiding it:
openRate: 0.4, // MPP-inflated — informational only
clickRate: 0.019,
ctor: 0.1,
Apple's Mail Privacy Protection pre-fetches images, which auto-opens mail whether or not a human looked at it. An open rate near 40% is therefore a measurement of Apple's proxy, not your subject line. We render it because marketers look for it and its absence reads like a bug — but it carries a flag, and every decision surface routes to clicks and revenue-per-recipient instead.
One caveat I want to be explicit about: those benchmark numbers are not our results. They're reference lines we encoded from Klaviyo's 2026 ecommerce benchmarks plus portfolio studies (BS&Co, Flowium), so a number has something to sit against. The most decision-relevant one is the split between flows and campaigns:
campaignRpr: 0.08, // revenue per recipient, campaign
flowRpr: 1.58 // revenue per recipient, flow
Roughly a 20× gap. If that ratio holds anywhere near your stores, the implication is blunt: an afternoon spent fixing a broken abandoned-cart flow beats a week of campaign sends. We built the campaign compare view anyway, because you still need to know which campaign died — but the flow/campaign RPR split is the number we put where the eye lands first.
We also benchmark complaint rate at 0.001. That one isn't a nice-to-have: sustained spam complaints above ~0.1% is Gmail and Yahoo enforcement territory, and for multi-store senders the blast radius is every store on the sending domain, not just the one that sent the bad campaign.
What actually cost us time
Not the dashboards. The two failures worth naming:
A reserved word. Our RFM query aliased a column as at. BigQuery treats at as reserved, so the query failed — a five-minute fix (at → last_at) that only surfaced after the segment logic looked done. The lesson isn't about BigQuery trivia; it's that we'd written the aggregation before running it against real data, which is a habit worth breaking.
A date boundary that dropped a day. Link-level click and send-timing queries used an upper bound that excluded the final day of any range. Nothing errored. The charts rendered, the numbers were plausible, and every range was quietly missing its most recent day — the day a marketer is most likely to be checking. We widened the bound by one day. Silent wrongness is worse than a stack trace, and analytics code is where it hides best.
There's a cost dimension too. Link-level click analytics on BigQuery means per-query scanning, so the click phase shipped with cost controls attached rather than as a follow-up. Scan-per-question is fine for one store and a bad surprise across a portfolio, which is the same trap our multi-store reporting and exports piece runs into from the other side.
Rolling it out to exactly one person
The read UI and the /api/email/analytics/* endpoints are gated to a single account right now — mine. Aggregation is not gated: the webhook and daily rollups run for every store from day one.
That split is intentional and I'd repeat it. Data has to accrue before a dashboard is worth opening; if you gate collection, your first user gets an empty chart and decides the feature is useless. Gating reads means the rollout risk sits entirely on who can look, while history builds quietly underneath. The cost of being wrong about the UI is a redesign. The cost of being wrong about collection is a month of data you can never backfill.
What I'd tell someone starting this
Decide your attribution windows before you build a single chart, and write down why each number is what it is. Ours are 5 and 14, and neither is a best practice I found in a blog post — 5 is bounded by log retention, 14 is bounded by how long a click token stays credible. Your constraints will differ. What won't differ is that a dashboard whose attribution rules live only in someone's head becomes unarguable: two people read the same Email Revenue number and mean different things, and there's no way to settle it.
And if you take one number from here, take the flow/campaign RPR split — not because ~20× is a promise, it isn't, but because it's the only benchmark in the set that changes what you do on Monday. Open rate has been decoration since MPP shipped. We render it with a warning label, and I'd have been happier deleting it. For where this fits against the rest of the numbers we track, the KPI set for multi-store Shopify is the wider map.