mailery 0.12.0 → 0.13.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.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as MailTesterFeedback, d as Mailer, T as TemplateDoc, e as SuppressionScope, D as DeliveryWindow, F as FlowStep, f as SegmentFilter, P as Predicate } from './null-CfzvqNEp.cjs';
2
- export { g as AuditLogDoc, B as BroadcastDoc, h as BroadcastStatus, i as CircuitBreakerThresholds, j as Collections, k as ContactTagDoc, E as EventDoc, l as FlowDoc, m as FlowGoal, n as FlowRunDoc, o as FlowRunStatus, p as FlowVersionDoc, H as HealthDoc, q as HealthStatus, L as LeadDoc, r as MailerConfig, s as NullProvider, O as OutboxDoc, t as RESERVED_VAR_KEYS, u as RedisOptions, v as SegmentDefinition, w as SendDoc, x as SendStatus, y as SenderDomainConfig, z as SenderDomainRegistry, G as SenderDomainValidation, I as SubscriptionDoc, J as SubscriptionStatus, K as SuppressionDoc, Q as SuppressionReason, U as TemplateKind, V as TemplateVersionDoc, W as VarsAdapter, X as VarsResolveInfo, Y as WebhookEventDoc, Z as defineVars, _ as ensureIndexes, $ as getCollections, a0 as validateSenderDomain, a1 as varsJsonSchema } from './null-CfzvqNEp.cjs';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as MailTesterFeedback, d as Mailer, T as TemplateDoc, e as SuppressionScope, D as DeliveryWindow, F as FlowStep, f as SegmentFilter, P as Predicate } from './null-DDg3ojHe.cjs';
2
+ export { g as AuditLogDoc, B as BroadcastDoc, h as BroadcastStatus, i as CircuitBreakerThresholds, j as Collections, k as ContactTagDoc, E as EventDoc, l as FlowDoc, m as FlowGoal, n as FlowRunDoc, o as FlowRunStatus, p as FlowVersionDoc, H as HealthDoc, q as HealthStatus, L as LeadDoc, r as MailerConfig, s as NullProvider, O as OutboxDoc, t as RESERVED_VAR_KEYS, u as RedisOptions, v as SegmentDefinition, w as SendDoc, x as SendStatus, y as SenderDomainConfig, z as SenderDomainRegistry, G as SenderDomainValidation, I as SubscriptionDoc, J as SubscriptionStatus, K as SuppressionDoc, Q as SuppressionReason, U as TemplateKind, V as TemplateVersionDoc, W as VarsAdapter, X as VarsResolveInfo, Y as WebhookEventDoc, Z as defineVars, _ as ensureIndexes, $ as getCollections, a0 as validateSenderDomain, a1 as varsJsonSchema } from './null-DDg3ojHe.cjs';
3
3
  import { ObjectId, Db, Filter } from 'mongodb';
4
4
  import { Request, Router } from 'express';
5
5
  import Handlebars from 'handlebars';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as MailTesterFeedback, d as Mailer, T as TemplateDoc, e as SuppressionScope, D as DeliveryWindow, F as FlowStep, f as SegmentFilter, P as Predicate } from './null-CfzvqNEp.js';
2
- export { g as AuditLogDoc, B as BroadcastDoc, h as BroadcastStatus, i as CircuitBreakerThresholds, j as Collections, k as ContactTagDoc, E as EventDoc, l as FlowDoc, m as FlowGoal, n as FlowRunDoc, o as FlowRunStatus, p as FlowVersionDoc, H as HealthDoc, q as HealthStatus, L as LeadDoc, r as MailerConfig, s as NullProvider, O as OutboxDoc, t as RESERVED_VAR_KEYS, u as RedisOptions, v as SegmentDefinition, w as SendDoc, x as SendStatus, y as SenderDomainConfig, z as SenderDomainRegistry, G as SenderDomainValidation, I as SubscriptionDoc, J as SubscriptionStatus, K as SuppressionDoc, Q as SuppressionReason, U as TemplateKind, V as TemplateVersionDoc, W as VarsAdapter, X as VarsResolveInfo, Y as WebhookEventDoc, Z as defineVars, _ as ensureIndexes, $ as getCollections, a0 as validateSenderDomain, a1 as varsJsonSchema } from './null-CfzvqNEp.js';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as MailTesterFeedback, d as Mailer, T as TemplateDoc, e as SuppressionScope, D as DeliveryWindow, F as FlowStep, f as SegmentFilter, P as Predicate } from './null-DDg3ojHe.js';
2
+ export { g as AuditLogDoc, B as BroadcastDoc, h as BroadcastStatus, i as CircuitBreakerThresholds, j as Collections, k as ContactTagDoc, E as EventDoc, l as FlowDoc, m as FlowGoal, n as FlowRunDoc, o as FlowRunStatus, p as FlowVersionDoc, H as HealthDoc, q as HealthStatus, L as LeadDoc, r as MailerConfig, s as NullProvider, O as OutboxDoc, t as RESERVED_VAR_KEYS, u as RedisOptions, v as SegmentDefinition, w as SendDoc, x as SendStatus, y as SenderDomainConfig, z as SenderDomainRegistry, G as SenderDomainValidation, I as SubscriptionDoc, J as SubscriptionStatus, K as SuppressionDoc, Q as SuppressionReason, U as TemplateKind, V as TemplateVersionDoc, W as VarsAdapter, X as VarsResolveInfo, Y as WebhookEventDoc, Z as defineVars, _ as ensureIndexes, $ as getCollections, a0 as validateSenderDomain, a1 as varsJsonSchema } from './null-DDg3ojHe.js';
3
3
  import { ObjectId, Db, Filter } from 'mongodb';
4
4
  import { Request, Router } from 'express';
5
5
  import Handlebars from 'handlebars';
package/dist/index.js CHANGED
@@ -1051,6 +1051,9 @@ var QUEUE_NAMES2 = {
1051
1051
  send: "mailer-send",
1052
1052
  webhook: "mailer-webhook"
1053
1053
  };
1054
+ var DEFAULT_COLLECTION = "_mailerJobs";
1055
+ var DEFAULT_FAILED_RETENTION_DAYS = 7;
1056
+ var FAILED_SWEEP_INTERVAL_MS = 60 * 60 * 1e3;
1054
1057
  var AgendaDriver = class _AgendaDriver {
1055
1058
  queues;
1056
1059
  agenda;
@@ -1068,7 +1071,7 @@ var AgendaDriver = class _AgendaDriver {
1068
1071
  "mailery: queue driver 'agenda' requires the 'agenda' and '@agendajs/mongo-backend' peer dependencies. Run `npm install agenda @agendajs/mongo-backend bottleneck`."
1069
1072
  );
1070
1073
  }
1071
- const collectionName = opts.collectionName ?? "_mailerJobs";
1074
+ const collectionName = opts.collectionName ?? collectionFor(opts.prefix);
1072
1075
  const backend = new backendMod.MongoBackend({
1073
1076
  mongo: opts.db,
1074
1077
  collection: collectionName
@@ -1078,17 +1081,29 @@ var AgendaDriver = class _AgendaDriver {
1078
1081
  processEvery: `${opts.processEverySeconds ?? 5} seconds`,
1079
1082
  defaultLockLifetime: (opts.lockLifetimeSeconds ?? 10 * 60) * 1e3,
1080
1083
  maxConcurrency: 50,
1081
- defaultConcurrency: 5
1084
+ defaultConcurrency: 5,
1085
+ // Drop succeeded one-shot jobs instead of retaining them forever.
1086
+ // Agenda guards this on `!nextRunAt`, so repeating jobs survive.
1087
+ removeOnComplete: true
1082
1088
  });
1083
- return new _AgendaDriver(agenda, agendaMod, opts.db, collectionName);
1089
+ const retentionDays = opts.failedJobRetentionDays ?? DEFAULT_FAILED_RETENTION_DAYS;
1090
+ try {
1091
+ await opts.db.collection(collectionName).createIndex({ failedAt: 1 }, { sparse: true });
1092
+ } catch (err) {
1093
+ console.error("mailery: could not index the Agenda jobs collection on failedAt", err);
1094
+ }
1095
+ return new _AgendaDriver(agenda, agendaMod, opts.db, collectionName, retentionDays);
1084
1096
  }
1085
1097
  db;
1086
1098
  collName;
1087
- constructor(agenda, agendaMod, db, collectionName) {
1099
+ failedRetentionDays;
1100
+ sweepTimer = null;
1101
+ constructor(agenda, agendaMod, db, collectionName, failedRetentionDays) {
1088
1102
  this.agenda = agenda;
1089
1103
  this.agendaMod = agendaMod;
1090
1104
  this.db = db;
1091
1105
  this.collName = collectionName;
1106
+ this.failedRetentionDays = failedRetentionDays;
1092
1107
  this.queues = {
1093
1108
  tick: this.makeQueueAPI(QUEUE_NAMES2.tick),
1094
1109
  advance: this.makeQueueAPI(QUEUE_NAMES2.advance),
@@ -1169,9 +1184,45 @@ var AgendaDriver = class _AgendaDriver {
1169
1184
  if (!this.started) {
1170
1185
  await this.agenda.start();
1171
1186
  this.started = true;
1187
+ this.startFailedJobSweep();
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Agenda's auto-remove only fires on success, so failed documents would
1192
+ * otherwise be retained forever. Runs once on worker start, then hourly.
1193
+ */
1194
+ startFailedJobSweep() {
1195
+ if (this.failedRetentionDays <= 0 || this.sweepTimer) return;
1196
+ void this.sweepFailedJobs();
1197
+ this.sweepTimer = setInterval(() => void this.sweepFailedJobs(), FAILED_SWEEP_INTERVAL_MS);
1198
+ this.sweepTimer.unref?.();
1199
+ }
1200
+ /** Delete failed, fully-retired job documents older than the retention window. */
1201
+ async sweepFailedJobs() {
1202
+ const cutoff = new Date(Date.now() - this.failedRetentionDays * 24 * 3600 * 1e3);
1203
+ try {
1204
+ const res = await this.jobsCollection().deleteMany({
1205
+ failedAt: { $lt: cutoff },
1206
+ // Never touch the repeating tick.
1207
+ repeatInterval: { $in: [null, void 0] },
1208
+ // Leave anything still scheduled for a retry, and anything a worker
1209
+ // currently holds a lock on.
1210
+ $and: [
1211
+ { $or: [{ nextRunAt: null }, { nextRunAt: { $exists: false } }, { nextRunAt: { $lt: cutoff } }] },
1212
+ { $or: [{ lockedAt: null }, { lockedAt: { $exists: false } }, { lockedAt: { $lt: cutoff } }] }
1213
+ ]
1214
+ });
1215
+ return res.deletedCount ?? 0;
1216
+ } catch (err) {
1217
+ console.error("mailery: failed-job sweep failed", err);
1218
+ return 0;
1172
1219
  }
1173
1220
  }
1174
1221
  async stopWorkers() {
1222
+ if (this.sweepTimer) {
1223
+ clearInterval(this.sweepTimer);
1224
+ this.sweepTimer = null;
1225
+ }
1175
1226
  if (!this.started) return;
1176
1227
  await this.agenda.stop();
1177
1228
  this.started = false;
@@ -1185,6 +1236,15 @@ var AgendaDriver = class _AgendaDriver {
1185
1236
  await this.stopWorkers();
1186
1237
  }
1187
1238
  };
1239
+ function collectionFor(prefix) {
1240
+ if (!prefix) return DEFAULT_COLLECTION;
1241
+ if (!/^[A-Za-z0-9_-]+$/.test(prefix)) {
1242
+ throw new Error(
1243
+ `mailery: queue prefix "${prefix}" must contain only letters, digits, '_' or '-' \u2014 it becomes part of a MongoDB collection name.`
1244
+ );
1245
+ }
1246
+ return `${DEFAULT_COLLECTION}_${prefix}`;
1247
+ }
1188
1248
 
1189
1249
  // src/server/queues/noop.ts
1190
1250
  function noopQueueAPI() {
@@ -1221,7 +1281,9 @@ async function createQueueDriver(config, fallbackDb) {
1221
1281
  db: config.db ?? fallbackDb,
1222
1282
  processEverySeconds: config.processEverySeconds,
1223
1283
  lockLifetimeSeconds: config.lockLifetimeSeconds,
1224
- collectionName: config.collectionName
1284
+ collectionName: config.collectionName,
1285
+ prefix: config.prefix,
1286
+ failedJobRetentionDays: config.failedJobRetentionDays
1225
1287
  });
1226
1288
  case "noop":
1227
1289
  return new NoopDriver();
@@ -3977,7 +4039,8 @@ var Mailer = class _Mailer {
3977
4039
  * MAILER_MONGODB_URI — Mongo connection string (required)
3978
4040
  * MAILER_MONGODB_DB — database name (optional; defaults to the URI default)
3979
4041
  * MAILER_REDIS_URL — Redis connection URL (required)
3980
- * MAILER_QUEUE_PREFIX — Redis key prefix to namespace this instance (optional)
4042
+ * MAILER_QUEUE_PREFIX — namespaces this instance (optional). Bull: Redis key
4043
+ * prefix. Agenda: jobs collection suffix.
3981
4044
  * MAILER_PUBLIC_URL — base for tracking/unsub URLs (required)
3982
4045
  * MAILER_UNSUBSCRIBE_SECRET — HMAC key (required)
3983
4046
  * MAILER_SENDER_ADDRESS — postal address for CAN-SPAM (optional)
@@ -4026,7 +4089,7 @@ var Mailer = class _Mailer {
4026
4089
  }
4027
4090
  const defaultProvider = env.MAILER_DEFAULT_PROVIDER ?? Object.keys(providers)[0];
4028
4091
  const driverEnv = env.MAILER_QUEUE_DRIVER ?? "bull";
4029
- const queue = driverEnv === "agenda" ? { driver: "agenda" } : driverEnv === "noop" ? { driver: "noop" } : {
4092
+ const queue = driverEnv === "agenda" ? { driver: "agenda", prefix: env.MAILER_QUEUE_PREFIX } : driverEnv === "noop" ? { driver: "noop" } : {
4030
4093
  driver: "bull",
4031
4094
  redis: { url: required("MAILER_REDIS_URL") },
4032
4095
  prefix: env.MAILER_QUEUE_PREFIX
@@ -7699,7 +7762,7 @@ var DEDUPE_POLICIES = [
7699
7762
  ];
7700
7763
 
7701
7764
  // src/server/index.ts
7702
- var VERSION = "0.12.0" ;
7765
+ var VERSION = "0.13.0" ;
7703
7766
 
7704
7767
  export { DEDUPE_POLICIES, FLOW_STEP_KINDS, Mailer, MongoContactAdapter, NullProvider, PREDICATE_KINDS, RESERVED_VAR_KEYS, SEGMENT_FILTER_KINDS, SendGridProvider, VERSION, applyTracking, applyWebhookEvent, compileMailyTemplate, compileTemplate, computeDeliveryTime, createAdminRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, defineVars, derivePlaintext, dispatchSend, ensureIndexes, getCollections, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, renderTemplate, runTick, sha256Hex, signUnsubscribeToken, sweepStrandedFlowRuns, validateSenderDomain, varsJsonSchema, verifyUnsubscribeToken };
7705
7768
  //# sourceMappingURL=index.js.map