← Blog 7 min read

Track Outreach Email Clicks to Your Demo With One Custom Event

You send ten cold emails on a Tuesday. Two replies come in, the rest are silence. The question that actually matters is not your open rate — it's which of the other eight prospects clicked through to the demo and said nothing. Here is the smallest setup that answers it cleanly: one redirect on your own domain, one custom event in Logly, five lines of JavaScript.

Why generic page tracking misses the point for outreach

A demo page view is a coarse signal. The default pageview tracker sees three identical events:

Only the third one is worth a follow-up at 9 a.m. tomorrow. Until you separate that traffic out, every "demo page views: 47" number is noise dressed up as a metric. We want to isolate the outreach-driven click so we can act on it — re-engage, deprioritise, promote to warm — and ignore the rest.

The minimal architecture: one redirect, one event

Pretend you run a small SaaS called Inboxly and you send roughly 10 cold emails a day. Each prospect gets a unique short token when you queue them up.

The outreach email contains one link:

https://inboxly.app/c/9f2a

That endpoint lives on your own domain, in your own Worker (or Express, or Flask — whatever you already run). It does two things and gets out of the way:

// /c/:token  — Cloudflare Worker handler
export async function handleClick(req, env) {
  const token = new URL(req.url).pathname.split('/').pop();

  await env.DB.prepare(
    `UPDATE prospects
        SET visited_at = COALESCE(visited_at, ?1)
      WHERE token = ?2`
  ).bind(Date.now(), token).run();

  return Response.redirect(
    `https://inboxly.app/demo?ref=outreach_${token}`,
    302
  );
}

The COALESCE is the only clever line: we only stamp visited_at the first time, so multiple clicks from the same prospect don't overwrite the first-touch timestamp. The 302 sends them to the real demo URL with a ref query param the frontend can read.

On the demo page, we fire the custom event only when the ref looks like ours:

<script defer src="https://logly.uk/p.js" data-site="inboxly"></script>
<script>
  const ref = new URLSearchParams(location.search).get('ref') || '';
  if (ref.startsWith('outreach_')) {
    window.logly && window.logly('event', 'or_demo');
  }
</script>

That is the whole pipeline. The prospect's status flips to "visited" in your DB. The aggregate click cadence shows up in the Logly events panel as or_demo.

Why a custom event beats UTM-only tracking

UTMs are fine for one-shot campaign tagging, but they fall apart for outreach attribution in three predictable ways:

A custom event is a single named signal that survives the rest of the session. The name (or_demo) is the join key with your own DB if you ever want to dig deeper. The events panel groups by name, not by a parameter you have to remember to clean up.

Keeping the signal clean

The fastest way to ruin this metric is to count yourself. Two small disciplines fix it:

<script>
  const isInternal =
    location.hostname === 'staging.inboxly.app' ||
    document.cookie.includes('staff=1');

  if (!isInternal) {
    const s = document.createElement('script');
    s.defer = true;
    s.src = 'https://logly.uk/p.js';
    s.dataset.site = 'inboxly';
    document.head.appendChild(s);
  }
</script>

Conditional loading keeps p.js out of staff sessions entirely — no event firing, no pageview, no skew. Combined with the ref check above, the only thing that can produce an or_demo event is a real prospect clicking a real outreach link.

What to do with the data

Open the Logly dashboard, filter Events → or_demo, and you'll see the click cadence after each send batch. The volume is small on purpose — ten emails a day means an or_demo count of one or two is already meaningful. Cross-reference with your own prospects table (token is the join key) to know which prospect clicked, and you have a daily list short enough to act on by hand:

This is outreach attribution without a CRM. The whole stack is one column in a SQLite/D1 table, one event name in Logly, and the willpower to actually look at the list on Friday morning.

In Logly, every event you fire is visible in the Events panel within seconds. No schema to register, no taxonomy to declare upfront. Same anonymous, cookie-free pipeline as pageviews — which is what makes it safe to use as a per-prospect signal without crossing into PII territory.

Variations worth noting

One event per destination. If your outreach links to three places, use or_demo, or_pricing, or_docs. Same redirect pattern, different event name on the landing page. The events panel will rank them and you'll learn which hook your prospects actually want.

Don't track opens. Pixel tracking for email opens looks tempting and is mostly noise: Apple Mail Privacy Protection pre-fetches images, security gateways pre-click links, and "opened" gets you no closer to "interested". It also nudges your deliverability in the wrong direction. Clicks are the first signal worth optimising for; opens are not.

Token rotation. If you re-send to the same prospect, generate a new token. It keeps visited_at honest as a first-touch timestamp per campaign instead of per lifetime.

One redirect, one event, one column

That is the whole outreach analytics stack you need until you have more prospects than you can read in a single sitting. When you cross that threshold, the next step is not a CRM — it's stringing or_demo together with signup_started and signup_completed into a funnel, so you can see at which step your warm clickers cool off.

One event name. Real outreach signal.

Logly's custom events are one line of JavaScript, anonymous, and visible in the dashboard within seconds. Full event API in the docs.

Get started free →