@huloglobal/vendure-plugin-email-tracking 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +34 -0
- package/README.md +129 -0
- package/dist/email-log.entity.d.ts +57 -0
- package/dist/email-log.entity.d.ts.map +1 -0
- package/dist/email-log.entity.js +147 -0
- package/dist/email-log.entity.js.map +1 -0
- package/dist/email-tracking.controller.d.ts +51 -0
- package/dist/email-tracking.controller.d.ts.map +1 -0
- package/dist/email-tracking.controller.js +260 -0
- package/dist/email-tracking.controller.js.map +1 -0
- package/dist/email-tracking.service.d.ts +48 -0
- package/dist/email-tracking.service.d.ts.map +1 -0
- package/dist/email-tracking.service.js +196 -0
- package/dist/email-tracking.service.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +43 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +52 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +53 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +118 -0
- package/dist/plugin.js.map +1 -0
- package/dist/proxy-headers.d.ts +37 -0
- package/dist/proxy-headers.d.ts.map +1 -0
- package/dist/proxy-headers.js +81 -0
- package/dist/proxy-headers.js.map +1 -0
- package/dist/tracking-email-sender.d.ts +22 -0
- package/dist/tracking-email-sender.d.ts.map +1 -0
- package/dist/tracking-email-sender.js +117 -0
- package/dist/tracking-email-sender.js.map +1 -0
- package/package.json +53 -0
- package/ui/components/email-log.component.ts +372 -0
- package/ui/email-log-nav.module.ts +24 -0
- package/ui/email-log.module.ts +17 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.EmailTrackingController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const email_log_entity_1 = require("./email-log.entity");
|
|
19
|
+
const email_tracking_service_1 = require("./email-tracking.service");
|
|
20
|
+
const loggerCtx = 'EmailTrackingController';
|
|
21
|
+
// 1×1 transparent GIF (43 bytes) returned by the open-tracking endpoint.
|
|
22
|
+
const ONE_PX_GIF = Buffer.from('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64');
|
|
23
|
+
const proxy_headers_1 = require("./proxy-headers");
|
|
24
|
+
function realIp(req) { return (0, proxy_headers_1.getRealIp)(req); }
|
|
25
|
+
function requireAdmin(ctx, res, write = false) {
|
|
26
|
+
if (!(ctx === null || ctx === void 0 ? void 0 : ctx.activeUserId)) {
|
|
27
|
+
res.status(401).json({ error: 'Authentication required' });
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const needed = write ? [core_1.Permission.UpdateCustomer] : [core_1.Permission.ReadCustomer];
|
|
31
|
+
if (!ctx.userHasPermissions(needed)) {
|
|
32
|
+
res.status(403).json({ error: 'Insufficient permissions' });
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
let EmailTrackingController = class EmailTrackingController {
|
|
38
|
+
constructor(connection, tracking) {
|
|
39
|
+
this.connection = connection;
|
|
40
|
+
this.tracking = tracking;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Open-tracking pixel. Returns a 1×1 transparent GIF and logs the
|
|
44
|
+
* open against the event. No-cache so privacy-protecting prefetchers
|
|
45
|
+
* still get caught per fetch (and we count opens accurately).
|
|
46
|
+
*
|
|
47
|
+
* GET /email-track/open/:id.gif
|
|
48
|
+
*/
|
|
49
|
+
async open(idParam, req, res) {
|
|
50
|
+
const id = parseInt(idParam, 10);
|
|
51
|
+
if (!isNaN(id) && id > 0) {
|
|
52
|
+
this.tracking.recordOpen(id, realIp(req), req.headers['user-agent'] || null)
|
|
53
|
+
.catch((e) => core_1.Logger.warn(`open log fail #${id}: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx));
|
|
54
|
+
}
|
|
55
|
+
res.setHeader('Content-Type', 'image/gif');
|
|
56
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0');
|
|
57
|
+
res.setHeader('Pragma', 'no-cache');
|
|
58
|
+
res.setHeader('Expires', '0');
|
|
59
|
+
return res.end(ONE_PX_GIF);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Click redirect. Logs the click then 302-redirects to the original
|
|
63
|
+
* URL. Returns 400 if the URL is missing or malformed (we never
|
|
64
|
+
* redirect to an empty / javascript: target).
|
|
65
|
+
*
|
|
66
|
+
* GET /email-track/click/:id?u=<encoded>
|
|
67
|
+
*/
|
|
68
|
+
async click(idParam, u, req, res) {
|
|
69
|
+
const id = parseInt(idParam, 10);
|
|
70
|
+
const url = (u || '').trim();
|
|
71
|
+
if (!url || !/^https?:\/\//i.test(url)) {
|
|
72
|
+
return res.status(400).send('Invalid redirect target');
|
|
73
|
+
}
|
|
74
|
+
if (!isNaN(id) && id > 0) {
|
|
75
|
+
await this.tracking.recordClick(id, url, realIp(req), req.headers['user-agent'] || null)
|
|
76
|
+
.catch((e) => core_1.Logger.warn(`click log fail #${id}: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx));
|
|
77
|
+
}
|
|
78
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
79
|
+
return res.redirect(302, url);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Bounce webhook. Wire up a postmaster / mail-server hook (or have a
|
|
83
|
+
* scheduled DSN-parser POST here) to mark messages as bounced or
|
|
84
|
+
* complained. Both fields are required; the messageId is the
|
|
85
|
+
* `<...@domain>` value Gmail returned at submit time.
|
|
86
|
+
*
|
|
87
|
+
* POST /email-track/bounce
|
|
88
|
+
* Body: { messageId, status: 'bounced'|'complained', reason? }
|
|
89
|
+
*
|
|
90
|
+
* Currently unauthenticated to keep the integration simple — gate
|
|
91
|
+
* behind a shared secret header if you expose the endpoint to the
|
|
92
|
+
* public internet (it's fine on a private network).
|
|
93
|
+
*/
|
|
94
|
+
async bounce(body, res) {
|
|
95
|
+
const messageId = String((body === null || body === void 0 ? void 0 : body.messageId) || '').trim();
|
|
96
|
+
const status = (body === null || body === void 0 ? void 0 : body.status) === 'complained' ? 'complained' : 'bounced';
|
|
97
|
+
if (!messageId)
|
|
98
|
+
return res.status(400).json({ error: 'messageId required' });
|
|
99
|
+
const ok = await this.tracking.recordBounce(messageId, status, body === null || body === void 0 ? void 0 : body.reason);
|
|
100
|
+
return res.json({ ok, matched: ok });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Admin: list email events, with filtering for the "Email Log" page
|
|
104
|
+
* and the per-customer Emails tab. Filterable by customerId, orderId,
|
|
105
|
+
* orderCode, status, type, recipient, and a date range.
|
|
106
|
+
*
|
|
107
|
+
* GET /email-track/log?customerId=&orderId=&status=&take=
|
|
108
|
+
*/
|
|
109
|
+
async listLog(ctx, req, res) {
|
|
110
|
+
if (!requireAdmin(ctx, res, false))
|
|
111
|
+
return;
|
|
112
|
+
const q = req.query;
|
|
113
|
+
const take = Math.min(parseInt(q.take, 10) || 100, 500);
|
|
114
|
+
const skip = parseInt(q.skip, 10) || 0;
|
|
115
|
+
const where = [];
|
|
116
|
+
const params = [];
|
|
117
|
+
if (q.customerId) {
|
|
118
|
+
where.push('customerId = ?');
|
|
119
|
+
params.push(parseInt(q.customerId, 10));
|
|
120
|
+
}
|
|
121
|
+
if (q.orderId) {
|
|
122
|
+
where.push('orderId = ?');
|
|
123
|
+
params.push(parseInt(q.orderId, 10));
|
|
124
|
+
}
|
|
125
|
+
if (q.orderCode) {
|
|
126
|
+
where.push('orderCode = ?');
|
|
127
|
+
params.push(String(q.orderCode));
|
|
128
|
+
}
|
|
129
|
+
if (q.invoiceId) {
|
|
130
|
+
where.push('invoiceId = ?');
|
|
131
|
+
params.push(parseInt(q.invoiceId, 10));
|
|
132
|
+
}
|
|
133
|
+
if (q.status) {
|
|
134
|
+
where.push('status = ?');
|
|
135
|
+
params.push(String(q.status));
|
|
136
|
+
}
|
|
137
|
+
if (q.type) {
|
|
138
|
+
where.push('type = ?');
|
|
139
|
+
params.push(String(q.type));
|
|
140
|
+
}
|
|
141
|
+
if (q.recipient) {
|
|
142
|
+
where.push('recipient LIKE ?');
|
|
143
|
+
params.push(`%${String(q.recipient)}%`);
|
|
144
|
+
}
|
|
145
|
+
if (q.from) {
|
|
146
|
+
where.push('createdAt >= ?');
|
|
147
|
+
params.push(new Date(String(q.from)));
|
|
148
|
+
}
|
|
149
|
+
if (q.to) {
|
|
150
|
+
where.push('createdAt <= ?');
|
|
151
|
+
params.push(new Date(String(q.to)));
|
|
152
|
+
}
|
|
153
|
+
const whereClause = where.length ? ` WHERE ${where.join(' AND ')}` : '';
|
|
154
|
+
const rows = await this.connection.rawConnection.query(`SELECT id, createdAt, type, recipient, subject, status,
|
|
155
|
+
customerId, orderId, orderCode, invoiceId, applicationId,
|
|
156
|
+
channelId, openCount, firstOpenedAt, lastOpenedAt,
|
|
157
|
+
clickCount, firstClickedAt, smtpResponse, errorMessage
|
|
158
|
+
FROM email_log
|
|
159
|
+
${whereClause}
|
|
160
|
+
ORDER BY createdAt DESC
|
|
161
|
+
LIMIT ? OFFSET ?`, [...params, take, skip]);
|
|
162
|
+
const [{ total }] = await this.connection.rawConnection.query(`SELECT COUNT(*) AS total FROM email_log${whereClause}`, params);
|
|
163
|
+
return res.json({ items: rows, total: Number(total) || 0, take, skip });
|
|
164
|
+
}
|
|
165
|
+
/** Admin: aggregate counts by status for a quick dashboard tile. */
|
|
166
|
+
async logSummary(ctx, req, res) {
|
|
167
|
+
if (!requireAdmin(ctx, res, false))
|
|
168
|
+
return;
|
|
169
|
+
const fromDays = parseInt(req.query.fromDays, 10) || 30;
|
|
170
|
+
const rows = await this.connection.rawConnection.query(`SELECT status, COUNT(*) AS n, SUM(openCount) AS opens, SUM(clickCount) AS clicks
|
|
171
|
+
FROM email_log
|
|
172
|
+
WHERE createdAt >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
|
173
|
+
GROUP BY status`, [fromDays]);
|
|
174
|
+
const summary = { sent: 0, failed: 0, deferred: 0, bounced: 0, complained: 0, opens: 0, clicks: 0, fromDays };
|
|
175
|
+
for (const r of rows) {
|
|
176
|
+
summary[r.status] = Number(r.n);
|
|
177
|
+
summary.opens += Number(r.opens) || 0;
|
|
178
|
+
summary.clicks += Number(r.clicks) || 0;
|
|
179
|
+
}
|
|
180
|
+
return res.json(summary);
|
|
181
|
+
}
|
|
182
|
+
/** Admin: full detail for one event including the clicks JSON. */
|
|
183
|
+
async logDetail(ctx, idParam, res) {
|
|
184
|
+
if (!requireAdmin(ctx, res, false))
|
|
185
|
+
return;
|
|
186
|
+
const id = parseInt(idParam, 10);
|
|
187
|
+
if (!id)
|
|
188
|
+
return res.status(400).json({ error: 'Invalid id' });
|
|
189
|
+
const row = await this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog).findOne({ where: { id } });
|
|
190
|
+
if (!row)
|
|
191
|
+
return res.status(404).json({ error: 'Not found' });
|
|
192
|
+
let clicks = [];
|
|
193
|
+
try {
|
|
194
|
+
clicks = JSON.parse(row.clicksJson || '[]');
|
|
195
|
+
}
|
|
196
|
+
catch { }
|
|
197
|
+
return res.json({ ...row, clicks, clicksJson: undefined });
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
exports.EmailTrackingController = EmailTrackingController;
|
|
201
|
+
__decorate([
|
|
202
|
+
(0, common_1.Get)('open/:id.gif'),
|
|
203
|
+
__param(0, (0, common_1.Param)('id')),
|
|
204
|
+
__param(1, (0, common_1.Req)()),
|
|
205
|
+
__param(2, (0, common_1.Res)()),
|
|
206
|
+
__metadata("design:type", Function),
|
|
207
|
+
__metadata("design:paramtypes", [String, Object, Object]),
|
|
208
|
+
__metadata("design:returntype", Promise)
|
|
209
|
+
], EmailTrackingController.prototype, "open", null);
|
|
210
|
+
__decorate([
|
|
211
|
+
(0, common_1.Get)('click/:id'),
|
|
212
|
+
__param(0, (0, common_1.Param)('id')),
|
|
213
|
+
__param(1, (0, common_1.Query)('u')),
|
|
214
|
+
__param(2, (0, common_1.Req)()),
|
|
215
|
+
__param(3, (0, common_1.Res)()),
|
|
216
|
+
__metadata("design:type", Function),
|
|
217
|
+
__metadata("design:paramtypes", [String, String, Object, Object]),
|
|
218
|
+
__metadata("design:returntype", Promise)
|
|
219
|
+
], EmailTrackingController.prototype, "click", null);
|
|
220
|
+
__decorate([
|
|
221
|
+
(0, common_1.Post)('bounce'),
|
|
222
|
+
__param(0, (0, common_1.Body)()),
|
|
223
|
+
__param(1, (0, common_1.Res)()),
|
|
224
|
+
__metadata("design:type", Function),
|
|
225
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
226
|
+
__metadata("design:returntype", Promise)
|
|
227
|
+
], EmailTrackingController.prototype, "bounce", null);
|
|
228
|
+
__decorate([
|
|
229
|
+
(0, common_1.Get)('log'),
|
|
230
|
+
__param(0, (0, core_1.Ctx)()),
|
|
231
|
+
__param(1, (0, common_1.Req)()),
|
|
232
|
+
__param(2, (0, common_1.Res)()),
|
|
233
|
+
__metadata("design:type", Function),
|
|
234
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, Object]),
|
|
235
|
+
__metadata("design:returntype", Promise)
|
|
236
|
+
], EmailTrackingController.prototype, "listLog", null);
|
|
237
|
+
__decorate([
|
|
238
|
+
(0, common_1.Get)('log/summary'),
|
|
239
|
+
__param(0, (0, core_1.Ctx)()),
|
|
240
|
+
__param(1, (0, common_1.Req)()),
|
|
241
|
+
__param(2, (0, common_1.Res)()),
|
|
242
|
+
__metadata("design:type", Function),
|
|
243
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, Object]),
|
|
244
|
+
__metadata("design:returntype", Promise)
|
|
245
|
+
], EmailTrackingController.prototype, "logSummary", null);
|
|
246
|
+
__decorate([
|
|
247
|
+
(0, common_1.Get)('log/:id'),
|
|
248
|
+
__param(0, (0, core_1.Ctx)()),
|
|
249
|
+
__param(1, (0, common_1.Param)('id')),
|
|
250
|
+
__param(2, (0, common_1.Res)()),
|
|
251
|
+
__metadata("design:type", Function),
|
|
252
|
+
__metadata("design:paramtypes", [core_1.RequestContext, String, Object]),
|
|
253
|
+
__metadata("design:returntype", Promise)
|
|
254
|
+
], EmailTrackingController.prototype, "logDetail", null);
|
|
255
|
+
exports.EmailTrackingController = EmailTrackingController = __decorate([
|
|
256
|
+
(0, common_1.Controller)('email-track'),
|
|
257
|
+
__metadata("design:paramtypes", [core_1.TransactionalConnection,
|
|
258
|
+
email_tracking_service_1.EmailTrackingService])
|
|
259
|
+
], EmailTrackingController);
|
|
260
|
+
//# sourceMappingURL=email-tracking.controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-tracking.controller.js","sourceRoot":"","sources":["../src/email-tracking.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAqF;AACrF,wCAAiG;AAEjG,yDAA8C;AAC9C,qEAAgE;AAEhE,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAE5C,yEAAyE;AACzE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAC1B,0DAA0D,EAC1D,QAAQ,CACX,CAAC;AAEF,mDAA4C;AAC5C,SAAS,MAAM,CAAC,GAAY,IAAmB,OAAO,IAAA,yBAAS,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAEvE,SAAS,YAAY,CAAC,GAAmB,EAAE,GAAa,EAAE,KAAK,GAAG,KAAK;IACnE,IAAI,CAAC,CAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,YAAY,CAAA,EAAE,CAAC;QACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC3D,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAU,CAAC,YAAY,CAAC,CAAC;IAC/E,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QAClC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC5D,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AAChB,CAAC;AAGM,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAChC,YACY,UAAmC,EACnC,QAA8B;QAD9B,eAAU,GAAV,UAAU,CAAyB;QACnC,aAAQ,GAAR,QAAQ,CAAsB;IACvC,CAAC;IAEJ;;;;;;OAMG;IAEG,AAAN,KAAK,CAAC,IAAI,CAAc,OAAe,EAAS,GAAY,EAAS,GAAa;QAC9E,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,CAAW,IAAI,IAAI,CAAC;iBACjF,KAAK,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,aAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE,KAAK,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;QAC1F,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAC3C,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,kEAAkE,CAAC,CAAC;QACnG,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACpC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;OAMG;IAEG,AAAN,KAAK,CAAC,KAAK,CAAc,OAAe,EAAc,CAAS,EAAS,GAAY,EAAS,GAAa;QACtG,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACrC,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,YAAY,CAAW,IAAI,IAAI,CAAC;iBAC7F,KAAK,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,aAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,KAAK,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;QAC3F,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC3C,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;;;;;;OAYG;IAEG,AAAN,KAAK,CAAC,MAAM,CAAS,IAAS,EAAS,GAAa;QAChD,MAAM,SAAS,GAAG,MAAM,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,SAAS,KAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,MAAM,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,MAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;QACxE,IAAI,CAAC,SAAS;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC7E,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,CAAC,CAAC;QAC7E,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED;;;;;;OAMG;IAEG,AAAN,KAAK,CAAC,OAAO,CAAQ,GAAmB,EAAS,GAAY,EAAS,GAAa;QAC/E,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC;YAAE,OAAO;QAC3C,MAAM,CAAC,GAAG,GAAG,CAAC,KAAY,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAU,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QAC5F,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QACnF,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAAC,CAAC;QACnF,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC;QAAC,CAAC;QACzF,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAAC,CAAC;QAC1E,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAAC,CAAC;QACpE,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAC7F,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC;QACpF,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAAC,CAAC;QAEhF,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAClD;;;;;eAKG,WAAW;;8BAEI,EAClB,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAC1B,CAAC;QACF,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CACzD,0CAA0C,WAAW,EAAE,EAAE,MAAM,CAClE,CAAC;QACF,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,oEAAoE;IAE9D,AAAN,KAAK,CAAC,UAAU,CAAQ,GAAmB,EAAS,GAAY,EAAS,GAAa;QAClF,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC;YAAE,OAAO;QAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAE,GAAG,CAAC,KAAa,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,CAClD;;;6BAGiB,EACjB,CAAC,QAAQ,CAAC,CACb,CAAC;QACF,MAAM,OAAO,GAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;QACnH,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACnB,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,OAAO,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAED,kEAAkE;IAE5D,AAAN,KAAK,CAAC,SAAS,CAAQ,GAAmB,EAAe,OAAe,EAAS,GAAa;QAC1F,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC;YAAE,OAAO;QAC3C,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QAC9D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,aAAa,CAAC,2BAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACnG,IAAI,CAAC,GAAG;YAAE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QAC9D,IAAI,MAAM,GAAU,EAAE,CAAC;QACvB,IAAI,CAAC;YAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QAC7D,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/D,CAAC;CACJ,CAAA;AAnJY,0DAAuB;AAc1B;IADL,IAAA,YAAG,EAAC,cAAc,CAAC;IACR,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAmB,WAAA,IAAA,YAAG,GAAE,CAAA;IAAgB,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAWnE;AAUK;IADL,IAAA,YAAG,EAAC,WAAW,CAAC;IACJ,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAmB,WAAA,IAAA,cAAK,EAAC,GAAG,CAAC,CAAA;IAAa,WAAA,IAAA,YAAG,GAAE,CAAA;IAAgB,WAAA,IAAA,YAAG,GAAE,CAAA;;;;oDAY3F;AAgBK;IADL,IAAA,aAAI,EAAC,QAAQ,CAAC;IACD,WAAA,IAAA,aAAI,GAAE,CAAA;IAAa,WAAA,IAAA,YAAG,GAAE,CAAA;;;;qDAMrC;AAUK;IADL,IAAA,YAAG,EAAC,KAAK,CAAC;IACI,WAAA,IAAA,UAAG,GAAE,CAAA;IAAuB,WAAA,IAAA,YAAG,GAAE,CAAA;IAAgB,WAAA,IAAA,YAAG,GAAE,CAAA;;qCAA3C,qBAAc;;sDAiCvC;AAIK;IADL,IAAA,YAAG,EAAC,aAAa,CAAC;IACD,WAAA,IAAA,UAAG,GAAE,CAAA;IAAuB,WAAA,IAAA,YAAG,GAAE,CAAA;IAAgB,WAAA,IAAA,YAAG,GAAE,CAAA;;qCAA3C,qBAAc;;yDAiB1C;AAIK;IADL,IAAA,YAAG,EAAC,SAAS,CAAC;IACE,WAAA,IAAA,UAAG,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAmB,WAAA,IAAA,YAAG,GAAE,CAAA;;qCAApD,qBAAc;;wDASzC;kCAlJQ,uBAAuB;IADnC,IAAA,mBAAU,EAAC,aAAa,CAAC;qCAGE,8BAAuB;QACzB,6CAAoB;GAHjC,uBAAuB,CAmJnC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { TransactionalConnection } from '@vendure/core';
|
|
2
|
+
import * as nodemailer from 'nodemailer';
|
|
3
|
+
import { EmailLog } from './email-log.entity';
|
|
4
|
+
interface SendTrackedMeta {
|
|
5
|
+
/** Logical email type — e.g. 'welcome', 'order-confirmation', 'invoice'. */
|
|
6
|
+
type: string;
|
|
7
|
+
/** Optional context string, e.g. chase stage 'T-7' / 'due' / 'T+14'. */
|
|
8
|
+
context?: string;
|
|
9
|
+
/** Channel id (1 = elite, 2 = LD). Defaults to 1. */
|
|
10
|
+
channelId?: number;
|
|
11
|
+
customerId?: number;
|
|
12
|
+
orderId?: number;
|
|
13
|
+
orderCode?: string;
|
|
14
|
+
invoiceId?: number;
|
|
15
|
+
applicationId?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class EmailTrackingService {
|
|
18
|
+
private connection;
|
|
19
|
+
constructor(connection: TransactionalConnection);
|
|
20
|
+
/**
|
|
21
|
+
* Send an email through nodemailer with full tracking: creates an
|
|
22
|
+
* EmailLog row, injects a 1×1 open-tracking pixel, rewrites every
|
|
23
|
+
* `href` in the html to go through our click-tracker, records the
|
|
24
|
+
* SMTP response on success / failure.
|
|
25
|
+
*
|
|
26
|
+
* Returns the created EmailLog row (with id). Throws only if the
|
|
27
|
+
* transport configuration itself is invalid; SMTP delivery failures
|
|
28
|
+
* are caught and recorded as status='failed' / 'deferred'.
|
|
29
|
+
*/
|
|
30
|
+
sendTracked(transporter: nodemailer.Transporter, mail: nodemailer.SendMailOptions, meta: SendTrackedMeta): Promise<EmailLog>;
|
|
31
|
+
/** Wrap an HTML body for tracking: rewrite links + append open pixel. */
|
|
32
|
+
wrapHtml(html: string, eventId: number): string;
|
|
33
|
+
/** Rewrite every <a href> in the html to go through the click tracker.
|
|
34
|
+
* Skips mailto:, tel:, #anchors, our own tracking endpoints, and the
|
|
35
|
+
* unsubscribe link (always passthrough). */
|
|
36
|
+
private rewriteLinks;
|
|
37
|
+
/** Record a pixel-hit open. Idempotent re-counting; firstOpenedAt only
|
|
38
|
+
* set the first time. */
|
|
39
|
+
recordOpen(id: number, ip: string | null, ua: string | null): Promise<void>;
|
|
40
|
+
/** Record a click + return the original URL so the controller can 302. */
|
|
41
|
+
recordClick(id: number, url: string, ip: string | null, ua: string | null): Promise<string | null>;
|
|
42
|
+
/** SMTP DSN (bounce / complaint) hook — wire up if you point Postmaster
|
|
43
|
+
* Tools / a bounce-processor at /email-track/bounce. */
|
|
44
|
+
recordBounce(messageId: string, status: 'bounced' | 'complained', reason?: string): Promise<boolean>;
|
|
45
|
+
private firstAddress;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=email-tracking.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-tracking.service.d.ts","sourceRoot":"","sources":["../src/email-tracking.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,UAAU,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAkB,MAAM,oBAAoB,CAAC;AAK9D,UAAU,eAAe;IACrB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qBACa,oBAAoB;IACjB,OAAO,CAAC,UAAU;gBAAV,UAAU,EAAE,uBAAuB;IAEvD;;;;;;;;;OASG;IACG,WAAW,CACb,WAAW,EAAE,UAAU,CAAC,WAAW,EACnC,IAAI,EAAE,UAAU,CAAC,eAAe,EAChC,IAAI,EAAE,eAAe,GACtB,OAAO,CAAC,QAAQ,CAAC;IAwDpB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAW/C;;iDAE6C;IAC7C,OAAO,CAAC,YAAY;IAepB;8BAC0B;IACpB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAejE,0EAA0E;IACpE,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuBxG;6DACyD;IACnD,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,YAAY,EAAE,MAAM,CAAC,EAAE,MAAM;IAUvF,OAAO,CAAC,YAAY;CAOvB"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EmailTrackingService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const core_1 = require("@vendure/core");
|
|
15
|
+
const email_log_entity_1 = require("./email-log.entity");
|
|
16
|
+
const options_1 = require("./options");
|
|
17
|
+
const loggerCtx = 'EmailTrackingService';
|
|
18
|
+
let EmailTrackingService = class EmailTrackingService {
|
|
19
|
+
constructor(connection) {
|
|
20
|
+
this.connection = connection;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Send an email through nodemailer with full tracking: creates an
|
|
24
|
+
* EmailLog row, injects a 1×1 open-tracking pixel, rewrites every
|
|
25
|
+
* `href` in the html to go through our click-tracker, records the
|
|
26
|
+
* SMTP response on success / failure.
|
|
27
|
+
*
|
|
28
|
+
* Returns the created EmailLog row (with id). Throws only if the
|
|
29
|
+
* transport configuration itself is invalid; SMTP delivery failures
|
|
30
|
+
* are caught and recorded as status='failed' / 'deferred'.
|
|
31
|
+
*/
|
|
32
|
+
async sendTracked(transporter, mail, meta) {
|
|
33
|
+
const repo = this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog);
|
|
34
|
+
// Create the row up-front so the pixel/click URLs have an id to
|
|
35
|
+
// reference. status starts as 'sent' optimistically; we update
|
|
36
|
+
// it if the SMTP submit throws or returns 4xx/5xx.
|
|
37
|
+
const row = repo.create({
|
|
38
|
+
type: meta.type,
|
|
39
|
+
recipient: this.firstAddress(mail.to),
|
|
40
|
+
subject: String(mail.subject || ''),
|
|
41
|
+
fromAddress: this.firstAddress(mail.from),
|
|
42
|
+
bcc: this.firstAddress(mail.bcc),
|
|
43
|
+
replyTo: this.firstAddress(mail.replyTo),
|
|
44
|
+
context: meta.context,
|
|
45
|
+
channelId: meta.channelId || 1,
|
|
46
|
+
customerId: meta.customerId,
|
|
47
|
+
orderId: meta.orderId,
|
|
48
|
+
orderCode: meta.orderCode,
|
|
49
|
+
invoiceId: meta.invoiceId,
|
|
50
|
+
applicationId: meta.applicationId,
|
|
51
|
+
status: 'sent',
|
|
52
|
+
tracked: true,
|
|
53
|
+
});
|
|
54
|
+
const saved = await repo.save(row);
|
|
55
|
+
// Wrap the HTML — clicks first (so the pixel itself isn't rewritten),
|
|
56
|
+
// then the pixel append. Plain-text-only emails are unaffected.
|
|
57
|
+
const html = mail.html ? String(mail.html) : '';
|
|
58
|
+
if (html) {
|
|
59
|
+
const wrapped = this.wrapHtml(html, Number(saved.id));
|
|
60
|
+
mail = { ...mail, html: wrapped };
|
|
61
|
+
}
|
|
62
|
+
// Send.
|
|
63
|
+
try {
|
|
64
|
+
const info = await transporter.sendMail(mail);
|
|
65
|
+
saved.smtpResponse = String(info.response || '').slice(0, 500);
|
|
66
|
+
saved.smtpMessageId = String(info.messageId || '').slice(0, 500);
|
|
67
|
+
// Gmail's "250 2.0.0 OK" → sent. 4xx → deferred. 5xx → failed.
|
|
68
|
+
const code = parseInt((info.response || '').split(' ')[0], 10);
|
|
69
|
+
if (code >= 500)
|
|
70
|
+
saved.status = 'failed';
|
|
71
|
+
else if (code >= 400)
|
|
72
|
+
saved.status = 'deferred';
|
|
73
|
+
else
|
|
74
|
+
saved.status = 'sent';
|
|
75
|
+
await repo.save(saved);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
saved.status = 'failed';
|
|
79
|
+
saved.errorMessage = String((e === null || e === void 0 ? void 0 : e.message) || e).slice(0, 2000);
|
|
80
|
+
saved.smtpResponse = (e === null || e === void 0 ? void 0 : e.response) ? String(e.response).slice(0, 500) : '';
|
|
81
|
+
await repo.save(saved);
|
|
82
|
+
core_1.Logger.error(`Tracked send FAILED [${meta.type}] to ${saved.recipient}: ${e === null || e === void 0 ? void 0 : e.message}`, loggerCtx);
|
|
83
|
+
throw e;
|
|
84
|
+
}
|
|
85
|
+
core_1.Logger.info(`Tracked send OK [${meta.type}] to ${saved.recipient} (id=${saved.id})`, loggerCtx);
|
|
86
|
+
return saved;
|
|
87
|
+
}
|
|
88
|
+
/** Wrap an HTML body for tracking: rewrite links + append open pixel. */
|
|
89
|
+
wrapHtml(html, eventId) {
|
|
90
|
+
const base = (0, options_1.trackingBaseUrl)();
|
|
91
|
+
const trackedHtml = this.rewriteLinks(html, eventId, base);
|
|
92
|
+
const pixel = `<img src="${base}/email-track/open/${eventId}.gif" width="1" height="1" alt="" border="0" style="display:none;border:0;max-height:1px;max-width:1px;outline:none;overflow:hidden;visibility:hidden">`;
|
|
93
|
+
// Insert the pixel just before </body> if possible, otherwise append.
|
|
94
|
+
if (/<\/body>/i.test(trackedHtml)) {
|
|
95
|
+
return trackedHtml.replace(/<\/body>/i, `${pixel}</body>`);
|
|
96
|
+
}
|
|
97
|
+
return trackedHtml + pixel;
|
|
98
|
+
}
|
|
99
|
+
/** Rewrite every <a href> in the html to go through the click tracker.
|
|
100
|
+
* Skips mailto:, tel:, #anchors, our own tracking endpoints, and the
|
|
101
|
+
* unsubscribe link (always passthrough). */
|
|
102
|
+
rewriteLinks(html, eventId, base) {
|
|
103
|
+
const ownPrefixes = (0, options_1.ownTrackingPrefixes)();
|
|
104
|
+
return html.replace(/<a\b([^>]*?)\bhref\s*=\s*(["'])(.*?)\2/gi, (match, attrs, quote, url) => {
|
|
105
|
+
const trimmed = String(url).trim();
|
|
106
|
+
if (!trimmed)
|
|
107
|
+
return match;
|
|
108
|
+
if (/^(mailto:|tel:|#)/i.test(trimmed))
|
|
109
|
+
return match;
|
|
110
|
+
if (ownPrefixes.some(p => trimmed.startsWith(p)))
|
|
111
|
+
return match;
|
|
112
|
+
// unsubscribe / list-unsubscribe links — never rewrite (ESP rules
|
|
113
|
+
// and recipient privacy expectations).
|
|
114
|
+
if (/unsubscribe/i.test(trimmed) || /\bopt[-_]?out\b/i.test(trimmed))
|
|
115
|
+
return match;
|
|
116
|
+
const encoded = encodeURIComponent(trimmed);
|
|
117
|
+
return `<a${attrs}href=${quote}${base}/email-track/click/${eventId}?u=${encoded}${quote}`;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Record a pixel-hit open. Idempotent re-counting; firstOpenedAt only
|
|
121
|
+
* set the first time. */
|
|
122
|
+
async recordOpen(id, ip, ua) {
|
|
123
|
+
const repo = this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog);
|
|
124
|
+
const row = await repo.findOne({ where: { id } });
|
|
125
|
+
if (!row)
|
|
126
|
+
return;
|
|
127
|
+
const now = new Date();
|
|
128
|
+
row.lastOpenedAt = now;
|
|
129
|
+
row.openCount = (row.openCount || 0) + 1;
|
|
130
|
+
if (!row.firstOpenedAt) {
|
|
131
|
+
row.firstOpenedAt = now;
|
|
132
|
+
row.firstOpenIp = ip || undefined;
|
|
133
|
+
row.firstOpenUserAgent = ua ? ua.slice(0, 1000) : undefined;
|
|
134
|
+
}
|
|
135
|
+
await repo.save(row);
|
|
136
|
+
}
|
|
137
|
+
/** Record a click + return the original URL so the controller can 302. */
|
|
138
|
+
async recordClick(id, url, ip, ua) {
|
|
139
|
+
const repo = this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog);
|
|
140
|
+
const row = await repo.findOne({ where: { id } });
|
|
141
|
+
if (!row)
|
|
142
|
+
return null;
|
|
143
|
+
const now = new Date();
|
|
144
|
+
if (!row.firstClickedAt)
|
|
145
|
+
row.firstClickedAt = now;
|
|
146
|
+
row.clickCount = (row.clickCount || 0) + 1;
|
|
147
|
+
let clicks = [];
|
|
148
|
+
try {
|
|
149
|
+
clicks = JSON.parse(row.clicksJson || '[]');
|
|
150
|
+
}
|
|
151
|
+
catch { }
|
|
152
|
+
clicks.push({
|
|
153
|
+
url: url.slice(0, 1000),
|
|
154
|
+
ts: now.toISOString(),
|
|
155
|
+
ip,
|
|
156
|
+
ua: ua ? ua.slice(0, 300) : null,
|
|
157
|
+
});
|
|
158
|
+
// Cap the JSON at the most recent 50 events; older ones live as
|
|
159
|
+
// a per-row clickCount only.
|
|
160
|
+
if (clicks.length > 50)
|
|
161
|
+
clicks = clicks.slice(-50);
|
|
162
|
+
row.clicksJson = JSON.stringify(clicks);
|
|
163
|
+
await repo.save(row);
|
|
164
|
+
return url;
|
|
165
|
+
}
|
|
166
|
+
/** SMTP DSN (bounce / complaint) hook — wire up if you point Postmaster
|
|
167
|
+
* Tools / a bounce-processor at /email-track/bounce. */
|
|
168
|
+
async recordBounce(messageId, status, reason) {
|
|
169
|
+
const repo = this.connection.rawConnection.getRepository(email_log_entity_1.EmailLog);
|
|
170
|
+
const row = await repo.findOne({ where: { smtpMessageId: messageId } });
|
|
171
|
+
if (!row)
|
|
172
|
+
return false;
|
|
173
|
+
row.status = status;
|
|
174
|
+
if (reason)
|
|
175
|
+
row.errorMessage = reason.slice(0, 2000);
|
|
176
|
+
await repo.save(row);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
firstAddress(addr) {
|
|
180
|
+
if (!addr)
|
|
181
|
+
return undefined;
|
|
182
|
+
if (typeof addr === 'string')
|
|
183
|
+
return addr.slice(0, 500);
|
|
184
|
+
if (Array.isArray(addr))
|
|
185
|
+
return addr.map(a => typeof a === 'string' ? a : a === null || a === void 0 ? void 0 : a.address).filter(Boolean).join(', ').slice(0, 500);
|
|
186
|
+
if (addr.address)
|
|
187
|
+
return String(addr.address).slice(0, 500);
|
|
188
|
+
return String(addr).slice(0, 500);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
exports.EmailTrackingService = EmailTrackingService;
|
|
192
|
+
exports.EmailTrackingService = EmailTrackingService = __decorate([
|
|
193
|
+
(0, common_1.Injectable)(),
|
|
194
|
+
__metadata("design:paramtypes", [core_1.TransactionalConnection])
|
|
195
|
+
], EmailTrackingService);
|
|
196
|
+
//# sourceMappingURL=email-tracking.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-tracking.service.js","sourceRoot":"","sources":["../src/email-tracking.service.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@huloglobal/vendure-plugin-email-tracking` — public exports.
|
|
3
|
+
*
|
|
4
|
+
* Consumers wire the plugin into Vendure via `EmailTrackingPlugin.init()`,
|
|
5
|
+
* pass `new TrackingEmailSender()` to the `@vendure/email-plugin`'s
|
|
6
|
+
* `emailSender` option, and (optionally) inject `EmailTrackingService`
|
|
7
|
+
* into their own custom controllers to send tracked emails outside the
|
|
8
|
+
* email-plugin pipeline (e.g. ad-hoc transactional sends from plugin
|
|
9
|
+
* code).
|
|
10
|
+
*/
|
|
11
|
+
export { EmailTrackingPlugin } from './plugin';
|
|
12
|
+
export { TrackingEmailSender } from './tracking-email-sender';
|
|
13
|
+
export { EmailTrackingService } from './email-tracking.service';
|
|
14
|
+
export { EmailLog, EmailLogStatus } from './email-log.entity';
|
|
15
|
+
export { EmailTrackingPluginOptions } from './options';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@huloglobal/vendure-plugin-email-tracking` — public exports.
|
|
4
|
+
*
|
|
5
|
+
* Consumers wire the plugin into Vendure via `EmailTrackingPlugin.init()`,
|
|
6
|
+
* pass `new TrackingEmailSender()` to the `@vendure/email-plugin`'s
|
|
7
|
+
* `emailSender` option, and (optionally) inject `EmailTrackingService`
|
|
8
|
+
* into their own custom controllers to send tracked emails outside the
|
|
9
|
+
* email-plugin pipeline (e.g. ad-hoc transactional sends from plugin
|
|
10
|
+
* code).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.EmailLog = exports.EmailTrackingService = exports.TrackingEmailSender = exports.EmailTrackingPlugin = void 0;
|
|
14
|
+
var plugin_1 = require("./plugin");
|
|
15
|
+
Object.defineProperty(exports, "EmailTrackingPlugin", { enumerable: true, get: function () { return plugin_1.EmailTrackingPlugin; } });
|
|
16
|
+
var tracking_email_sender_1 = require("./tracking-email-sender");
|
|
17
|
+
Object.defineProperty(exports, "TrackingEmailSender", { enumerable: true, get: function () { return tracking_email_sender_1.TrackingEmailSender; } });
|
|
18
|
+
var email_tracking_service_1 = require("./email-tracking.service");
|
|
19
|
+
Object.defineProperty(exports, "EmailTrackingService", { enumerable: true, get: function () { return email_tracking_service_1.EmailTrackingService; } });
|
|
20
|
+
var email_log_entity_1 = require("./email-log.entity");
|
|
21
|
+
Object.defineProperty(exports, "EmailLog", { enumerable: true, get: function () { return email_log_entity_1.EmailLog; } });
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH,mCAA+C;AAAtC,6GAAA,mBAAmB,OAAA;AAC5B,iEAA8D;AAArD,4HAAA,mBAAmB,OAAA;AAC5B,mEAAgE;AAAvD,8HAAA,oBAAoB,OAAA;AAC7B,uDAA8D;AAArD,4GAAA,QAAQ,OAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-scoped plugin options. Populated by `EmailTrackingPlugin.init()`
|
|
3
|
+
* at boot and read by the service / sender / controller via the
|
|
4
|
+
* exported helpers below. Keeping options in module scope rather than
|
|
5
|
+
* threading them through every constructor avoids a refactor of the
|
|
6
|
+
* Nest providers — Nest creates services lazily and we want options
|
|
7
|
+
* available before that happens.
|
|
8
|
+
*/
|
|
9
|
+
import { LicenceStatus } from '@huloglobal/vendure-licence-sdk';
|
|
10
|
+
export interface EmailTrackingPluginOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Public base URL where the tracking endpoints are reachable. Must
|
|
13
|
+
* be the externally-resolvable hostname of your Vendure server —
|
|
14
|
+
* the pixel + click URLs we embed in outgoing email point to this
|
|
15
|
+
* host. Example: `https://shop.example.com`.
|
|
16
|
+
*/
|
|
17
|
+
publicBaseUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Required licence JWT for production use. Without a valid key the
|
|
20
|
+
* plugin still registers and writes basic delivery rows to the
|
|
21
|
+
* EmailLog table, but the open/click tracking endpoints respond
|
|
22
|
+
* with 410 Gone and the admin UI shows an "Unlicensed" banner.
|
|
23
|
+
*/
|
|
24
|
+
licenceKey?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Hostnames considered "self" — used by the click rewriter to
|
|
27
|
+
* detect that a link points to our tracking endpoint and shouldn't
|
|
28
|
+
* be rewritten a second time. Defaults to `[publicBaseUrl]` if
|
|
29
|
+
* omitted.
|
|
30
|
+
*/
|
|
31
|
+
trackedHosts?: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function setOptions(opts: EmailTrackingPluginOptions): void;
|
|
34
|
+
export declare function getOptions(): EmailTrackingPluginOptions;
|
|
35
|
+
export declare function setLicenceStatus(status: LicenceStatus): void;
|
|
36
|
+
export declare function getLicenceStatus(): LicenceStatus | null;
|
|
37
|
+
/** Public tracking base URL — used by both the click-rewriter and the
|
|
38
|
+
* open-pixel injector. Always returns a value without a trailing slash. */
|
|
39
|
+
export declare function trackingBaseUrl(): string;
|
|
40
|
+
/** Hostname prefixes the click-rewriter should treat as already-ours
|
|
41
|
+
* and skip rewriting. */
|
|
42
|
+
export declare function ownTrackingPrefixes(): string[];
|
|
43
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAEhE,MAAM,WAAW,0BAA0B;IACvC;;;;;OAKG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AASD,wBAAgB,UAAU,CAAC,IAAI,EAAE,0BAA0B,GAAG,IAAI,CAMjE;AAED,wBAAgB,UAAU,IAAI,0BAA0B,CAEvD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAE5D;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAEvD;AAED;4EAC4E;AAC5E,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;0BAC0B;AAC1B,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAK9C"}
|