Mail tracking is a vital component of cold emailing, providing valuable insights into recipient engagement. ColdEmailingJet gives you the tools to embed tracking information into your emails, but you are responsible for setting up your own tracking infrastructure. This guide walks you through the entire process.
How Mail Tracking Works
ColdEmailingJet does not host tracking infrastructure. Instead, it embeds tracking data (like recipient email addresses) into your email links. You set up your own tracking server (Cloudflare Worker + D1 database) to capture and log recipient interactions.
The Complete Flow
1. You set up Cloudflare Worker + D1 Database
2. ColdEmailingJet inserts {email.email} tag into your tracking links
3. Email sent → Recipient clicks link → Request hits YOUR Worker
4. Worker logs data (email, IP, country, user agent) to D1 database
5. Worker serves age verification page (bot detection)
6. Human passes verification → Redirected to final offer page
Part 1: Setting Up Cloudflare Worker & D1 Database
Step 1: Create a D1 Database
- Log into your Cloudflare Dashboard
- Navigate to Workers & Pages → D1
- Click Create Database
- Name it (e.g.,
tracking_db) - Click Create
Step 2: Create the Database Table
Run this SQL in your D1 console:
CREATE TABLE email_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT,
ip TEXT,
country TEXT,
user_agent TEXT,
language TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Step 3: Create a Cloudflare Worker
- Go to Workers & Pages → Create Application → Create Worker
- Name your worker (e.g.,
tracking-worker) - Click Deploy then Edit Code
Step 4: Worker Code
Here’s a complete worker that:
- Captures email from URL parameters
- Logs IP, country, user agent, and language
- Serves an age verification page
- Detects bots before redirecting
export default {
async fetch(request, env, ctx) {
try {
const url = new URL(request.url);
const pathname = url.pathname;
if (request.method !== "GET") {
return new Response("Method not allowed", { status: 405 });
}
// Extract email from URL parameters
const email = url.searchParams.get("email") ||
url.searchParams.get("addr") ||
url.searchParams.get("mail") ||
"no_email";
// Tracking endpoint - logs data and redirects
if (pathname === "/track") {
if (isValidEmail(email)) {
const ip = request.headers.get("CF-Connecting-IP") ||
request.headers.get("x-forwarded-for") || "unknown";
const userAgent = request.headers.get("user-agent") || "unknown";
const country = request.headers.get("CF-IPCountry") || "unknown";
const language = request.headers.get("Accept-Language")?.split(',')[0] || "unknown";
// Save to D1 database (async, don't block redirect)
ctx.waitUntil(
env.DB.prepare(
`INSERT INTO email_events (email, ip, country, user_agent, language)
VALUES (?, ?, ?, ?, ?)`
).bind(email, ip, country, userAgent, language).run()
);
}
// Redirect to final destination
return Response.redirect("https://your-final-offer-page.com", 302);
}
// Age verification landing page
const object = await env.R2html.get("landing_page.html");
if (!object) {
return new Response("Page not found", { status: 404 });
}
let html = await object.text();
html = html.replace(/{{EMAIL}}/g, escapeHtml(email));
return new Response(html, {
headers: { "Content-Type": "text/html; charset=utf-8" }
});
} catch (err) {
return new Response("Error: " + err.message, { status: 500 });
}
}
};
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function escapeHtml(str) {
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&';
if (m === '<') return '<';
if (m === '>') return '>';
return m;
});
}
Step 5: Bind D1 Database to Worker
- Go to your Worker → Settings → Variables
- Under D1 Database Bindings, click Add binding
- Variable name:
DB - Select your D1 database
- Click Save
Step 6: Upload HTML Landing Page to R2
- Create an R2 bucket (e.g.,
tracking-assets) - Upload your
landing_page.htmlfile - Bind R2 to your Worker as
R2html
Part 2: Bot Detection on Landing Page
Your HTML landing page should verify the user is human before redirecting. Here’s the key logic:
Bot Detection Techniques
| Technique | How It Works |
|---|---|
| Mouse Movement Detection | Button only enables after mouse/touch interaction |
| Countdown Timer | Forces a 3-5 second wait (bots rarely wait) |
| Hidden Trap Link | Bot that crawls all links gets caught |
| Shadow DOM | Hides verification logic from simple scrapers |
Sample Landing Page Structure
<!DOCTYPE html>
<html>
<head>
<title>Age Verification</title>
</head>
<body>
<img id="previewImage">
<h1>You must be 18+ to enter</h1>
<div id="buttonContainer"></div>
<a href="/trap" style="display:none">Hidden trap for bots</a>
<script>
const email = "{{EMAIL}}";
let humanDetected = false;
let countdownFinished = false;
// Detect human interaction
window.addEventListener("mousemove", () => markHuman(), { once: true });
window.addEventListener("touchstart", () => markHuman(), { once: true });
window.addEventListener("keydown", () => markHuman(), { once: true });
function markHuman() {
humanDetected = true;
enableButtonIfReady();
}
// Countdown timer
let countdown = 3;
const interval = setInterval(() => {
countdown--;
if (countdown <= 0) {
clearInterval(interval);
countdownFinished = true;
enableButtonIfReady();
}
button.textContent = `Wait ${countdown}s`;
}, 1000);
function enableButtonIfReady() {
if (humanDetected && countdownFinished) {
button.disabled = false;
button.textContent = "ENTER";
button.onclick = () => {
window.location.href = `/track?email=${encodeURIComponent(email)}`;
};
}
}
</script>
</body>
</html>
Part 3: Configuring Tracking Links in ColdEmailingJet
Step 1: Create Your Tracking Link Template
In your email content, insert tracking links using the {email.email} tag:
https://your-worker.yourdomain.com/track?email={email.email}
Step 2: Using Different URL Parameters
ColdEmailingJet supports multiple parameter names:
| Parameter | Usage |
|---|---|
?email= | Standard email parameter |
?addr= | Alternative parameter name |
?mail= | Another alternative |
Example:
https://your-worker.com/track?addr={email.email}
Step 3: Adding Custom Fields
You can also include custom fields from your contact list:
https://your-worker.com/track?email={email.email}&name={email.FirstName}&campaign=camp_123
Step 4: Where to Put the Tracking Link
Place the tracking link in your email content:
HTML Email Example:
<a href="https://your-worker.com/track?email={email.email}">
Click Here to Claim Your Offer
</a>
Text Email Example:
Claim your offer: https://your-worker.com/track?email={email.email}
Part 4: Testing Your Setup
Test the Complete Flow
- Send a test email to yourself
- Click the tracking link in the email
- Verify the database record appears in your D1 database:
SELECT * FROM email_events ORDER BY created_at DESC LIMIT 10;
Expected Results
| Step | Expected Behavior |
|---|---|
| Link click | Worker logs IP, email, country to D1 |
| Landing page | Shows age verification with countdown |
| Human verification | Button enables after mouse move + timer |
| Final click | Redirects to your offer page |
Summary Checklist
| Task | Done |
|---|---|
| Create Cloudflare D1 database | ☐ |
| Create database table | ☐ |
| Create Cloudflare Worker | ☐ |
| Deploy worker code | ☐ |
| Bind D1 to worker | ☐ |
| Upload landing page to R2 | ☐ |
| Bind R2 to worker | ☐ |
| Add bot detection to landing page | ☐ |
Insert {email.email} tag in ColdEmailingJet links | ☐ |
| Test with real email | ☐ |
Key Takeaways
- ColdEmailingJet only embeds tracking data into links – it does NOT host tracking
- You must set up Cloudflare Worker + D1 database to capture clicks
- Bot detection requires mouse/touch events and countdown timers on your landing page
- Use
{email.email}tag to insert recipient addresses into your tracking links
With this setup, you have complete control over your tracking data while ColdEmailingJet handles the email delivery and personalization.