# Open tracking, click tracking and why they are off by default

> Mailgun's tracking adds a pixel and rewrites every link. Both are personal data under GDPR, both cost deliverability, and neither belongs on a magic link.

Someone asks how many people opened the invoice email. Mailgun has a switch for
that, it is one line of code, and turning it on is the wrong first move, not
because tracking is forbidden, but because the two features hiding behind that
switch do more than count.

## What "tracking" actually does to your message

**Open tracking** appends a 1×1 transparent image to the HTML body, hosted on a
Mailgun domain with a unique identifier per message. When a mail client loads
images, Mailgun records a timestamp, the requesting IP address and the
user-agent. So an "open" is: the recipient's approximate location, the time they
read their mail, and the device they read it on.

**Click tracking** rewrites every `href` in your message to point at a Mailgun
redirect, which logs the click and forwards on. Your carefully written
`https://app.example.com/invoice/1234` becomes something like
`https://email.mg.example.com/c/eJx…`.

Both are useful for a marketing campaign. Both have costs that are easy to
discover late.

## Cost one: it is personal data

An IP address is personal data under GDPR, and so is a record of when a named
person read a message. Turning on open tracking means you are collecting
behavioural data about identified individuals, which needs a lawful basis, an
entry in your record of processing, a line in your privacy policy, and an answer
when someone files a subject access request asking what you know about them.

For transactional mail this is a bad trade. Legitimate interest is a defensible
basis for *sending* a receipt. It is much harder to argue for logging where the
recipient was standing when they read it.

## Cost two: it hurts deliverability

Click tracking rewrites links so that the visible text and the destination
disagree. That is the exact shape of a phishing email, and filters score it that
way. You are also inheriting the reputation of the tracking domain, if it is
Mailgun's shared one, that reputation is shared with every other account using
it; if it is yours, you now have a second domain to warm up and monitor.

Open-rate numbers have also been unreliable since Apple's Mail Privacy
Protection began pre-fetching images for anyone using Apple Mail. Those opens
are recorded whether or not a human looked. You are paying the deliverability
and privacy costs of tracking to receive a number that is inflated by an unknown
amount.

## Cost three: it can break the thing the email is for

Corporate mail security (Defender, Proofpoint, Mimecast) follows every link in
an incoming message before delivery, to check where it goes. With click tracking
on, each of those checks is a recorded click. Your "click rate" now includes
robots.

Worse, if the link is single-use, the scanner burns it. The user clicks a
perfectly good magic link and is told it has already been used. Tracking did not
cause that problem, but it adds a redirect hop to a URL that was already
fragile.

## The wrong way

```ts
// Tracking on globally, because someone wanted a dashboard.
await client().messages.create(domain, {
  from, to, subject, html,
  "o:tracking": "yes",
  "o:tracking-clicks": "yes",
  "o:tracking-opens": "yes",
});
```

Now every password reset carries a pixel and every sign-in link is rewritten.
Nobody decided that; it was a default applied to everything.

## The right way: off by default, opt in per send

Make tracking a parameter with a safe default, so enabling it is always a
visible decision in a diff:

```ts
export interface SendEmailOptions {
  // ...
  /** Off by default. See the tracking-and-privacy doc before turning it on. */
  tracking?: boolean;
}

await client().messages.create(sendingDomain(), {
  from: fromAddress(),
  to,
  subject: options.subject,
  html,
  text,
  "h:Reply-To": replyTo,
  "o:tracking": options.tracking ? "yes" : "no",
});
```

Note that the per-message setting overrides the domain's default, which is
exactly what you want: the domain can be configured however the dashboard was
left, and your code still sends what it intended.

Then hold the line on three categories, permanently:

- **Magic links and password resets.** No pixel, no rewritten URL. The link is a
  credential and every extra hop is a place for it to be consumed early.
- **Receipts and invoices.** They are financial records. A tracking pixel in
  one is indefensible and a rewritten link in one looks like fraud.
- **Anything to a recipient in a jurisdiction whose consent you do not have.**

## What to measure instead

The questions people actually want answered rarely need a pixel:

- **"Did it arrive?"**: that is a Mailgun `delivered` event, from the webhook.
  No pixel involved, no personal data beyond the address you already have.
- **"Did it bounce or get reported?"**: `failed` and `complained` events. These
  are the ones worth alerting on.
- **"Did they do the thing?"**: measure the outcome in your own product. A
  signed URL with a campaign parameter that lands on your page gives you a
  conversion number you own, tied to an action rather than an image load, and it
  is not inflated by Apple's pre-fetching.

That last substitution is the important one. "Opened the email" is a proxy for
"the email worked". You can measure the real thing on your own domain, where you
already have consent and a privacy policy that covers it.

## If you do turn it on

For genuine marketing sends, to a list that consented:

1. Set up a **custom tracking domain** (a `CNAME` on something like
   `email.yourdomain.com`) so you are not sharing reputation with strangers.
2. Say so in your privacy policy, in words a person can read.
3. Scope it to the campaign: `tracking: true` on that send, not on the domain
   default.
4. Set a retention period for the events and honour it.
5. Never re-use the same domain for transactional mail.

The default stays off. That way, the day someone asks "do we track opens on
password resets?", the answer is a one-word no rather than an audit.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
