SwiftTracker Guide
Email tracking framework - setup, destinations, and integration guide.
Overview
SwiftTracker is the receiving end of email tracking. Your SMTP provider sends the email; SwiftTracker handles what happens after the recipient interacts with it.
- Invisible 1x1 pixel logs every open with IP, country, and device
- Wrapped links log every click and redirect to the destination
- One-click unsubscribe manages your suppression list
- Health monitor rotates tracking domains when spam is detected
- Live globe shows every beacon on a spinning world map
SwiftTracker does not send emails. You keep your existing SMTP provider. This system only listens.
URL structure changed. Tracking links now use short path segments like /g/slug/email instead of long query strings. This is far less likely to be red-flagged by spam filters.
Installation
1. Install Wrangler
npm install
npx wrangler --version
2. Log in to Cloudflare
npx wrangler login
3. Create the D1 database
npx wrangler d1 create swift_tracker_db
Copy the database_id from the output into wrangler.toml.
4. Initialize the schema
npx wrangler d1 execute swift_tracker_db --file=schema.sql --remote
5. Set secrets
echo -n "YourPassword" | shasum -a 256
npx wrangler pages secret put ADMIN_PASSWORD_HASH
npx wrangler pages secret put TELEGRAM_BOT_TOKEN
npx wrangler pages secret put TELEGRAM_CHAT_ID
Secrets bind at deploy time. After changing any secret, redeploy:
npx wrangler pages deploy public --project-name swift-tracker-for-campaign
6. Deploy
npx wrangler pages deploy public --project-name swift-tracker-for-campaign
7. Attach custom subdomains
In the Cloudflare dashboard, add tracking subdomains to your Pages project:
- Workers & Pages → your project → Custom domains
- Add
track1.yourdomain.com,track2.yourdomain.com,track3.yourdomain.com - Cloudflare creates the DNS records automatically
Never use the raw *.pages.dev domain for tracking links. It's on spam blocklists due to phishing abuse. Always use your custom subdomain.
8. Verify _routes.json
This file tells Cloudflare which paths invoke functions. It must contain all four route prefixes:
{
"version": 1,
"include": ["/api/*", "/g/*", "/o/*", "/u/*"],
"exclude": []
}
If /g/, /o/, or /u/ are missing, your tracking links will return the homepage instead of redirecting.
9. Seed the domain pool
INSERT OR IGNORE INTO tracking_domains (domain, label, priority, active, status)
VALUES ('https://track1.yourdomain.com', 'Primary', 10, 1, 'healthy');
INSERT OR IGNORE INTO tracking_domains (domain, label, priority, active, status)
VALUES ('https://track2.yourdomain.com', 'Backup', 20, 0, 'healthy');
10. Log in
Visit https://yourdomain.com/login.html and enter the password whose hash you set in step 5.
Destinations
A destination is a slug you register once that maps to a real URL. Every email link uses the slug - if you ever need to change where the link points, you change the URL in the Destinations page and every email using that slug redirects to the new URL instantly.
Why this matters
Old pattern (spam-filter bait):
https://track1.yourdomain.com/api/track/click?email=user@example.com&url=https://yoursite.com/offer
New pattern (clean, trusted):
https://track1.yourdomain.com/g/offer2025/user@example.com
No query parameters. No visible destination. No redirector signature. Filters see a normal content URL.
Creating a destination
- Open Destinations from the top navigation
- Click + New Destination
- Fill in:
- Slug:
offer2025(lowercase, alphanumeric, dash, underscore) - URL:
https://yoursite.com/offer - Label: a human-readable name
- Slug:
- Click Save
- Click Copy next to the new row - you now have the ready-to-paste link template
Changing a destination URL later
Click Edit on any row, update the URL field, click Save. All existing emails that used that slug now redirect to the new URL. No re-sending needed.
Deactivating vs deleting
| Action | Effect |
|---|---|
| Set to Inactive | Links using this slug return 404. Slug remains for record-keeping. |
| Delete | Slug is removed. Any future email using it will fail. Use for retired campaigns. |
Email Integration
Click tracking
<a href="https://track1.yourdomain.com/g/YOUR-SLUG/[~EMail~]">
View your offer
</a>
Replace YOUR-SLUG with the slug you created in Destinations.
Open pixel
<img src="https://track1.yourdomain.com/o/YOUR-SLUG/[~EMail~]"
width="1" height="1" alt=""
style="display:block;width:1px;height:1px;border:0;" />
Unsubscribe
<a href="https://track1.yourdomain.com/u/YOUR-SLUG/[~EMail~]">
Unsubscribe
</a>
Gmail one-click header (optional but recommended)
List-Unsubscribe: <https://track1.yourdomain.com/u/YOUR-SLUG/[~EMail~]>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
The [~EMail~] placeholder is substituted by your sender at send time. Do not URL-encode it. SwiftTracker decodes it on receipt.
Domain Rotation
SwiftTracker rotates tracking domains automatically when one gets reported as spam. This is D1-driven, so rotation is instant - no redeploy needed.
How it works
- All tracking subdomains point to the same Pages project
- The active domain lives in
tracking_domains(one row hasactive = 1) - When spam is detected, the flag flips to the next healthy domain
- New emails automatically use the new domain - templates don't change
Getting the active domain (for your sender)
GET https://yourdomain.com/api/domain/current
Response: { "domain": "https://track2.yourdomain.com", "domainId": 2 }
Rotation triggers
| Trigger | How |
|---|---|
| Spam report | POST to /api/spam/report - exceeds threshold |
| Health check | Active domain reports down or spam |
| Manual | Click Force Rotate on the dashboard |
API Reference
Tracking routes (public)
| Method | Path | Purpose |
|---|---|---|
| GET | /g/SLUG/EMAIL | Log click, 302 to destination |
| GET | /o/SLUG/EMAIL | Return 1x1 GIF, log open |
| GET | /u/SLUG/EMAIL | Show unsubscribe page |
| POST | /u/SLUG/EMAIL | One-click unsubscribe (RFC 8058) |
Admin routes (session cookie)
| Method | Path | Purpose |
|---|---|---|
| POST | /api/auth/login | Log in |
| POST | /api/auth/logout | Log out |
| GET | /api/auth/session | Current session info |
| GET | /api/dashboard/stats | Aggregate metrics |
| GET | /api/dashboard/events | Paginated events |
| GET | /api/dashboard/geopoints | Country data for the globe |
| GET | /api/destinations/list | All destinations |
| POST | /api/destinations/save | Create or update |
| POST | /api/destinations/delete | Remove destination |
| GET | /api/domain/list | All tracking domains |
| POST | /api/domain/rotate | Force rotation |
| POST | /api/health/check | Ping all domains |
| GET | /api/settings/get | Read settings |
| POST | /api/settings/save | Write settings |
Troubleshooting
Click route returns the homepage instead of redirecting
Most common cause: _routes.json doesn't include /g/*.
cat public/_routes.json
Should include all four: /api/*, /g/*, /o/*, /u/*. If missing, add them and redeploy.
Click route returns 404 "Link not found"
Function ran, but the slug doesn't exist in the destinations table. Create it via the Destinations page.
Click route returns 400 "Invalid link"
The URL structure is wrong. Expected format: /g/slug/email@example.com. Check that the email portion has a valid format and the slug isn't empty.
"Unknown mode" error when saving a destination
Older versions of save.js accepted create and update but not edit. Update functions/api/destinations/save.js to include:
if (mode === 'edit') mode = 'update';
Login fails with "Invalid password"
- Use
echo -n, notecho- trailing newline changes the hash - Redeploy after setting the secret - Pages binds secrets at deploy time
- Verify with
wrangler pages secret list
Dashboard shows HTML instead of JSON
Console shows Unexpected token '<'. The function isn't loading, so Pages serves index.html as fallback.
- Delete
functions/_middleware.jsif it exists at the root (onlyfunctions/api/_middleware.jsshould exist) - Verify
public/_routes.jsonincludes/api/* - Redeploy
Instant logout after login
- D1 replica lag:
requireAdminshould retry 3x with 150ms delay - Use SQLite datetime format:
.replace('T',' ').replace(/\.\d+Z$/,'')
Custom domain not showing in preview
The Destinations editor preview uses location.origin. Log into the dashboard via your custom domain, not pages.dev.
FAQ
Do I need my own SMTP?
No. SwiftTracker only receives tracking events.
Why not use a pages.dev domain?
Shared domains are on spam blocklists. Use a custom subdomain of a domain you own.
How many tracking subdomains do I need?
At least 2, ideally 3-5. More domains = more rotation runway.
What happens when a domain is flagged as spam?
It's marked spam, skipped during rotation, and returns to healthy after the recovery window (default 24h).
Can I change a destination URL after sending?
Yes. Update it in Destinations and all emails using that slug redirect to the new URL.
Does it work with Gmail bulk requirements?
Yes - RFC 8058 one-click is implemented at /u/SLUG/EMAIL.
How do I back up data?npx wrangler d1 export swift_tracker_db --output backup.sql --remote