Настройка трекинга открытий и кликов в email
Трекинг открытий работает через пиксель — прозрачное изображение 1×1 px, встроенное в письмо. Трекинг кликов — через редирект-ссылки. Большинство ESP делают это автоматически, но иногда нужна своя реализация для хранения данных в собственной базе.
Трекинг через пиксель (открытия)
// GET /api/email/track/open/:token
app.get('/api/email/track/open/:token', async (req, res) => {
const { token } = req.params;
// Не await — не блокировать ответ
trackEmailOpen(token, {
ip: req.ip,
userAgent: req.get('User-Agent') ?? '',
timestamp: new Date(),
}).catch(console.error);
// Вернуть 1x1 прозрачный GIF
const pixel = Buffer.from(
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
'base64'
);
res.writeHead(200, {
'Content-Type': 'image/gif',
'Content-Length': pixel.length,
'Cache-Control': 'no-store, no-cache, must-revalidate',
});
res.end(pixel);
});
async function trackEmailOpen(token: string, meta: EmailOpenMeta) {
const emailLog = await db.emailLogs.findByTrackingToken(token);
if (!emailLog) return;
await db.emailOpenEvents.create({
emailLogId: emailLog.id,
userId: emailLog.userId,
...meta,
});
await db.emailLogs.update(emailLog.id, {
openedAt: meta.timestamp,
openCount: emailLog.openCount + 1,
});
}
Пиксель вставляется в письмо при рендере:
const trackingToken = generateTrackingToken(emailLogId);
const pixelUrl = `https://app.example.com/api/email/track/open/${trackingToken}`;
// В HTML письма перед закрывающим </body>
const trackingPixel = `<img src="${pixelUrl}" width="1" height="1" alt="" style="display:block" />`;
Трекинг кликов через редирект
// GET /api/email/track/click/:token?url=...
app.get('/api/email/track/click/:token', async (req, res) => {
const { token } = req.params;
const targetUrl = req.query.url as string;
if (!targetUrl) return res.redirect('/');
// Трекинг асинхронно
trackEmailClick(token, targetUrl, {
ip: req.ip,
userAgent: req.get('User-Agent') ?? '',
}).catch(console.error);
res.redirect(302, targetUrl);
});
// Подстановка ссылок при рендере шаблона
function wrapLinksWithTracking(html: string, emailLogId: string): string {
return html.replace(
/href="(https?:\/\/[^"]+)"/g,
(match, url) => {
if (url.includes('/api/email/track/')) return match; // уже обёрнута
const token = generateTrackingToken(emailLogId);
const wrapped = `https://app.example.com/api/email/track/click/${token}?url=${encodeURIComponent(url)}`;
return `href="${wrapped}"`;
}
);
}
Аналитика по кампании
-- Метрики кампании
SELECT
el.campaign_id,
COUNT(DISTINCT el.id) AS sent,
COUNT(DISTINCT eo.email_log_id) AS opened,
COUNT(DISTINCT ec.email_log_id) AS clicked,
ROUND(COUNT(DISTINCT eo.email_log_id)::numeric / COUNT(DISTINCT el.id) * 100, 1) AS open_rate,
ROUND(COUNT(DISTINCT ec.email_log_id)::numeric / COUNT(DISTINCT el.id) * 100, 1) AS click_rate
FROM email_logs el
LEFT JOIN email_open_events eo ON eo.email_log_id = el.id
LEFT JOIN email_click_events ec ON ec.email_log_id = el.id
WHERE el.campaign_id = $1
GROUP BY el.campaign_id;
Ограничения
- Apple Mail Privacy Protection (iOS 15+) — предварительно загружает пиксели, завышая open rate. Компенсация: использовать click rate как основную метрику.
- Блокировщики изображений — часть Outlook-пользователей открывает письма с отключёнными картинками; пиксели не срабатывают.
Сроки
Собственный трекинг открытий + кликов + базовая аналитика — 1–2 дня.







