@bernierllc/email-tracking 1.0.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.
Files changed (58) hide show
  1. package/.eslintrc.cjs +24 -0
  2. package/README.md +478 -0
  3. package/__mocks__/neverhub-adapter.ts +29 -0
  4. package/__tests__/EmailTracking.test.ts +334 -0
  5. package/__tests__/campaign-stats.test.ts +224 -0
  6. package/__tests__/config.test.ts +108 -0
  7. package/__tests__/integration.test.ts +244 -0
  8. package/__tests__/recipient-history.test.ts +210 -0
  9. package/__tests__/status-calculator.test.ts +141 -0
  10. package/coverage/clover.xml +187 -0
  11. package/coverage/coverage-final.json +6 -0
  12. package/coverage/lcov-report/EmailTracking.ts.html +973 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/config.ts.html +163 -0
  16. package/coverage/lcov-report/database.ts.html +622 -0
  17. package/coverage/lcov-report/favicon.png +0 -0
  18. package/coverage/lcov-report/index.html +176 -0
  19. package/coverage/lcov-report/prettify.css +1 -0
  20. package/coverage/lcov-report/prettify.js +2 -0
  21. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  22. package/coverage/lcov-report/sorter.js +210 -0
  23. package/coverage/lcov-report/status-calculator.ts.html +214 -0
  24. package/coverage/lcov-report/types.ts.html +430 -0
  25. package/coverage/lcov.info +356 -0
  26. package/dist/EmailTracking.d.ts +22 -0
  27. package/dist/EmailTracking.d.ts.map +1 -0
  28. package/dist/EmailTracking.js +253 -0
  29. package/dist/EmailTracking.js.map +1 -0
  30. package/dist/config.d.ts +3 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +27 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/database.d.ts +22 -0
  35. package/dist/database.d.ts.map +1 -0
  36. package/dist/database.js +156 -0
  37. package/dist/database.js.map +1 -0
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +33 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/status-calculator.d.ts +4 -0
  43. package/dist/status-calculator.d.ts.map +1 -0
  44. package/dist/status-calculator.js +40 -0
  45. package/dist/status-calculator.js.map +1 -0
  46. package/dist/types.d.ts +98 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +22 -0
  49. package/dist/types.js.map +1 -0
  50. package/jest.config.cjs +32 -0
  51. package/package.json +54 -0
  52. package/src/EmailTracking.ts +296 -0
  53. package/src/config.ts +26 -0
  54. package/src/database.ts +179 -0
  55. package/src/index.ts +12 -0
  56. package/src/status-calculator.ts +43 -0
  57. package/src/types.ts +115 -0
  58. package/tsconfig.json +31 -0
@@ -0,0 +1,356 @@
1
+ TN:
2
+ SF:src/EmailTracking.ts
3
+ FN:34,(anonymous_0)
4
+ FN:39,(anonymous_1)
5
+ FN:60,(anonymous_2)
6
+ FN:91,(anonymous_3)
7
+ FN:95,(anonymous_4)
8
+ FN:100,(anonymous_5)
9
+ FN:105,(anonymous_6)
10
+ FN:157,(anonymous_7)
11
+ FN:229,(anonymous_8)
12
+ FN:255,(anonymous_9)
13
+ FN:267,(anonymous_10)
14
+ FN:279,(anonymous_11)
15
+ FN:283,(anonymous_12)
16
+ FN:287,(anonymous_13)
17
+ FN:293,(anonymous_14)
18
+ FNF:15
19
+ FNH:11
20
+ FNDA:38,(anonymous_0)
21
+ FNDA:39,(anonymous_1)
22
+ FNDA:0,(anonymous_2)
23
+ FNDA:0,(anonymous_3)
24
+ FNDA:0,(anonymous_4)
25
+ FNDA:0,(anonymous_5)
26
+ FNDA:70,(anonymous_6)
27
+ FNDA:78,(anonymous_7)
28
+ FNDA:13,(anonymous_8)
29
+ FNDA:16,(anonymous_9)
30
+ FNDA:9,(anonymous_10)
31
+ FNDA:70,(anonymous_11)
32
+ FNDA:147,(anonymous_12)
33
+ FNDA:38,(anonymous_13)
34
+ FNDA:35,(anonymous_14)
35
+ DA:9,4
36
+ DA:10,4
37
+ DA:23,4
38
+ DA:24,4
39
+ DA:25,4
40
+ DA:27,4
41
+ DA:32,38
42
+ DA:35,38
43
+ DA:36,38
44
+ DA:40,39
45
+ DA:41,1
46
+ DA:45,38
47
+ DA:46,0
48
+ DA:47,0
49
+ DA:48,0
50
+ DA:52,38
51
+ DA:56,38
52
+ DA:57,38
53
+ DA:61,0
54
+ DA:63,0
55
+ DA:92,0
56
+ DA:95,0
57
+ DA:96,0
58
+ DA:100,0
59
+ DA:101,0
60
+ DA:106,70
61
+ DA:107,70
62
+ DA:124,70
63
+ DA:127,70
64
+ DA:134,70
65
+ DA:137,70
66
+ DA:138,69
67
+ DA:141,70
68
+ DA:147,70
69
+ DA:149,0
70
+ DA:150,0
71
+ DA:158,78
72
+ DA:160,78
73
+ DA:162,78
74
+ DA:163,1
75
+ DA:167,1
76
+ DA:171,77
77
+ DA:174,77
78
+ DA:183,77
79
+ DA:186,77
80
+ DA:188,77
81
+ DA:189,74
82
+ DA:192,74
83
+ DA:193,0
84
+ DA:209,77
85
+ DA:210,77
86
+ DA:213,77
87
+ DA:219,77
88
+ DA:221,0
89
+ DA:222,0
90
+ DA:230,13
91
+ DA:231,13
92
+ DA:232,13
93
+ DA:233,2
94
+ DA:236,11
95
+ DA:237,11
96
+ DA:239,11
97
+ DA:246,11
98
+ DA:248,0
99
+ DA:256,16
100
+ DA:257,16
101
+ DA:258,16
102
+ DA:260,0
103
+ DA:268,9
104
+ DA:269,9
105
+ DA:270,9
106
+ DA:272,0
107
+ DA:280,70
108
+ DA:284,147
109
+ DA:294,35
110
+ LF:75
111
+ LH:57
112
+ BRDA:34,0,0,15
113
+ BRDA:40,1,0,1
114
+ BRDA:45,2,0,0
115
+ BRDA:52,3,0,0
116
+ BRDA:52,3,1,38
117
+ BRDA:61,4,0,0
118
+ BRDA:92,5,0,0
119
+ BRDA:137,6,0,69
120
+ BRDA:152,7,0,0
121
+ BRDA:152,7,1,0
122
+ BRDA:162,8,0,1
123
+ BRDA:188,9,0,74
124
+ BRDA:192,10,0,0
125
+ BRDA:209,11,0,77
126
+ BRDA:224,12,0,0
127
+ BRDA:224,12,1,0
128
+ BRDA:232,13,0,2
129
+ BRDA:243,14,0,11
130
+ BRDA:243,14,1,0
131
+ BRDA:250,15,0,0
132
+ BRDA:250,15,1,0
133
+ BRDA:262,16,0,0
134
+ BRDA:262,16,1,0
135
+ BRDA:274,17,0,0
136
+ BRDA:274,17,1,0
137
+ BRF:25
138
+ BRH:9
139
+ end_of_record
140
+ TN:
141
+ SF:src/config.ts
142
+ FN:11,loadConfig
143
+ FNF:1
144
+ FNH:1
145
+ FNDA:48,loadConfig
146
+ DA:11,5
147
+ DA:12,48
148
+ LF:2
149
+ LH:2
150
+ BRDA:11,0,0,7
151
+ BRDA:13,1,0,17
152
+ BRDA:13,1,1,31
153
+ BRDA:15,2,0,31
154
+ BRDA:15,2,1,30
155
+ BRDA:16,3,0,15
156
+ BRDA:16,3,1,33
157
+ BRDA:18,4,0,33
158
+ BRDA:18,4,1,33
159
+ BRDA:19,5,0,16
160
+ BRDA:19,5,1,32
161
+ BRDA:21,6,0,32
162
+ BRDA:21,6,1,29
163
+ BRDA:22,7,0,24
164
+ BRDA:22,7,1,24
165
+ BRDA:24,8,0,24
166
+ BRDA:24,8,1,24
167
+ BRF:17
168
+ BRH:17
169
+ end_of_record
170
+ TN:
171
+ SF:src/database.ts
172
+ FN:21,(anonymous_0)
173
+ FN:27,(anonymous_1)
174
+ FN:32,(anonymous_2)
175
+ FN:36,(anonymous_3)
176
+ FN:42,(anonymous_4)
177
+ FN:51,(anonymous_5)
178
+ FN:57,(anonymous_6)
179
+ FN:61,(anonymous_7)
180
+ FN:117,(anonymous_8)
181
+ FN:137,(anonymous_9)
182
+ FN:139,(anonymous_10)
183
+ FN:142,(anonymous_11)
184
+ FN:146,(anonymous_12)
185
+ FN:147,(anonymous_13)
186
+ FN:150,(anonymous_14)
187
+ FN:151,(anonymous_15)
188
+ FN:152,(anonymous_16)
189
+ FN:173,(anonymous_17)
190
+ FNF:18
191
+ FNH:18
192
+ FNDA:38,(anonymous_0)
193
+ FNDA:70,(anonymous_1)
194
+ FNDA:90,(anonymous_2)
195
+ FNDA:78,(anonymous_3)
196
+ FNDA:74,(anonymous_4)
197
+ FNDA:147,(anonymous_5)
198
+ FNDA:11,(anonymous_6)
199
+ FNDA:146,(anonymous_7)
200
+ FNDA:16,(anonymous_8)
201
+ FNDA:9,(anonymous_9)
202
+ FNDA:42,(anonymous_10)
203
+ FNDA:34,(anonymous_11)
204
+ FNDA:34,(anonymous_12)
205
+ FNDA:34,(anonymous_13)
206
+ FNDA:34,(anonymous_14)
207
+ FNDA:34,(anonymous_15)
208
+ FNDA:34,(anonymous_16)
209
+ FNDA:35,(anonymous_17)
210
+ DA:9,4
211
+ DA:21,4
212
+ DA:22,38
213
+ DA:23,38
214
+ DA:24,38
215
+ DA:25,38
216
+ DA:28,70
217
+ DA:29,70
218
+ DA:33,90
219
+ DA:37,78
220
+ DA:38,78
221
+ DA:39,77
222
+ DA:43,74
223
+ DA:44,74
224
+ DA:45,74
225
+ DA:46,74
226
+ DA:47,74
227
+ DA:52,147
228
+ DA:53,147
229
+ DA:54,147
230
+ DA:58,11
231
+ DA:62,146
232
+ DA:63,147
233
+ DA:65,147
234
+ DA:66,55
235
+ DA:79,147
236
+ DA:81,147
237
+ DA:83,70
238
+ DA:84,70
239
+ DA:85,70
240
+ DA:87,33
241
+ DA:88,33
242
+ DA:89,33
243
+ DA:91,11
244
+ DA:92,11
245
+ DA:93,11
246
+ DA:94,11
247
+ DA:96,12
248
+ DA:97,12
249
+ DA:98,12
250
+ DA:100,11
251
+ DA:101,11
252
+ DA:102,11
253
+ DA:104,6
254
+ DA:105,6
255
+ DA:106,6
256
+ DA:108,4
257
+ DA:109,4
258
+ DA:110,4
259
+ DA:113,147
260
+ DA:118,16
261
+ DA:120,16
262
+ DA:121,3
263
+ DA:134,13
264
+ DA:138,9
265
+ DA:139,42
266
+ DA:141,9
267
+ DA:142,9
268
+ DA:143,34
269
+ DA:146,34
270
+ DA:147,9
271
+ DA:148,34
272
+ DA:150,34
273
+ DA:151,34
274
+ DA:152,34
275
+ DA:154,9
276
+ DA:155,9
277
+ DA:156,9
278
+ DA:158,9
279
+ DA:174,35
280
+ DA:175,35
281
+ DA:176,35
282
+ DA:177,35
283
+ LF:73
284
+ LH:73
285
+ BRDA:33,0,0,90
286
+ BRDA:33,0,1,2
287
+ BRDA:38,1,0,1
288
+ BRDA:44,2,0,74
289
+ BRDA:52,3,0,147
290
+ BRDA:52,3,1,70
291
+ BRDA:58,4,0,11
292
+ BRDA:58,4,1,0
293
+ BRDA:65,5,0,55
294
+ BRDA:81,6,0,70
295
+ BRDA:81,6,1,33
296
+ BRDA:81,6,2,11
297
+ BRDA:81,6,3,12
298
+ BRDA:81,6,4,11
299
+ BRDA:81,6,5,6
300
+ BRDA:81,6,6,4
301
+ BRDA:120,7,0,3
302
+ BRDA:154,8,0,7
303
+ BRDA:154,8,1,2
304
+ BRDA:155,9,0,4
305
+ BRDA:155,9,1,5
306
+ BRDA:156,10,0,2
307
+ BRDA:156,10,1,7
308
+ BRF:23
309
+ BRH:22
310
+ end_of_record
311
+ TN:
312
+ SF:src/status-calculator.ts
313
+ FN:22,calculateNewStatus
314
+ FN:31,mapWebhookEventType
315
+ FNF:2
316
+ FNH:2
317
+ FNDA:96,calculateNewStatus
318
+ FNDA:89,mapWebhookEventType
319
+ DA:9,5
320
+ DA:11,5
321
+ DA:22,5
322
+ DA:26,96
323
+ DA:31,5
324
+ DA:32,89
325
+ DA:42,89
326
+ LF:7
327
+ LH:7
328
+ BRDA:26,0,0,88
329
+ BRDA:26,0,1,8
330
+ BRDA:42,1,0,89
331
+ BRDA:42,1,1,2
332
+ BRF:4
333
+ BRH:4
334
+ end_of_record
335
+ TN:
336
+ SF:src/types.ts
337
+ FN:9,(anonymous_0)
338
+ FNF:1
339
+ FNH:1
340
+ FNDA:5,(anonymous_0)
341
+ DA:9,5
342
+ DA:10,5
343
+ DA:11,5
344
+ DA:12,5
345
+ DA:13,5
346
+ DA:14,5
347
+ DA:15,5
348
+ DA:16,5
349
+ DA:17,5
350
+ LF:9
351
+ LH:9
352
+ BRDA:9,0,0,5
353
+ BRDA:9,0,1,5
354
+ BRF:2
355
+ BRH:2
356
+ end_of_record
@@ -0,0 +1,22 @@
1
+ import { EmailSendRecord, EmailTimeline, RecipientHistory, TrackingConfig, EmailTrackingResult, RecordEmailSentData, WebhookEventData, CampaignStatistics } from './types.js';
2
+ export declare class EmailTracking {
3
+ private neverhub?;
4
+ private logger?;
5
+ private config;
6
+ private database;
7
+ private initialized;
8
+ constructor(config?: TrackingConfig);
9
+ initialize(): Promise<void>;
10
+ private registerWithNeverHub;
11
+ private setupEventSubscriptions;
12
+ recordEmailSent(data: RecordEmailSentData): Promise<EmailTrackingResult<EmailSendRecord>>;
13
+ processWebhookEvent(webhookEvent: WebhookEventData): Promise<EmailTrackingResult>;
14
+ getTimeline(trackingId: string): Promise<EmailTrackingResult<EmailTimeline>>;
15
+ getRecipientHistory(emailAddress: string): Promise<EmailTrackingResult<RecipientHistory>>;
16
+ getCampaignStats(campaignId: string): Promise<EmailTrackingResult<CampaignStatistics>>;
17
+ private generateTrackingId;
18
+ private generateEventId;
19
+ private initializeCore;
20
+ clearDatabase(): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=EmailTracking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailTracking.d.ts","sourceRoot":"","sources":["../src/EmailTracking.ts"],"names":[],"mappings":"AASA,OAAO,EACL,eAAe,EAEf,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAGhB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAKpB,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAC,CAAU;IACzB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,GAAE,cAAmB;IAKjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBnB,oBAAoB;YA+BpB,uBAAuB;IAc/B,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,eAAe,CAAC,CAAC;IAoDzF,mBAAmB,CAAC,YAAY,EAAE,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAwEjF,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC;IA0B5E,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAYzF,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;IAY5F,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,eAAe;YAIT,cAAc;IAMtB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.EmailTracking = void 0;
11
+ const neverhub_adapter_1 = require("@bernierllc/neverhub-adapter");
12
+ const types_js_1 = require("./types.js");
13
+ const config_js_1 = require("./config.js");
14
+ const status_calculator_js_1 = require("./status-calculator.js");
15
+ const database_js_1 = require("./database.js");
16
+ class EmailTracking {
17
+ constructor(config = {}) {
18
+ this.initialized = false;
19
+ this.config = (0, config_js_1.loadConfig)(config);
20
+ this.database = new database_js_1.Database();
21
+ }
22
+ async initialize() {
23
+ if (this.initialized) {
24
+ return;
25
+ }
26
+ // Auto-detect NeverHub
27
+ if (await neverhub_adapter_1.NeverHubAdapter.detect()) {
28
+ this.neverhub = new neverhub_adapter_1.NeverHubAdapter();
29
+ await this.registerWithNeverHub();
30
+ await this.setupEventSubscriptions();
31
+ }
32
+ // Initialize logger
33
+ this.logger = this.neverhub
34
+ ? await this.neverhub.getService('logging')
35
+ : console;
36
+ await this.initializeCore();
37
+ this.initialized = true;
38
+ }
39
+ async registerWithNeverHub() {
40
+ if (!this.neverhub)
41
+ return;
42
+ await this.neverhub.register({
43
+ type: 'email-tracking',
44
+ name: '@bernierllc/email-tracking',
45
+ version: '1.0.0',
46
+ dependencies: ['email-webhook-events', 'logging'],
47
+ capabilities: [
48
+ {
49
+ type: 'email',
50
+ name: 'delivery-tracking',
51
+ version: '1.0.0',
52
+ metadata: {
53
+ features: ['send-tracking', 'event-linking', 'timeline', 'recipient-history']
54
+ }
55
+ }
56
+ ],
57
+ discoveryEnabled: true,
58
+ discoverySubscriptions: [
59
+ {
60
+ pattern: 'capability.available',
61
+ handler: 'onCapabilityAvailable',
62
+ metadata: {
63
+ capabilityTypes: ['email', 'logging']
64
+ }
65
+ }
66
+ ]
67
+ });
68
+ }
69
+ async setupEventSubscriptions() {
70
+ if (!this.neverhub)
71
+ return;
72
+ // Subscribe to email sent events
73
+ await this.neverhub.subscribe('email.sent', async (event) => {
74
+ await this.recordEmailSent(event.data);
75
+ });
76
+ // Subscribe to normalized webhook events
77
+ await this.neverhub.subscribe('email.webhook.*', async (event) => {
78
+ await this.processWebhookEvent(event.data);
79
+ });
80
+ }
81
+ async recordEmailSent(data) {
82
+ try {
83
+ const sendRecord = {
84
+ id: this.generateTrackingId(),
85
+ messageId: data.messageId,
86
+ provider: data.provider,
87
+ from: data.from,
88
+ to: data.to,
89
+ cc: data.cc,
90
+ bcc: data.bcc,
91
+ subject: data.subject,
92
+ campaignId: data.campaignId,
93
+ metadata: data.metadata,
94
+ sentAt: new Date(),
95
+ currentStatus: types_js_1.EmailDeliveryStatus.SENT,
96
+ updatedAt: new Date()
97
+ };
98
+ // Store send record
99
+ await this.database.storeSendRecord(sendRecord);
100
+ // Create initial "sent" event
101
+ const event = {
102
+ id: this.generateEventId(),
103
+ sendRecordId: sendRecord.id,
104
+ eventType: types_js_1.EmailDeliveryStatus.SENT,
105
+ timestamp: sendRecord.sentAt,
106
+ metadata: { provider: data.provider }
107
+ };
108
+ await this.database.createEvent(event);
109
+ // Update recipient history
110
+ if (this.config.aggregateStats) {
111
+ await this.database.updateRecipientHistory(sendRecord.to, types_js_1.EmailDeliveryStatus.SENT);
112
+ }
113
+ this.logger?.log('Email send recorded', {
114
+ trackingId: sendRecord.id,
115
+ messageId: data.messageId,
116
+ recipients: data.to.length
117
+ });
118
+ return { success: true, data: sendRecord };
119
+ }
120
+ catch (error) {
121
+ this.logger?.error('Failed to record email send', { error });
122
+ return {
123
+ success: false,
124
+ error: error instanceof Error ? error.message : 'Unknown error'
125
+ };
126
+ }
127
+ }
128
+ async processWebhookEvent(webhookEvent) {
129
+ try {
130
+ // Find matching send record by messageId
131
+ const sendRecord = await this.database.findSendRecordByMessageId(webhookEvent.messageId);
132
+ if (!sendRecord) {
133
+ this.logger?.warn('No send record found for webhook event', {
134
+ messageId: webhookEvent.messageId,
135
+ eventType: webhookEvent.eventType
136
+ });
137
+ return { success: false, error: 'Send record not found' };
138
+ }
139
+ // Map webhook event type to our enum
140
+ const eventType = (0, status_calculator_js_1.mapWebhookEventType)(webhookEvent.eventType);
141
+ // Create event
142
+ const event = {
143
+ id: this.generateEventId(),
144
+ sendRecordId: sendRecord.id,
145
+ eventType,
146
+ timestamp: new Date(webhookEvent.timestamp),
147
+ metadata: webhookEvent.metadata,
148
+ sourceEvent: webhookEvent
149
+ };
150
+ await this.database.createEvent(event);
151
+ // Update send record status
152
+ const newStatus = (0, status_calculator_js_1.calculateNewStatus)(sendRecord.currentStatus, eventType);
153
+ if (newStatus !== sendRecord.currentStatus) {
154
+ await this.database.updateSendRecordStatus(sendRecord.id, newStatus);
155
+ // Publish status change event
156
+ if (this.neverhub) {
157
+ await this.neverhub.publishEvent({
158
+ type: 'email.status_changed',
159
+ data: {
160
+ trackingId: sendRecord.id,
161
+ messageId: sendRecord.messageId,
162
+ oldStatus: sendRecord.currentStatus,
163
+ newStatus: newStatus,
164
+ eventType: eventType,
165
+ recipient: sendRecord.to[0],
166
+ timestamp: event.timestamp
167
+ }
168
+ });
169
+ }
170
+ }
171
+ // Update recipient history
172
+ if (this.config.aggregateStats) {
173
+ await this.database.updateRecipientHistory(sendRecord.to, eventType);
174
+ }
175
+ this.logger?.log('Webhook event processed', {
176
+ trackingId: sendRecord.id,
177
+ eventType: eventType,
178
+ newStatus: newStatus
179
+ });
180
+ return { success: true };
181
+ }
182
+ catch (error) {
183
+ this.logger?.error('Failed to process webhook event', { error });
184
+ return {
185
+ success: false,
186
+ error: error instanceof Error ? error.message : 'Unknown error'
187
+ };
188
+ }
189
+ }
190
+ async getTimeline(trackingId) {
191
+ try {
192
+ const sendRecord = await this.database.getSendRecord(trackingId);
193
+ if (!sendRecord) {
194
+ return { success: false, error: 'Send record not found' };
195
+ }
196
+ const events = await this.database.getEventsBySendRecord(trackingId);
197
+ const lastEvent = events[events.length - 1];
198
+ const timeline = {
199
+ sendRecord,
200
+ events,
201
+ currentStatus: sendRecord.currentStatus,
202
+ lastEventAt: lastEvent ? lastEvent.timestamp : sendRecord.sentAt
203
+ };
204
+ return { success: true, data: timeline };
205
+ }
206
+ catch (error) {
207
+ return {
208
+ success: false,
209
+ error: error instanceof Error ? error.message : 'Unknown error'
210
+ };
211
+ }
212
+ }
213
+ async getRecipientHistory(emailAddress) {
214
+ try {
215
+ const history = await this.database.fetchRecipientHistory(emailAddress);
216
+ return { success: true, data: history };
217
+ }
218
+ catch (error) {
219
+ return {
220
+ success: false,
221
+ error: error instanceof Error ? error.message : 'Unknown error'
222
+ };
223
+ }
224
+ }
225
+ async getCampaignStats(campaignId) {
226
+ try {
227
+ const stats = await this.database.aggregateCampaignStats(campaignId);
228
+ return { success: true, data: stats };
229
+ }
230
+ catch (error) {
231
+ return {
232
+ success: false,
233
+ error: error instanceof Error ? error.message : 'Unknown error'
234
+ };
235
+ }
236
+ }
237
+ generateTrackingId() {
238
+ return `track_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
239
+ }
240
+ generateEventId() {
241
+ return `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
242
+ }
243
+ async initializeCore() {
244
+ // Core initialization (database connections, etc.)
245
+ // In production, this would set up real database connections
246
+ }
247
+ // Test helper to clear database
248
+ async clearDatabase() {
249
+ await this.database.clear();
250
+ }
251
+ }
252
+ exports.EmailTracking = EmailTracking;
253
+ //# sourceMappingURL=EmailTracking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmailTracking.js","sourceRoot":"","sources":["../src/EmailTracking.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { TrackingConfig } from './types.js';
2
+ export declare function loadConfig(userConfig?: TrackingConfig): Required<TrackingConfig>;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,wBAAgB,UAAU,CAAC,UAAU,GAAE,cAAmB,GAAG,QAAQ,CAAC,cAAc,CAAC,CAepF"}
package/dist/config.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ /*
3
+ Copyright (c) 2025 Bernier LLC
4
+
5
+ This file is licensed to the client under a limited-use license.
6
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
7
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.loadConfig = loadConfig;
11
+ function loadConfig(userConfig = {}) {
12
+ return {
13
+ enableOpenTracking: userConfig.enableOpenTracking !== undefined
14
+ ? userConfig.enableOpenTracking
15
+ : (process.env.EMAIL_TRACKING_ENABLE_OPEN_TRACKING === 'true' || process.env.EMAIL_TRACKING_ENABLE_OPEN_TRACKING === undefined),
16
+ enableClickTracking: userConfig.enableClickTracking !== undefined
17
+ ? userConfig.enableClickTracking
18
+ : (process.env.EMAIL_TRACKING_ENABLE_CLICK_TRACKING === 'true' || process.env.EMAIL_TRACKING_ENABLE_CLICK_TRACKING === undefined),
19
+ eventRetentionDays: userConfig.eventRetentionDays !== undefined
20
+ ? userConfig.eventRetentionDays
21
+ : parseInt(process.env.EMAIL_TRACKING_EVENT_RETENTION_DAYS || '90', 10),
22
+ aggregateStats: userConfig.aggregateStats !== undefined
23
+ ? userConfig.aggregateStats
24
+ : (process.env.EMAIL_TRACKING_AGGREGATE_STATS === 'true' || process.env.EMAIL_TRACKING_AGGREGATE_STATS === undefined)
25
+ };
26
+ }
27
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;;;;;EAME;;AAIF,gCAeC;AAfD,SAAgB,UAAU,CAAC,aAA6B,EAAE;IACxD,OAAO;QACL,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,KAAK,SAAS;YAC7D,CAAC,CAAC,UAAU,CAAC,kBAAkB;YAC/B,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,mCAAmC,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,mCAAmC,KAAK,SAAS,CAAC;QACjI,mBAAmB,EAAE,UAAU,CAAC,mBAAmB,KAAK,SAAS;YAC/D,CAAC,CAAC,UAAU,CAAC,mBAAmB;YAChC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,oCAAoC,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,oCAAoC,KAAK,SAAS,CAAC;QACnI,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,KAAK,SAAS;YAC7D,CAAC,CAAC,UAAU,CAAC,kBAAkB;YAC/B,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAI,IAAI,EAAE,EAAE,CAAC;QACzE,cAAc,EAAE,UAAU,CAAC,cAAc,KAAK,SAAS;YACrD,CAAC,CAAC,UAAU,CAAC,cAAc;YAC3B,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,8BAA8B,KAAK,SAAS,CAAC;KACxH,CAAC;AACJ,CAAC"}