@astralibx/email-rule-engine 12.0.1 → 12.2.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/dist/index.d.mts CHANGED
@@ -192,6 +192,11 @@ interface BeforeSendParams {
192
192
  email: string;
193
193
  name: string;
194
194
  };
195
+ context: {
196
+ ruleId: string;
197
+ templateId: string;
198
+ runId: string;
199
+ };
195
200
  }
196
201
  interface BeforeSendResult {
197
202
  htmlBody: string;
@@ -258,27 +263,39 @@ interface EmailRuleEngineConfig {
258
263
  onRunStart?: (info: {
259
264
  rulesCount: number;
260
265
  triggeredBy: string;
266
+ runId: string;
261
267
  }) => void;
262
268
  onRuleStart?: (info: {
263
269
  ruleId: string;
264
270
  ruleName: string;
265
271
  matchedCount: number;
272
+ templateId: string;
273
+ runId: string;
266
274
  }) => void;
267
275
  onSend?: (info: {
268
276
  ruleId: string;
269
277
  ruleName: string;
270
278
  email: string;
271
279
  status: 'sent' | 'error' | 'skipped' | 'invalid' | 'throttled';
280
+ accountId: string;
281
+ templateId: string;
282
+ runId: string;
283
+ subjectIndex: number;
284
+ bodyIndex: number;
285
+ failureReason?: string;
272
286
  }) => void;
273
287
  onRuleComplete?: (info: {
274
288
  ruleId: string;
275
289
  ruleName: string;
276
290
  stats: RuleRunStats;
291
+ templateId: string;
292
+ runId: string;
277
293
  }) => void;
278
294
  onRunComplete?: (info: {
279
295
  duration: number;
280
296
  totalStats: RuleRunStats;
281
297
  perRuleStats: PerRuleStats[];
298
+ runId: string;
282
299
  }) => void;
283
300
  beforeSend?: (params: BeforeSendParams) => Promise<BeforeSendResult>;
284
301
  };
package/dist/index.d.ts CHANGED
@@ -192,6 +192,11 @@ interface BeforeSendParams {
192
192
  email: string;
193
193
  name: string;
194
194
  };
195
+ context: {
196
+ ruleId: string;
197
+ templateId: string;
198
+ runId: string;
199
+ };
195
200
  }
196
201
  interface BeforeSendResult {
197
202
  htmlBody: string;
@@ -258,27 +263,39 @@ interface EmailRuleEngineConfig {
258
263
  onRunStart?: (info: {
259
264
  rulesCount: number;
260
265
  triggeredBy: string;
266
+ runId: string;
261
267
  }) => void;
262
268
  onRuleStart?: (info: {
263
269
  ruleId: string;
264
270
  ruleName: string;
265
271
  matchedCount: number;
272
+ templateId: string;
273
+ runId: string;
266
274
  }) => void;
267
275
  onSend?: (info: {
268
276
  ruleId: string;
269
277
  ruleName: string;
270
278
  email: string;
271
279
  status: 'sent' | 'error' | 'skipped' | 'invalid' | 'throttled';
280
+ accountId: string;
281
+ templateId: string;
282
+ runId: string;
283
+ subjectIndex: number;
284
+ bodyIndex: number;
285
+ failureReason?: string;
272
286
  }) => void;
273
287
  onRuleComplete?: (info: {
274
288
  ruleId: string;
275
289
  ruleName: string;
276
290
  stats: RuleRunStats;
291
+ templateId: string;
292
+ runId: string;
277
293
  }) => void;
278
294
  onRunComplete?: (info: {
279
295
  duration: number;
280
296
  totalStats: RuleRunStats;
281
297
  perRuleStats: PerRuleStats[];
298
+ runId: string;
282
299
  }) => void;
283
300
  beforeSend?: (params: BeforeSendParams) => Promise<BeforeSendResult>;
284
301
  };
package/dist/index.mjs CHANGED
@@ -1014,7 +1014,7 @@ var RuleRunnerService = class {
1014
1014
  }
1015
1015
  return true;
1016
1016
  });
1017
- this.config.hooks?.onRunStart?.({ rulesCount: activeRules.length, triggeredBy });
1017
+ this.config.hooks?.onRunStart?.({ rulesCount: activeRules.length, triggeredBy, runId });
1018
1018
  await this.updateRunProgress(runId, {
1019
1019
  progress: { rulesTotal: activeRules.length, rulesCompleted: 0, sent: 0, failed: 0, skipped: 0, invalid: 0 }
1020
1020
  });
@@ -1103,7 +1103,7 @@ var RuleRunnerService = class {
1103
1103
  status: runStatus
1104
1104
  });
1105
1105
  await this.updateRunProgress(runId, { status: runStatus, currentRule: "", elapsed: Date.now() - runStartTime });
1106
- this.config.hooks?.onRunComplete?.({ duration: Date.now() - runStartTime, totalStats, perRuleStats });
1106
+ this.config.hooks?.onRunComplete?.({ duration: Date.now() - runStartTime, totalStats, perRuleStats, runId });
1107
1107
  this.logger.info("Rule run completed", {
1108
1108
  triggeredBy,
1109
1109
  rulesProcessed: activeRules.length,
@@ -1142,7 +1142,7 @@ var RuleRunnerService = class {
1142
1142
  stats.matched = emailsToProcess.length;
1143
1143
  const ruleId = rule._id.toString();
1144
1144
  const templateId = rule.templateId.toString();
1145
- this.config.hooks?.onRuleStart?.({ ruleId, ruleName: rule.name, matchedCount: emailsToProcess.length });
1145
+ this.config.hooks?.onRuleStart?.({ ruleId, ruleName: rule.name, matchedCount: emailsToProcess.length, templateId, runId: runId || "" });
1146
1146
  if (emailsToProcess.length === 0) return stats;
1147
1147
  const identifierResults = await processInChunks(
1148
1148
  emailsToProcess,
@@ -1187,7 +1187,7 @@ var RuleRunnerService = class {
1187
1187
  const identifier = identifierMap.get(email);
1188
1188
  if (!identifier) {
1189
1189
  stats.skipped++;
1190
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "invalid" });
1190
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "invalid", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "invalid email" });
1191
1191
  continue;
1192
1192
  }
1193
1193
  const dedupKey = identifier.id;
@@ -1195,27 +1195,27 @@ var RuleRunnerService = class {
1195
1195
  if (lastSend) {
1196
1196
  if (rule.sendOnce && !rule.resendAfterDays) {
1197
1197
  stats.skipped++;
1198
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1198
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "send once" });
1199
1199
  continue;
1200
1200
  }
1201
1201
  if (rule.resendAfterDays) {
1202
1202
  const daysSince = (Date.now() - new Date(lastSend.sentAt).getTime()) / MS_PER_DAY;
1203
1203
  if (daysSince < rule.resendAfterDays) {
1204
1204
  stats.skipped++;
1205
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1205
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "resend too soon" });
1206
1206
  continue;
1207
1207
  }
1208
1208
  } else {
1209
1209
  stats.skipped++;
1210
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1210
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "send once" });
1211
1211
  continue;
1212
1212
  }
1213
1213
  }
1214
- if (!this.checkThrottle(rule, dedupKey, email, throttleMap, throttleConfig, stats)) continue;
1214
+ if (!this.checkThrottle(rule, dedupKey, email, throttleMap, throttleConfig, stats, templateId, runId)) continue;
1215
1215
  const agentSelection = await this.config.adapters.selectAgent(identifier.id, { ruleId, templateId });
1216
1216
  if (!agentSelection) {
1217
1217
  stats.skipped++;
1218
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1218
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "no account available" });
1219
1219
  continue;
1220
1220
  }
1221
1221
  const user = { _id: identifier.id, email };
@@ -1253,6 +1253,11 @@ var RuleRunnerService = class {
1253
1253
  id: dedupKey,
1254
1254
  email,
1255
1255
  name: ""
1256
+ },
1257
+ context: {
1258
+ ruleId,
1259
+ templateId,
1260
+ runId: runId || ""
1256
1261
  }
1257
1262
  });
1258
1263
  finalHtml = modified.htmlBody;
@@ -1261,7 +1266,7 @@ var RuleRunnerService = class {
1261
1266
  } catch (hookErr) {
1262
1267
  this.logger.error(`beforeSend hook failed for email ${email}: ${hookErr.message}`);
1263
1268
  stats.errorCount++;
1264
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error" });
1269
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error", accountId: agentSelection.accountId, templateId, runId: runId || "", subjectIndex: si, bodyIndex: bi, failureReason: hookErr.message });
1265
1270
  continue;
1266
1271
  }
1267
1272
  }
@@ -1289,7 +1294,7 @@ var RuleRunnerService = class {
1289
1294
  lastSentDate: /* @__PURE__ */ new Date()
1290
1295
  });
1291
1296
  stats.sent++;
1292
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "sent" });
1297
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "sent", accountId: agentSelection.accountId, templateId, runId: runId || "", subjectIndex: si, bodyIndex: bi });
1293
1298
  totalProcessed++;
1294
1299
  if (runId && totalProcessed % 10 === 0) {
1295
1300
  await this.updateRunSendProgress(runId, stats);
@@ -1304,7 +1309,7 @@ var RuleRunnerService = class {
1304
1309
  }
1305
1310
  } catch (err) {
1306
1311
  stats.errorCount++;
1307
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error" });
1312
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: err.message || "unknown error" });
1308
1313
  this.logger.error(`Rule "${rule.name}" failed for identifier ${email}`, { error: err });
1309
1314
  }
1310
1315
  }
@@ -1327,7 +1332,7 @@ var RuleRunnerService = class {
1327
1332
  this.logger.info(`Rule '${rule.name}' auto-disabled \u2014 all identifiers processed`);
1328
1333
  }
1329
1334
  }
1330
- this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats });
1335
+ this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats, templateId, runId: runId || "" });
1331
1336
  return stats;
1332
1337
  }
1333
1338
  async executeQueryMode(rule, template, throttleMap, throttleConfig, stats, runId) {
@@ -1340,7 +1345,7 @@ var RuleRunnerService = class {
1340
1345
  return stats;
1341
1346
  }
1342
1347
  stats.matched = users.length;
1343
- this.config.hooks?.onRuleStart?.({ ruleId: rule._id.toString(), ruleName: rule.name, matchedCount: users.length });
1348
+ this.config.hooks?.onRuleStart?.({ ruleId: rule._id.toString(), ruleName: rule.name, matchedCount: users.length, templateId: rule.templateId.toString(), runId: runId || "" });
1344
1349
  if (users.length === 0) return stats;
1345
1350
  const userIds = users.map((u) => u._id?.toString()).filter(Boolean);
1346
1351
  const emails = users.map((u) => u.email).filter(Boolean);
@@ -1389,40 +1394,40 @@ var RuleRunnerService = class {
1389
1394
  const email = user.email;
1390
1395
  if (!userId || !email) {
1391
1396
  stats.skipped++;
1392
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email: email || "unknown", status: "invalid" });
1397
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email: email || "unknown", status: "invalid", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "invalid email" });
1393
1398
  continue;
1394
1399
  }
1395
1400
  const lastSend = sendMap.get(userId);
1396
1401
  if (lastSend) {
1397
1402
  if (rule.sendOnce && !rule.resendAfterDays) {
1398
1403
  stats.skipped++;
1399
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1404
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "send once" });
1400
1405
  continue;
1401
1406
  }
1402
1407
  if (rule.resendAfterDays) {
1403
1408
  const daysSince = (Date.now() - new Date(lastSend.sentAt).getTime()) / MS_PER_DAY;
1404
1409
  if (daysSince < rule.resendAfterDays) {
1405
1410
  stats.skipped++;
1406
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1411
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "resend too soon" });
1407
1412
  continue;
1408
1413
  }
1409
1414
  } else {
1410
1415
  stats.skipped++;
1411
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1416
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "send once" });
1412
1417
  continue;
1413
1418
  }
1414
1419
  }
1415
1420
  const identifier = identifierMap.get(email.toLowerCase().trim());
1416
1421
  if (!identifier) {
1417
1422
  stats.skipped++;
1418
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "invalid" });
1423
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "invalid", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "invalid email" });
1419
1424
  continue;
1420
1425
  }
1421
- if (!this.checkThrottle(rule, userId, email, throttleMap, throttleConfig, stats)) continue;
1426
+ if (!this.checkThrottle(rule, userId, email, throttleMap, throttleConfig, stats, templateId, runId)) continue;
1422
1427
  const agentSelection = await this.config.adapters.selectAgent(identifier.id, { ruleId, templateId });
1423
1428
  if (!agentSelection) {
1424
1429
  stats.skipped++;
1425
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped" });
1430
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "skipped", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "no account available" });
1426
1431
  continue;
1427
1432
  }
1428
1433
  const resolvedData = this.config.adapters.resolveData(user);
@@ -1459,6 +1464,11 @@ var RuleRunnerService = class {
1459
1464
  id: String(userId),
1460
1465
  email,
1461
1466
  name: String(user.name || user.firstName || "")
1467
+ },
1468
+ context: {
1469
+ ruleId,
1470
+ templateId,
1471
+ runId: runId || ""
1462
1472
  }
1463
1473
  });
1464
1474
  finalHtml = modified.htmlBody;
@@ -1467,7 +1477,7 @@ var RuleRunnerService = class {
1467
1477
  } catch (hookErr) {
1468
1478
  this.logger.error(`beforeSend hook failed for email ${email}: ${hookErr.message}`);
1469
1479
  stats.errorCount++;
1470
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error" });
1480
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "error", accountId: agentSelection.accountId, templateId, runId: runId || "", subjectIndex: si, bodyIndex: bi, failureReason: hookErr.message });
1471
1481
  continue;
1472
1482
  }
1473
1483
  }
@@ -1495,7 +1505,7 @@ var RuleRunnerService = class {
1495
1505
  lastSentDate: /* @__PURE__ */ new Date()
1496
1506
  });
1497
1507
  stats.sent++;
1498
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "sent" });
1508
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email, status: "sent", accountId: agentSelection.accountId, templateId, runId: runId || "", subjectIndex: si, bodyIndex: bi });
1499
1509
  totalProcessed++;
1500
1510
  if (runId && totalProcessed % 10 === 0) {
1501
1511
  await this.updateRunSendProgress(runId, stats);
@@ -1510,7 +1520,7 @@ var RuleRunnerService = class {
1510
1520
  }
1511
1521
  } catch (err) {
1512
1522
  stats.errorCount++;
1513
- this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email: user.email || "unknown", status: "error" });
1523
+ this.config.hooks?.onSend?.({ ruleId, ruleName: rule.name, email: user.email || "unknown", status: "error", accountId: "", templateId, runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: err.message || "unknown error" });
1514
1524
  this.logger.error(`Rule "${rule.name}" failed for user ${user._id?.toString()}`, { error: err });
1515
1525
  }
1516
1526
  }
@@ -1518,27 +1528,27 @@ var RuleRunnerService = class {
1518
1528
  $set: { lastRunAt: /* @__PURE__ */ new Date(), lastRunStats: stats },
1519
1529
  $inc: { totalSent: stats.sent, totalSkipped: stats.skipped }
1520
1530
  });
1521
- this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats });
1531
+ this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats, templateId, runId: runId || "" });
1522
1532
  return stats;
1523
1533
  }
1524
- checkThrottle(rule, userId, email, throttleMap, config, stats) {
1534
+ checkThrottle(rule, userId, email, throttleMap, config, stats, templateId, runId) {
1525
1535
  if (rule.emailType === EMAIL_TYPE.Transactional || rule.bypassThrottle) return true;
1526
1536
  const userThrottle = throttleMap.get(userId) || { today: 0, thisWeek: 0, lastSentDate: null };
1527
1537
  if (userThrottle.today >= config.maxPerUserPerDay) {
1528
1538
  stats.skippedByThrottle++;
1529
- this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled" });
1539
+ this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled", accountId: "", templateId: templateId || "", runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "daily throttle limit" });
1530
1540
  return false;
1531
1541
  }
1532
1542
  if (userThrottle.thisWeek >= config.maxPerUserPerWeek) {
1533
1543
  stats.skippedByThrottle++;
1534
- this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled" });
1544
+ this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled", accountId: "", templateId: templateId || "", runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "weekly throttle limit" });
1535
1545
  return false;
1536
1546
  }
1537
1547
  if (userThrottle.lastSentDate) {
1538
1548
  const daysSinceLastSend = (Date.now() - userThrottle.lastSentDate.getTime()) / MS_PER_DAY;
1539
1549
  if (daysSinceLastSend < config.minGapDays) {
1540
1550
  stats.skippedByThrottle++;
1541
- this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled" });
1551
+ this.config.hooks?.onSend?.({ ruleId: rule._id.toString(), ruleName: rule.name, email, status: "throttled", accountId: "", templateId: templateId || "", runId: runId || "", subjectIndex: -1, bodyIndex: -1, failureReason: "min gap days" });
1542
1552
  return false;
1543
1553
  }
1544
1554
  }