Win back the customers who are already returning

The outcome: a short list of former customers who are on your site right now, and a reason to contact each one this week rather than next quarter.

Who this is for: anyone sitting on a lapsed-customer list they have not emailed because they know a blast would burn it.

Time: an hour to build, five minutes a week to run.


Before you start

  • You need an API key with BOTH scopes — read for the visitor list in Step 2, copilot for the questions in Steps 1, 3 and 6. Scopes are chosen at creation and fixed, so ask for both up front rather than minting twice.
  • You need the pixel live.
  • You need a connected store, because the lapsed half of this comes from your orders.
  • Useful but not required: a connected email platform, if you want the list pushed rather than read.

Your store already knows who lapsed. That is not the hard part.

Every commerce platform can give you "customers who have not ordered in 90 days". Most teams have that list. Almost nobody emails it, and the ones who do usually regret it, because a lapsed list is mostly people who lapsed for a reason and a blast to all of them is how you teach a mailbox provider that your domain sends mail nobody wants.

The question that changes the economics is not who lapsed. It is which of them is looking again right now. That one is invisible in your store, because a former customer browsing anonymously leaves no order and no session you can tie to their name.

That is the join this playbook is about: your order history knows who they were, Signal knows who is on the site today, and the overlap is the only part worth acting on.

The numbers, measured, so you size this correctly

Run on a real workspace on 2026-09-17:

Lapsed customers (no order in 90 days)8,926
Identified visitors on the site67,494
Lapsed customers ever identified on the site166
…of those, seen in the last 30 days33

Look at the last row before you decide this is too small to bother with.

33 is the number you can act on well. Eight thousand is a number you can only act on badly. The entire value here is that the list is short enough to write a real sentence to each person, and current enough that the sentence is about something they did this week.

And be clear about why the overlap is 166 and not 8,926: identity resolution catches a fraction of traffic, not all of it. Most lapsed customers who visit are never identified. That is the honest ceiling of this play, it is not going to improve much by tuning, and it is still the best-timed list you will get.

Step 1 — Establish the lapsed set, on your terms

"Lapsed" is a business definition, not a technical one, and the right window depends on your repurchase cycle. A coffee subscription lapses in six weeks; a mattress does not lapse in two years.

{"question": "How many distinct customers bought in the last 90 days versus the 90 days before that, and what is the repeat purchase rate?"}

Use the repeat rate to pick the window rather than defaulting to 90 days because it is round. If most repeat orders land within 40 days, then 90 days is already deep into "gone" and you are being too patient.

Step 2 — Find which of them are back

This step is a join you run, not a question you ask. Signal has no tool that returns it, and that is not an oversight — only about 0.5% of identified visitors carry order data (350 of 67,656 on the workspace measured above), so an endpoint that tried to do this join inside Signal would return almost nothing and look broken. The two halves live in two systems and you own the join.

Half one, from your store or ESP: customer email and last order date. Every commerce platform exports this; it is the lapsed list you already have.

Half two, from Signal: who has been identified, and when they last visited.

curl -s "https://app.signal.geysera.com/signal-api/v1/visitors?page_size=200" \
  -H "Authorization: Bearer $SIGNAL_API_KEY"

Page through it — total, page and page_size come back in the envelope — and keep resolved_email and last_visit_at.

The join, in whatever you already use:

lapsed = {          # from your store: email -> last order date
    r["email"].lower(): r["last_order_at"]
    for r in store_export
    if r["last_order_at"] < ninety_days_ago
}

back = [
    v for v in signal_visitors
    if v.get("resolved_email")
    and v["resolved_email"].lower() in lapsed
    and v["last_visit_at"] > thirty_days_ago
]

back is the list. Expect it to be short — that is the point of the table above, not a sign you did it wrong.

Two things to get right. Lowercase both sides before comparing, or you will lose matches to capitalisation that means nothing. And do not widen the window to make the number bigger: a longer window buys you people who are not actually back, which is the whole thing this play is avoiding.

Step 3 — Read what they looked at, one at a time

This is the step people skip, and it is where the return on the whole exercise sits.

{"question": "What pages has dana@acme.com viewed, in order, and when?"}

A former customer on your pricing page is a different email from a former customer reading a support article. One is reconsidering, the other has a problem with the thing they already bought. Sending the reconsidering email to the second person is worse than sending nothing.

Thirty-three people is few enough to do this individually. That is the argument for the short list restated: it makes a five-minute-per-person step affordable.

Step 4 — Write to the behaviour, not to the segment

The email that works here is short and specific and does not mention that they lapsed. "We noticed you have not ordered in a while" tells someone you have been watching and have nothing to say.

What you have instead is a reason to write today: they looked at something. Reference the category, not the page view — "you were looking at the refill packs" is helpful; "you visited /products/refill-6pack at 14:32" is unsettling and will cost you more than the order is worth.

Draft it in your own model, not in Signal. Signal answers questions about your data; it has no drafting tool, and asking it to write copy is refused rather than attempted — which is the right boundary, but it means the drafting step belongs on your side.

The pattern that works: take the visitor's page history from Step 3, paste it into whatever model you already use, and ask for the email there. The LLM tools workflow wires both halves into one loop, with Signal as the reader and your model as the writer.

Then read it before it goes. Every time.

Step 5 — Decide what you are actually offering

A discount is the reflex and usually the wrong first move. You are writing to someone who already bought at full price once; leading with money teaches them to wait for the next discount, and trains your best segment into your worst.

Better first moves, in rough order of how often they work:

  • Answer the question they were researching. They were on a comparison page for a reason.
  • Tell them what changed since they last bought, if anything did.
  • Make reordering trivial — the friction is often the whole reason.
  • Then, if none of that lands, consider an offer.

Step 6 — Measure it as a cohort, not as a campaign

Open rates will look excellent, because the list is tiny and well-targeted. That number will teach you nothing.

The question is whether these people ordered:

{"question": "How many distinct customers bought in the last 30 days, and how many of those were returning rather than new?"}

Run it before you start and again a month later, and look at the returning count. If you want to know whether the campaign caused the change rather than coincided with it, that is a different question and there is exactly one tool for it — see Prove a change actually worked.

Run it weekly, not once

The list regenerates. Someone who lapsed and came back this week was not on last week's list, and the reason this works is timing — a week late is most of the value gone.

This is a good standing job for an agent: once a week, pull the overlap, drop anyone already contacted, and put the rest in front of a human with what they read. See Give Signal to any LLM as tools for the loop, and keep the send manual until you have watched it be right several times.

What will go wrong

The list will be smaller than you hoped. That is identity resolution, not a configuration error. Thirty-three well-timed emails will out-earn eight thousand untimed ones and it will still feel wrong.

Someone will want to email the whole lapsed list anyway. The measured overlap is the argument against it: you have evidence that 0.4% of them are demonstrably back, and no evidence about the rest.

A consumer-mailbox workspace will see personal dominate. Consumer addresses roll up under that sentinel, so filter it out of anything you are treating as company-level, and do not read it as one enormous account.

Someone will reply "how did you know I was looking?" Have an answer ready that you are comfortable saying out loud. If you are not comfortable saying it, that is worth knowing before you send, not after.


Next

Markdown source: /developers/playbooks/win-back-the-customers-who-are-already-returning.md