Write a first-touch email from what they read
The job: an opener that references something the person actually did, written by a model, checked by a human, sent by your normal tooling.
Who this is for: anyone whose first-touch reply rate is bad because the first touch is generic.
What it costs you: two API calls per person and a prompt you will rewrite four times.
Why this works when "personalisation" usually does not
Most personalisation is a merge field. This is different: you know which pages they read, in order, and how many times they came back. That is a statement about what they are trying to solve, and it is the only thing worth opening with.
Getting the behaviour, not just the person
The REST API gives you the person. For what they read, ask the assistant — it has the journey tools and will answer in one call:
curl -s https://app.signal.geysera.com/agent-api/signal/copilot/ask \
-H "Authorization: Bearer $SIGNAL_COPILOT_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "What pages did dana@acme.com look at, in order, and how many times did she come back?"}'
{
"answer": "Dana Cole visited 7 times between 2 August and 15 September...",
"question_kind": "descriptive",
"plan": { "reasoning": "...", "calls": ["visitor_journey"] },
"disclosures": ["..."],
"refusal": null,
"trace": [ { "tool": "visitor_journey", "data": { "...": "..." } } ]
}
Two things to note. answer is prose for a human; trace[].data is the
structured result the prose was written from, and that is what you feed a model
if you want it to reason rather than paraphrase. And refusal is not an error
— it is the assistant declining to answer from data it does not have. Check it.
The prompt
You are drafting the FIRST email to someone who has never spoken to us.
What they did on our site:
{journey_json}
What we know about them:
{visitor_json}
Write at most five sentences. Rules:
- Open by referencing one specific thing they read, by name.
- Do not mention that we can see their browsing. Say "I noticed you were
looking into X" only if X is a topic, never a URL.
- No claim about their company, budget, timeline or intent that is not in the
data above.
- End with one question that is easy to answer in a sentence.
- If the journey is a single pageview, say you have nothing to personalise on
and return the word SKIP instead of an email.
The SKIP clause is the important one. Roughly a third of identified visitors have one shallow pageview, and an opener built on that is worse than silence — it reads as surveillance without insight.
Keeping a human in the loop
Draft, don't send. Write the drafts somewhere a person clicks approve — your CRM's task queue, a Slack thread, a spreadsheet. Sending unreviewed generated mail to people who have not opted in is how a domain gets burned, and Signal cannot un-send it for you.
What will go wrong
The model will invent a pain point. Every time, unless the prompt forbids it and you reject drafts that do it. Put the raw journey in the review UI next to the draft so the reviewer can see the gap.
Pages tell you less than you think. A pricing-page visit is not intent to
buy; it is often a competitor or a candidate. classification helps —
competitor and excluded exist for this — but it is not perfect.
Quiet periods look like churn. The pixel misses landing pageviews for a meaningful share of visitors, so "they only read one page" sometimes means "we only saw one page."