open mail and click url

This commit is contained in:
romantarkin 2025-08-18 11:22:56 +05:00
parent 833470cbdb
commit 2371aa727e
4 changed files with 101 additions and 1 deletions

View File

@ -0,0 +1,72 @@
import { DeliveryLog } from '../models/index.js';
export default {
async trackOpen(req, res) {
try {
const { deliveryLogId } = req.params;
// Находим запись в DeliveryLog
const deliveryLog = await DeliveryLog.findByPk(deliveryLogId);
if (!deliveryLog) {
return res.status(404).send('Not found');
}
// Обновляем время открытия, если еще не было установлено
if (!deliveryLog.opened_at) {
await deliveryLog.update({
opened_at: new Date()
});
console.log(`[Tracking] Email opened: deliveryLogId=${deliveryLogId}, campaignId=${deliveryLog.campaign_id}, subscriberId=${deliveryLog.subscriber_id}`);
}
// Возвращаем прозрачный 1x1 пиксель
const pixel = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', 'base64');
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': pixel.length,
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
});
res.end(pixel);
} catch (err) {
console.error('[Tracking] Error tracking email open:', err);
res.status(500).send('Error');
}
},
async trackClick(req, res) {
try {
const { deliveryLogId } = req.params;
const { url } = req.query;
if (!url) {
return res.status(400).send('URL parameter required');
}
// Находим запись в DeliveryLog
const deliveryLog = await DeliveryLog.findByPk(deliveryLogId);
if (!deliveryLog) {
return res.status(404).send('Not found');
}
// Обновляем время клика, если еще не было установлено
if (!deliveryLog.clicked_at) {
await deliveryLog.update({
clicked_at: new Date()
});
console.log(`[Tracking] Email clicked: deliveryLogId=${deliveryLogId}, campaignId=${deliveryLog.campaign_id}, subscriberId=${deliveryLog.subscriber_id}, url=${url}`);
}
// Перенаправляем на оригинальный URL
res.redirect(url);
} catch (err) {
console.error('[Tracking] Error tracking email click:', err);
res.status(500).send('Error');
}
}
};

View File

@ -8,6 +8,7 @@ import campaignRoutes from './campaign.js';
import deliveryLogRoutes from './deliveryLog.js'; import deliveryLogRoutes from './deliveryLog.js';
import smtpServerRoutes from './smtpServer.js'; import smtpServerRoutes from './smtpServer.js';
import topicRoutes from './topic.js'; import topicRoutes from './topic.js';
import trackingRoutes from './tracking.js';
const router = Router(); const router = Router();
@ -20,5 +21,6 @@ router.use('/campaigns', campaignRoutes);
router.use('/delivery-logs', deliveryLogRoutes); router.use('/delivery-logs', deliveryLogRoutes);
router.use('/smtp-servers', smtpServerRoutes); router.use('/smtp-servers', smtpServerRoutes);
router.use('/topics', topicRoutes); router.use('/topics', topicRoutes);
router.use('/track', trackingRoutes);
export default router; export default router;

View File

@ -0,0 +1,12 @@
import express from 'express';
import trackingController from '../controllers/trackingController.js';
const router = express.Router();
// Трекинг открытия письма
router.get('/open/:deliveryLogId', trackingController.trackOpen);
// Трекинг клика по ссылке
router.get('/click/:deliveryLogId', trackingController.trackClick);
export default router;

View File

@ -259,12 +259,26 @@ async function processEmailTask(task, topic) {
}); });
console.log(`[DynamicConsumer] Transporter created successfully`); console.log(`[DynamicConsumer] Transporter created successfully`);
// Добавляем трекинг-пиксель для отслеживания открытия письма
const trackingPixel = `<img src="http://${smtp.from_email}/api/mail/track/open/${deliveryLog.id}" width="1" height="1" style="display:none;" />`;
// Обрабатываем ссылки для отслеживания кликов
const htmlWithClickTracking = task.html.replace(
/<a\s+href=["']([^"']+)["']/gi,
(match, url) => {
const trackingUrl = `http://${smtp.from_email}/api/mail/track/click/${deliveryLog.id}?url=${encodeURIComponent(url)}`;
return `<a href="${trackingUrl}"`;
}
);
const htmlWithTracking = htmlWithClickTracking + trackingPixel;
const mailOptions = { const mailOptions = {
from: smtp.from_email, from: smtp.from_email,
to: task.email, to: task.email,
subject: task.subject, subject: task.subject,
text: task.text, text: task.text,
html: task.html, html: htmlWithTracking,
headers: { headers: {
'List-Unsubscribe': `<mailto:${smtp.from_email}?subject=unsubscribe>`, 'List-Unsubscribe': `<mailto:${smtp.from_email}?subject=unsubscribe>`,
} }