---
title: Tracking
path: concepts/tracking
status: published
---

# Tracking

ScaiSend tracks four things: opens, clicks, unsubscribes, and (optionally) Google Analytics UTM parameters. Each one has a tenant-level default and a per-request override. This page covers what's tracked, how it's instrumented, and how to turn it on or off.

## The four tracking features

| Feature | What it tracks | How it's instrumented |
|---------|----------------|-----------------------|
| **Open tracking** | Whether an email was opened | 1×1 transparent GIF appended before `</body>` |
| **Click tracking** | Which links were clicked | HTML links rewritten to redirect via `/t/c/{token}` |
| **Subscription tracking** | Unsubscribes | Footer link + `List-Unsubscribe` / `List-Unsubscribe-Post` headers |
| **Google Analytics** | Campaign attribution | UTM parameters appended to links |

All four are off-the-shelf; you enable them with configuration, not code.

## Configuration levels

Two layers, with request overriding tenant.

| Layer | Scope | How to configure |
|-------|-------|------------------|
| **Tenant default** | Every message from this tenant unless overridden | `PUT /api/admin/tenants/{id}/tracking` |
| **Request override** | This single send only | `tracking_settings` in the `/v3/mail/send` body |

The request-level settings are deep-merged over the tenant defaults. A request with `tracking_settings: {click_tracking: {enable: false}}` disables click tracking for that send only; open tracking, subscription tracking, etc. still use the tenant default.

## Defaults

| Feature | Default |
|---------|---------|
| `open_tracking_enabled` | `true` |
| `click_tracking_enabled` | `true` |
| `click_tracking_enable_text` | `false` (HTML only by default) |
| `subscription_tracking_enabled` | `false` |
| `ganalytics_enabled` | `false` |
| `image_embed_mode` | `proxy` |

## Setting tenant defaults

```bash
curl -X PUT https://scaisend.scailabs.ai/api/admin/tenants/tnt_acme/tracking \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "open_tracking_enabled": true,
    "click_tracking_enabled": true,
    "subscription_tracking_enabled": true,
    "subscription_tracking_html": "<p>Preferences? <a href=\"<% %>\">Manage here</a>.</p>",
    "image_embed_mode": "proxy"
  }'
```

```python
import os, httpx

httpx.put(
    f"https://scaisend.scailabs.ai/api/admin/tenants/{tenant_id}/tracking",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_JWT']}"},
    json={
        "open_tracking_enabled": True,
        "click_tracking_enabled": True,
        "subscription_tracking_enabled": True,
        "subscription_tracking_html": '<p>Preferences? <a href="<% %>">Manage here</a>.</p>',
        "image_embed_mode": "proxy",
    },
)
```

```typescript
await fetch(`https://scaisend.scailabs.ai/api/admin/tenants/${tenantId}/tracking`, {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.SCAISEND_JWT}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    open_tracking_enabled: true,
    click_tracking_enabled: true,
    subscription_tracking_enabled: true,
    subscription_tracking_html: '<p>Preferences? <a href="<% %>">Manage here</a>.</p>',
    image_embed_mode: "proxy",
  }),
});
```

PUT is additive — fields you don't send keep their current value.

## Per-request override

```json
{
  "personalizations": [{"to": [{"email": "user@example.com"}]}],
  "from": {"email": "hello@mail.example.com"},
  "subject": "Newsletter #42",
  "content": [{"type": "text/html", "value": "<html>..."}],
  "tracking_settings": {
    "click_tracking": {"enable": true, "enable_text": false},
    "open_tracking": {"enable": true, "substitution_tag": "%open_pixel%"},
    "subscription_tracking": {"enable": true, "substitution_tag": "%unsubscribe_url%"},
    "ganalytics": {
      "enable": true,
      "utm_source": "newsletter",
      "utm_medium": "email",
      "utm_campaign": "april_2026"
    }
  }
}
```

## How each feature works

### Open tracking

If `open_tracking.enable` is true and the message has an HTML body, ScaiSend injects a 1×1 transparent GIF before the closing `</body>` tag. When the recipient's email client loads the image, it hits `/t/o/{token}.gif` on your ScaiSend instance. That request records an `open` event.

**Caveat: images are cached.** Repeat opens from the same client may not be detected — most opens log once, then subsequent opens are silent. The image proxy (`image_embed_mode: proxy`, below) is a backup signal for when the pixel is blocked.

**To embed the pixel at a custom spot**, use a substitution tag. If `substitution_tag` is set, ScaiSend replaces occurrences of the tag with the pixel URL instead of appending to `</body>`. Use this if your template has structure that breaks when things are injected at the end.

### Click tracking

Every `<a href="...">` in the HTML body (and, if `enable_text` is true, every URL in the plain-text body) is rewritten to `https://scaisend.scailabs.ai/t/c/{token}?u=<original_url>`. When the recipient clicks, ScaiSend records a `click` event and redirects with `302` to the original URL.

**The redirect is fast** — measured in tens of milliseconds — but it's not zero. If you have latency-sensitive links (e.g., login magic links with a short TTL), you can bypass click tracking with `tracking_settings.click_tracking.enable: false` on that send.

Text-mode click tracking is off by default because it tends to produce weird-looking links in plain-text email. Turn it on deliberately.

### Subscription tracking

If `subscription_tracking.enable` is true, ScaiSend:

1. **Injects an unsubscribe footer** at the bottom of the HTML (and text) body. Default text is minimal; override with `subscription_tracking_html` / `subscription_tracking_text` on the tenant setting. Use `<% %>` as the placeholder for the unsubscribe URL. If `substitution_tag` is set on the request, ScaiSend replaces that tag instead of injecting a footer — useful when you want the unsubscribe link in a specific position in your template.
2. **Adds a `List-Unsubscribe` header** containing the unsubscribe URL. Email clients that honor it (Gmail, Apple Mail, Outlook) show a native "Unsubscribe" button in the UI.
3. **Adds a `List-Unsubscribe-Post` header** (`List-Unsubscribe=One-Click`), enabling [RFC 8058](https://datatracker.ietf.org/doc/html/rfc8058) one-click unsubscribes. Gmail and Yahoo use this to honor unsubscribes without the recipient visiting your landing page.

When a recipient unsubscribes — clicking the footer link, clicking the native button, or via one-click — ScaiSend:

- Adds the address to your tenant's global unsubscribe list.
- Records an `unsubscribe` event.
- Fans out the `unsubscribe` webhook.
- Future sends to this address skip it (unless `bypass_list_management` or `bypass_unsubscribe_management` is set on the send).

### Google Analytics

If `ganalytics.enable` is true, ScaiSend appends UTM parameters to every tracked link (and, if tenant-configured, un-tracked links too). Specify any of `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` — they become query-string parameters on the final redirect target.

You typically only care about this if your landing page has Google Analytics (or equivalent) configured to report on email campaigns.

## Image embedding modes

ScaiSend offers two modes for images referenced by a template:

| Mode | Where images live | Pros | Cons |
|------|-------------------|------|------|
| `proxy` (default) | Served via `/i/{image_id}` | Smaller email size; image-load tracking as backup open signal | One extra request per image; requires public HTTPS |
| `cid` | Embedded as `Content-ID` inline attachments | Works offline; no outbound request from client | Bigger email; breaks with strict attachment stripping; no image-load tracking |

Configure per tenant:

```bash
curl -X PUT https://scaisend.scailabs.ai/api/admin/tenants/tnt_acme/tracking \
  -H "Authorization: Bearer $SCAISEND_JWT" \
  -H "Content-Type: application/json" \
  -d '{"image_embed_mode": "proxy"}'
```

## The tracking base URL

Tracking pixels, click redirects, and unsubscribe URLs all use the `TRACKING_BASE_URL` environment variable. Set it to a publicly reachable HTTPS URL that terminates at your ScaiSend API service:

```bash
TRACKING_BASE_URL=https://scaisend.example.com
```

Pointing this at `localhost` will break tracking for any recipient outside your network. If you're self-hosting, put a public hostname with valid TLS in front of ScaiSend and set this variable to match.

## Public tracking endpoints

These don't require authentication — they're invoked by recipients' email clients:

| Endpoint | Purpose |
|----------|---------|
| `GET /t/o/{token}.gif` | Open tracking pixel. Returns a 1×1 GIF. |
| `GET /t/c/{token}` | Click redirect. Records event, redirects to original URL with 302. |
| `GET /t/u/{token}` | Unsubscribe landing page. Shows a confirmation page. |
| `POST /t/u/{token}` | Unsubscribe confirmation (from the landing page). |
| `POST /t/u/{token}/one-click` | RFC 8058 one-click unsubscribe. Returns 200 or 400. |
| `GET /i/{image_id}` | Image proxy (hides backend URL; records image loads). |

Tokens are long, opaque, and per-message. An attacker can't enumerate.

## What events are recorded

| Event | When |
|-------|------|
| `open` | Tracking pixel loaded |
| `click` | Click redirect hit (one event per clicked link; duplicate clicks deduplicated by URL per recipient) |
| `unsubscribe` | Unsubscribe landing POST, or one-click POST, or `List-Unsubscribe` header honored |
| `group_unsubscribe` | Unsubscribe specific to an ASM group (see [Suppressions](suppressions)) |
| `group_resubscribe` | Recipient resubscribed to an ASM group |

Each event includes `recipient_email`, `timestamp`, and event-specific metadata (URL for clicks, user-agent/IP for opens). See [Messages and Events](../tutorials/messages-and-events) for the timeline view and [Webhooks](../tutorials/webhooks) for the push-based delivery.

## What's next

- [Suppressions](suppressions) — what happens when a recipient unsubscribes.
- [Events and Webhooks](events-and-webhooks) — getting events delivered to your service.
- [Sending Mail](../tutorials/sending-mail) — `tracking_settings` on a send.
