@astralibx/email-rule-engine 12.1.0 → 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
@@ -263,27 +263,39 @@ interface EmailRuleEngineConfig {
263
263
  onRunStart?: (info: {
264
264
  rulesCount: number;
265
265
  triggeredBy: string;
266
+ runId: string;
266
267
  }) => void;
267
268
  onRuleStart?: (info: {
268
269
  ruleId: string;
269
270
  ruleName: string;
270
271
  matchedCount: number;
272
+ templateId: string;
273
+ runId: string;
271
274
  }) => void;
272
275
  onSend?: (info: {
273
276
  ruleId: string;
274
277
  ruleName: string;
275
278
  email: string;
276
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;
277
286
  }) => void;
278
287
  onRuleComplete?: (info: {
279
288
  ruleId: string;
280
289
  ruleName: string;
281
290
  stats: RuleRunStats;
291
+ templateId: string;
292
+ runId: string;
282
293
  }) => void;
283
294
  onRunComplete?: (info: {
284
295
  duration: number;
285
296
  totalStats: RuleRunStats;
286
297
  perRuleStats: PerRuleStats[];
298
+ runId: string;
287
299
  }) => void;
288
300
  beforeSend?: (params: BeforeSendParams) => Promise<BeforeSendResult>;
289
301
  };
package/dist/index.d.ts CHANGED
@@ -263,27 +263,39 @@ interface EmailRuleEngineConfig {
263
263
  onRunStart?: (info: {
264
264
  rulesCount: number;
265
265
  triggeredBy: string;
266
+ runId: string;
266
267
  }) => void;
267
268
  onRuleStart?: (info: {
268
269
  ruleId: string;
269
270
  ruleName: string;
270
271
  matchedCount: number;
272
+ templateId: string;
273
+ runId: string;
271
274
  }) => void;
272
275
  onSend?: (info: {
273
276
  ruleId: string;
274
277
  ruleName: string;
275
278
  email: string;
276
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;
277
286
  }) => void;
278
287
  onRuleComplete?: (info: {
279
288
  ruleId: string;
280
289
  ruleName: string;
281
290
  stats: RuleRunStats;
291
+ templateId: string;
292
+ runId: string;
282
293
  }) => void;
283
294
  onRunComplete?: (info: {
284
295
  duration: number;
285
296
  totalStats: RuleRunStats;
286
297
  perRuleStats: PerRuleStats[];
298
+ runId: string;
287
299
  }) => void;
288
300
  beforeSend?: (params: BeforeSendParams) => Promise<BeforeSendResult>;
289
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 };
@@ -1266,7 +1266,7 @@ var RuleRunnerService = class {
1266
1266
  } catch (hookErr) {
1267
1267
  this.logger.error(`beforeSend hook failed for email ${email}: ${hookErr.message}`);
1268
1268
  stats.errorCount++;
1269
- 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 });
1270
1270
  continue;
1271
1271
  }
1272
1272
  }
@@ -1294,7 +1294,7 @@ var RuleRunnerService = class {
1294
1294
  lastSentDate: /* @__PURE__ */ new Date()
1295
1295
  });
1296
1296
  stats.sent++;
1297
- 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 });
1298
1298
  totalProcessed++;
1299
1299
  if (runId && totalProcessed % 10 === 0) {
1300
1300
  await this.updateRunSendProgress(runId, stats);
@@ -1309,7 +1309,7 @@ var RuleRunnerService = class {
1309
1309
  }
1310
1310
  } catch (err) {
1311
1311
  stats.errorCount++;
1312
- 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" });
1313
1313
  this.logger.error(`Rule "${rule.name}" failed for identifier ${email}`, { error: err });
1314
1314
  }
1315
1315
  }
@@ -1332,7 +1332,7 @@ var RuleRunnerService = class {
1332
1332
  this.logger.info(`Rule '${rule.name}' auto-disabled \u2014 all identifiers processed`);
1333
1333
  }
1334
1334
  }
1335
- this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats });
1335
+ this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats, templateId, runId: runId || "" });
1336
1336
  return stats;
1337
1337
  }
1338
1338
  async executeQueryMode(rule, template, throttleMap, throttleConfig, stats, runId) {
@@ -1345,7 +1345,7 @@ var RuleRunnerService = class {
1345
1345
  return stats;
1346
1346
  }
1347
1347
  stats.matched = users.length;
1348
- 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 || "" });
1349
1349
  if (users.length === 0) return stats;
1350
1350
  const userIds = users.map((u) => u._id?.toString()).filter(Boolean);
1351
1351
  const emails = users.map((u) => u.email).filter(Boolean);
@@ -1394,40 +1394,40 @@ var RuleRunnerService = class {
1394
1394
  const email = user.email;
1395
1395
  if (!userId || !email) {
1396
1396
  stats.skipped++;
1397
- 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" });
1398
1398
  continue;
1399
1399
  }
1400
1400
  const lastSend = sendMap.get(userId);
1401
1401
  if (lastSend) {
1402
1402
  if (rule.sendOnce && !rule.resendAfterDays) {
1403
1403
  stats.skipped++;
1404
- 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" });
1405
1405
  continue;
1406
1406
  }
1407
1407
  if (rule.resendAfterDays) {
1408
1408
  const daysSince = (Date.now() - new Date(lastSend.sentAt).getTime()) / MS_PER_DAY;
1409
1409
  if (daysSince < rule.resendAfterDays) {
1410
1410
  stats.skipped++;
1411
- 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" });
1412
1412
  continue;
1413
1413
  }
1414
1414
  } else {
1415
1415
  stats.skipped++;
1416
- 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" });
1417
1417
  continue;
1418
1418
  }
1419
1419
  }
1420
1420
  const identifier = identifierMap.get(email.toLowerCase().trim());
1421
1421
  if (!identifier) {
1422
1422
  stats.skipped++;
1423
- 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" });
1424
1424
  continue;
1425
1425
  }
1426
- if (!this.checkThrottle(rule, userId, email, throttleMap, throttleConfig, stats)) continue;
1426
+ if (!this.checkThrottle(rule, userId, email, throttleMap, throttleConfig, stats, templateId, runId)) continue;
1427
1427
  const agentSelection = await this.config.adapters.selectAgent(identifier.id, { ruleId, templateId });
1428
1428
  if (!agentSelection) {
1429
1429
  stats.skipped++;
1430
- 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" });
1431
1431
  continue;
1432
1432
  }
1433
1433
  const resolvedData = this.config.adapters.resolveData(user);
@@ -1477,7 +1477,7 @@ var RuleRunnerService = class {
1477
1477
  } catch (hookErr) {
1478
1478
  this.logger.error(`beforeSend hook failed for email ${email}: ${hookErr.message}`);
1479
1479
  stats.errorCount++;
1480
- 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 });
1481
1481
  continue;
1482
1482
  }
1483
1483
  }
@@ -1505,7 +1505,7 @@ var RuleRunnerService = class {
1505
1505
  lastSentDate: /* @__PURE__ */ new Date()
1506
1506
  });
1507
1507
  stats.sent++;
1508
- 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 });
1509
1509
  totalProcessed++;
1510
1510
  if (runId && totalProcessed % 10 === 0) {
1511
1511
  await this.updateRunSendProgress(runId, stats);
@@ -1520,7 +1520,7 @@ var RuleRunnerService = class {
1520
1520
  }
1521
1521
  } catch (err) {
1522
1522
  stats.errorCount++;
1523
- 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" });
1524
1524
  this.logger.error(`Rule "${rule.name}" failed for user ${user._id?.toString()}`, { error: err });
1525
1525
  }
1526
1526
  }
@@ -1528,27 +1528,27 @@ var RuleRunnerService = class {
1528
1528
  $set: { lastRunAt: /* @__PURE__ */ new Date(), lastRunStats: stats },
1529
1529
  $inc: { totalSent: stats.sent, totalSkipped: stats.skipped }
1530
1530
  });
1531
- this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats });
1531
+ this.config.hooks?.onRuleComplete?.({ ruleId, ruleName: rule.name, stats, templateId, runId: runId || "" });
1532
1532
  return stats;
1533
1533
  }
1534
- checkThrottle(rule, userId, email, throttleMap, config, stats) {
1534
+ checkThrottle(rule, userId, email, throttleMap, config, stats, templateId, runId) {
1535
1535
  if (rule.emailType === EMAIL_TYPE.Transactional || rule.bypassThrottle) return true;
1536
1536
  const userThrottle = throttleMap.get(userId) || { today: 0, thisWeek: 0, lastSentDate: null };
1537
1537
  if (userThrottle.today >= config.maxPerUserPerDay) {
1538
1538
  stats.skippedByThrottle++;
1539
- 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" });
1540
1540
  return false;
1541
1541
  }
1542
1542
  if (userThrottle.thisWeek >= config.maxPerUserPerWeek) {
1543
1543
  stats.skippedByThrottle++;
1544
- 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" });
1545
1545
  return false;
1546
1546
  }
1547
1547
  if (userThrottle.lastSentDate) {
1548
1548
  const daysSinceLastSend = (Date.now() - userThrottle.lastSentDate.getTime()) / MS_PER_DAY;
1549
1549
  if (daysSinceLastSend < config.minGapDays) {
1550
1550
  stats.skippedByThrottle++;
1551
- 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" });
1552
1552
  return false;
1553
1553
  }
1554
1554
  }