Email tracking

Swap mailto: links to per-visitor tracked addresses. Replies come back through Funnelion.ai, attributed to the originating campaign.

Every visitor session claims a unique address on a domain you control (e.g. antanas@inbox.yoursite.com). When the visitor sends mail to that address, Funnelion.ai records a lead event tied to their traffic source and forwards the mail to the inbox you operate from day-to-day.

1. Dashboard setup

  1. Add an email domain at Email domains → Add. Pick a subdomain you don’t already use for mail - e.g. inbox.yoursite.com or mail.yoursite.com.
  2. Publish the MX / SPF / DKIM DNS records Funnelion.ai shows you. Wait for the row to flip to “verified” (a couple of minutes once DNS propagates).
  3. Create a dynamic email pool at Pools → New: channel email, type dynamic, domain you just verified, one or more language packs (or custom names), forward-to recipient, reply mode intercept.
  4. Create a swap zone at Swap zones → New: kind email, selector a[href^="mailto:"], pool you just created.
  5. Optional: add routing rules so submissions from specific UTMs get a custom source label (“Facebook”, “Google”, etc.).

Why a separate subdomain?

Funnelion.ai takes over the MX records for whatever host you point at it. Use a fresh subdomain - never your apex domain (yoursite.com) and never the host you already receive primary mail on. inbox., mail., track. are common picks.

2. Frontend path (JS snippet)

Drop the snippet into your site’s <head>. It binds to every mailto: link on the page and swaps the address (and the visible text) with the visitor’s claimed pool address. Ships zero requests until a real visitor hits the page.

HTML
<!-- Production (minified, recommended) -->
<script async src="https://dash.funnelion.ai/track.min.js?t=YOUR_SITE_TOKEN"></script>

<!-- Same code, unminified - handy when debugging in DevTools -->
<script async src="https://dash.funnelion.ai/track.js?t=YOUR_SITE_TOKEN"></script>

Your SITE_TOKEN is the public token on the Site row in the dashboard - safe to embed in HTML. For specific anchors, add data-funnelion="Email Address" (the zone name from step 4 above) so the snippet knows exactly which element to swap:

HTML
<a href="mailto:hello@yoursite.com" data-funnelion="Email Address">
  hello@yoursite.com
</a>

The visible text inside the link is your fallback - what the visitor sees if Funnelion.ai is unreachable. Always write a real working address there.

3. Backend path (PHP SDK)

Server-side resolution survives ad blockers, content blockers, and strict CSPs because the visitor’s browser never has to reach Funnelion.ai. Requires PHP 8.1+ and ext-curl.

Install the SDK

Bash
composer config repositories.funnelion vcs https://github.com/funnelion/funnelion-php.git
composer require funnelion/sdk:^0.3.0

Wire it into your page render

Read the server-side token from the dashboard (Sites → Edit → Server-side token) and put it in your server’s .env. Then, in your front controller / page renderer:

PHP
<?php
require __DIR__.'/vendor/autoload.php';

use Funnelion\Client;
use Funnelion\Config;
use Funnelion\Cookie\Session;
use Funnelion\Html\ZoneSwapper;
use Funnelion\Resolve\Request;

$client = new Client(new Config(
    siteToken: getenv('FUNNELION_SERVER_SIDE_TOKEN'),
    timeoutSeconds: 0.5,
));

$response = $client->resolveOrNull(new Request(
    url:       'https://yoursite.com'.($_SERVER['REQUEST_URI'] ?? '/'),
    ip:        $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0',
    referrer:  $_SERVER['HTTP_REFERER'] ?? null,
    userAgent: $_SERVER['HTTP_USER_AGENT'] ?? null,
    visitorId: Session::readFromGlobals(),
));

if ($response !== null) {
    $html = (new ZoneSwapper())->swap($html, $response);
    if ($response->visitorId !== null) {
        header('Set-Cookie: '.Session::headerValue($response->visitorId), false);
    }
}

echo $html;

Mark up your HTML

The ZoneSwapper looks for data-funnelion="<Zone Name>" on a leaf element (text only, no nested tags). For <a> tags with a mailto: href, both the href and the visible text get rewritten.

HTML
<a href="mailto:hello@yoursite.com" data-funnelion="Email Address">
  hello@yoursite.com
</a>

React / Vue hydration gotcha

If your link wraps an icon (<svg> child), the server-side swapper can’t touch it - its regex requires a leaf element. The fix: render the resolved address in JSX from window.__FUNNELION_ZONES (the SDK injects this script tag into your HTML for exactly this case). See src/pages/Contact.jsx in the funnelion.ai source repo for a working pattern.

4. What happens when mail arrives

Inbound mail to any address on your verified domain is parsed by Funnelion.ai and ingested as a lead event of kind email, attributed to the visitor session whose cookie was set when they were on your page. The mail is then forwarded to the recipient you configured on the pool - your real inbox.

  • Reply mode intercept rewrites Reply-To headers so visitor responses still come back through Funnelion.ai (full thread visibility).
  • Reply mode pass_through forwards the mail as-is - Funnelion.ai sees the inbound event but drops out of the loop after that.