mailery 0.18.0 → 0.19.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, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, d as MailTesterFeedback, e as Mailer, T as TemplateDoc, F as FlowStep, f as Collections, g as FlowDoc, h as SuppressionScope, D as DeliveryWindow, i as SegmentFilter, P as Predicate } from './null-BFipn5tF.cjs';
2
- export { j as AuditLogDoc, B as BotFilterConfig, k as BroadcastDoc, l as BroadcastOrder, m as BroadcastPauseReason, n as BroadcastStatus, o as CircuitBreakerThresholds, p as ContactTagDoc, E as EventDoc, q as FlowGoal, r as FlowRunDoc, s as FlowRunStatus, t as FlowVersionDoc, H as HealthDoc, u as HealthStatus, L as LeadDoc, v as MailerConfig, w as NullProvider, O as OutboxDoc, x as RESERVED_VAR_KEYS, y as RedisOptions, z as SegmentDefinition, G as SendDoc, I as SendStatus, J as SenderDomainConfig, K as SenderDomainRegistry, Q as SenderDomainValidation, U as SubscriptionDoc, V as SubscriptionStatus, W as SuppressionDoc, X as SuppressionReason, Y as TemplateKind, Z as TemplateVersionDoc, _ as VarsAdapter, $ as VarsResolveInfo, a0 as WebhookEventDoc, a1 as defineVars, a2 as ensureIndexes, a3 as getCollections, a4 as validateSenderDomain, a5 as varsJsonSchema } from './null-BFipn5tF.cjs';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, d as MailTesterFeedback, e as Mailer, T as TemplateDoc, F as FlowStep, f as Collections, g as FlowDoc, h as SuppressionScope, D as DeliveryWindow, i as SegmentFilter, P as Predicate } from './null-DxTHQSrJ.cjs';
2
+ export { j as AuditLogDoc, B as BotFilterConfig, k as BroadcastDoc, l as BroadcastOrder, m as BroadcastPauseReason, n as BroadcastStatus, o as CircuitBreakerThresholds, p as ContactTagDoc, E as EventDoc, q as FlowGoal, r as FlowRunDoc, s as FlowRunStatus, t as FlowVersionDoc, H as HealthDoc, u as HealthStatus, L as LeadDoc, v as MailerConfig, w as NullProvider, O as OutboxDoc, x as RESERVED_VAR_KEYS, y as RedisOptions, z as SegmentDefinition, G as SendDoc, I as SendStatus, J as SenderDomainConfig, K as SenderDomainRegistry, Q as SenderDomainValidation, U as SubscriptionDoc, V as SubscriptionStatus, W as SuppressionDoc, X as SuppressionReason, Y as TemplateKind, Z as TemplateVersionDoc, _ as VarsAdapter, $ as VarsResolveInfo, a0 as WebhookEventDoc, a1 as defineVars, a2 as ensureIndexes, a3 as getCollections, a4 as validateSenderDomain, a5 as varsJsonSchema } from './null-DxTHQSrJ.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, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, d as MailTesterFeedback, e as Mailer, T as TemplateDoc, F as FlowStep, f as Collections, g as FlowDoc, h as SuppressionScope, D as DeliveryWindow, i as SegmentFilter, P as Predicate } from './null-BFipn5tF.js';
2
- export { j as AuditLogDoc, B as BotFilterConfig, k as BroadcastDoc, l as BroadcastOrder, m as BroadcastPauseReason, n as BroadcastStatus, o as CircuitBreakerThresholds, p as ContactTagDoc, E as EventDoc, q as FlowGoal, r as FlowRunDoc, s as FlowRunStatus, t as FlowVersionDoc, H as HealthDoc, u as HealthStatus, L as LeadDoc, v as MailerConfig, w as NullProvider, O as OutboxDoc, x as RESERVED_VAR_KEYS, y as RedisOptions, z as SegmentDefinition, G as SendDoc, I as SendStatus, J as SenderDomainConfig, K as SenderDomainRegistry, Q as SenderDomainValidation, U as SubscriptionDoc, V as SubscriptionStatus, W as SuppressionDoc, X as SuppressionReason, Y as TemplateKind, Z as TemplateVersionDoc, _ as VarsAdapter, $ as VarsResolveInfo, a0 as WebhookEventDoc, a1 as defineVars, a2 as ensureIndexes, a3 as getCollections, a4 as validateSenderDomain, a5 as varsJsonSchema } from './null-BFipn5tF.js';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, d as MailTesterFeedback, e as Mailer, T as TemplateDoc, F as FlowStep, f as Collections, g as FlowDoc, h as SuppressionScope, D as DeliveryWindow, i as SegmentFilter, P as Predicate } from './null-DxTHQSrJ.js';
2
+ export { j as AuditLogDoc, B as BotFilterConfig, k as BroadcastDoc, l as BroadcastOrder, m as BroadcastPauseReason, n as BroadcastStatus, o as CircuitBreakerThresholds, p as ContactTagDoc, E as EventDoc, q as FlowGoal, r as FlowRunDoc, s as FlowRunStatus, t as FlowVersionDoc, H as HealthDoc, u as HealthStatus, L as LeadDoc, v as MailerConfig, w as NullProvider, O as OutboxDoc, x as RESERVED_VAR_KEYS, y as RedisOptions, z as SegmentDefinition, G as SendDoc, I as SendStatus, J as SenderDomainConfig, K as SenderDomainRegistry, Q as SenderDomainValidation, U as SubscriptionDoc, V as SubscriptionStatus, W as SuppressionDoc, X as SuppressionReason, Y as TemplateKind, Z as TemplateVersionDoc, _ as VarsAdapter, $ as VarsResolveInfo, a0 as WebhookEventDoc, a1 as defineVars, a2 as ensureIndexes, a3 as getCollections, a4 as validateSenderDomain, a5 as varsJsonSchema } from './null-DxTHQSrJ.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
@@ -1699,11 +1699,11 @@ var ZERO_COUNTERS = {
1699
1699
  complained: 0,
1700
1700
  failedToSend: 0
1701
1701
  };
1702
- var ZERO_RATES = {
1703
- bounceRate: 0,
1704
- hardBounceRate: 0,
1705
- complaintRate: 0,
1706
- failureRate: 0
1702
+ var EMPTY_RATES = {
1703
+ bounceRate: null,
1704
+ hardBounceRate: null,
1705
+ complaintRate: null,
1706
+ failureRate: null
1707
1707
  };
1708
1708
  async function recordHealthCounter(ctx, counter2, dims, by = 1) {
1709
1709
  const windowMs = ctx.config.circuitBreaker.windowMinutes * 60 * 1e3;
@@ -1733,7 +1733,7 @@ async function upsertCounter(ctx, _id, senderDomain, kind, counter2, by, windowM
1733
1733
  trippedAt: null,
1734
1734
  trippedReason: null,
1735
1735
  manuallyResumedAt: null,
1736
- rates: { ...ZERO_RATES }
1736
+ rates: { ...EMPTY_RATES }
1737
1737
  },
1738
1738
  $set: { updatedAt: /* @__PURE__ */ new Date() }
1739
1739
  },
@@ -1760,7 +1760,7 @@ async function evaluateHealth(ctx) {
1760
1760
  windowStartedAt: /* @__PURE__ */ new Date(),
1761
1761
  windowDurationMs: windowMs,
1762
1762
  counters: { ...ZERO_COUNTERS },
1763
- rates: { ...ZERO_RATES },
1763
+ rates: { ...EMPTY_RATES },
1764
1764
  status: "healthy",
1765
1765
  updatedAt: /* @__PURE__ */ new Date()
1766
1766
  }
@@ -1768,13 +1768,21 @@ async function evaluateHealth(ctx) {
1768
1768
  );
1769
1769
  continue;
1770
1770
  }
1771
- const c = doc.counters;
1772
- const total = c.sent || 1;
1771
+ const c = { ...ZERO_COUNTERS, ...doc.counters };
1772
+ const perSent = (n) => c.sent > 0 ? n / c.sent : null;
1773
+ const attempts = c.sent + c.failedToSend;
1773
1774
  const rates = {
1774
- bounceRate: c.bounced / total,
1775
- hardBounceRate: c.hardBounced / total,
1776
- complaintRate: c.complained / total,
1777
- failureRate: c.failedToSend / total
1775
+ bounceRate: perSent(c.bounced),
1776
+ hardBounceRate: perSent(c.hardBounced),
1777
+ complaintRate: perSent(c.complained),
1778
+ // Failed attempts over all attempts, so it stays within 0–100%.
1779
+ failureRate: attempts > 0 ? c.failedToSend / attempts : null
1780
+ };
1781
+ const pct2 = {
1782
+ bounce: (rates.bounceRate ?? 0) * 100,
1783
+ hardBounce: (rates.hardBounceRate ?? 0) * 100,
1784
+ complaint: (rates.complaintRate ?? 0) * 100,
1785
+ failure: (rates.failureRate ?? 0) * 100
1778
1786
  };
1779
1787
  await ctx.collections.health.updateOne(
1780
1788
  { _id: doc._id },
@@ -1784,12 +1792,12 @@ async function evaluateHealth(ctx) {
1784
1792
  if (c.sent < cb.minSendsBeforeEval) continue;
1785
1793
  if (doc.status === "tripped") continue;
1786
1794
  let trippedReason = null;
1787
- if (rates.hardBounceRate * 100 >= cb.hardBounceRatePctTrip) {
1788
- trippedReason = `hard bounce rate ${(rates.hardBounceRate * 100).toFixed(2)}% >= ${cb.hardBounceRatePctTrip}%`;
1789
- } else if (rates.complaintRate * 100 >= cb.complaintRatePctTrip) {
1790
- trippedReason = `complaint rate ${(rates.complaintRate * 100).toFixed(2)}% >= ${cb.complaintRatePctTrip}%`;
1791
- } else if (rates.bounceRate * 100 >= cb.combinedBounceRatePctTrip) {
1792
- trippedReason = `combined bounce rate ${(rates.bounceRate * 100).toFixed(2)}% >= ${cb.combinedBounceRatePctTrip}%`;
1795
+ if (pct2.hardBounce >= cb.hardBounceRatePctTrip) {
1796
+ trippedReason = `hard bounce rate ${pct2.hardBounce.toFixed(2)}% >= ${cb.hardBounceRatePctTrip}%`;
1797
+ } else if (pct2.complaint >= cb.complaintRatePctTrip) {
1798
+ trippedReason = `complaint rate ${pct2.complaint.toFixed(2)}% >= ${cb.complaintRatePctTrip}%`;
1799
+ } else if (pct2.bounce >= cb.combinedBounceRatePctTrip) {
1800
+ trippedReason = `combined bounce rate ${pct2.bounce.toFixed(2)}% >= ${cb.combinedBounceRatePctTrip}%`;
1793
1801
  }
1794
1802
  if (trippedReason) {
1795
1803
  const result = await ctx.collections.health.updateOne(
@@ -1824,7 +1832,7 @@ async function evaluateHealth(ctx) {
1824
1832
  }
1825
1833
  continue;
1826
1834
  }
1827
- if (rates.failureRate * 100 >= cb.failedToSendRatePctDegrade) {
1835
+ if (pct2.failure >= cb.failedToSendRatePctDegrade) {
1828
1836
  if (doc.status !== "degraded") {
1829
1837
  await ctx.collections.health.updateOne(
1830
1838
  { _id: doc._id },
@@ -2786,6 +2794,10 @@ async function dispatchSend(sendId, ctx) {
2786
2794
  await markFailed(send._id, "contact_missing", ctx);
2787
2795
  return;
2788
2796
  }
2797
+ if (!send.emailAtSend?.trim() || !contact.email?.trim()) {
2798
+ await markFailed(send._id, "no_recipient: the contact has no email address", ctx);
2799
+ return;
2800
+ }
2789
2801
  const run = send.flowRunId ? await ctx.collections.flowRuns.findOne({ _id: send.flowRunId }) : null;
2790
2802
  if (run && run.status === "exited" && run.exitReason?.startsWith("aborted_by_host")) {
2791
2803
  await ctx.collections.sends.updateOne(
@@ -2982,6 +2994,10 @@ async function processOneRunStep(runId, ctx) {
2982
2994
  await exitFlowRun(run, "contact_missing", ctx);
2983
2995
  return;
2984
2996
  }
2997
+ if (!contact.email?.trim()) {
2998
+ await exitFlowRun(run, "contact_no_email", ctx);
2999
+ return;
3000
+ }
2985
3001
  const sub = await ctx.collections.subscriptions.findOne({ externalId: run.externalId });
2986
3002
  if (sub && sub.status !== "subscribed") {
2987
3003
  await exitFlowRun(run, sub.status, ctx);
@@ -3877,7 +3893,7 @@ async function runDnsblChecks(ctx, opts = {}) {
3877
3893
  }
3878
3894
  let listedCount = 0;
3879
3895
  async function processPair(p) {
3880
- const queryName = buildQueryName(p.target, p.targetKind, p.list.host);
3896
+ const queryName = buildQueryName(p.target, p.targetKind, spamhausQueryHost(p.list.host, cfg.spamhausDqsKey));
3881
3897
  const lookup2 = queryName ? await queryDnsbl(resolver, queryName) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
3882
3898
  if (lookup2.transient) return;
3883
3899
  if (lookup2.result === "listed") listedCount++;
@@ -3924,6 +3940,10 @@ function collectTargets(ctx, cfg) {
3924
3940
  ips: (cfg.dedicatedIps ?? []).filter((ip) => net.isIP(ip) !== 0)
3925
3941
  };
3926
3942
  }
3943
+ function spamhausQueryHost(listHost, dqsKey) {
3944
+ const zone = dqsKey ? /^([a-z0-9-]+)\.spamhaus\.org$/i.exec(listHost)?.[1] : void 0;
3945
+ return zone ? `${dqsKey}.${zone}.dq.spamhaus.net` : listHost;
3946
+ }
3927
3947
  function buildQueryName(target, kind, listHost) {
3928
3948
  if (kind === "ip") {
3929
3949
  const v = net.isIP(target);
@@ -3959,16 +3979,22 @@ async function queryDnsbl(resolver, query) {
3959
3979
  };
3960
3980
  }
3961
3981
  }
3982
+ var REFUSAL_REASONS = {
3983
+ "127.255.255.252": "malformed query name for this list",
3984
+ "127.255.255.254": "queried through a public or shared DNS resolver, which Spamhaus blocks. Set dnsbl.spamhausDqsKey (free Spamhaus DQS key) or use a private resolver",
3985
+ "127.255.255.255": "this resolver has exceeded the free query limit"
3986
+ };
3962
3987
  function interpretRecords(records) {
3963
3988
  if (!records || records.length === 0) {
3964
3989
  return { result: "clean", returnCodes: [], errorMessage: null };
3965
3990
  }
3966
3991
  const errorish = records.filter((r) => r.startsWith("127.255.255."));
3967
3992
  if (errorish.length === records.length) {
3993
+ const reasons = [...new Set(records.map((r) => REFUSAL_REASONS[r] ?? "unrecognised refusal code"))];
3968
3994
  return {
3969
3995
  result: "error",
3970
3996
  returnCodes: records,
3971
- errorMessage: `list returned reserved code(s): ${records.join(", ")}`
3997
+ errorMessage: `not a listing \u2014 the list refused the query (${records.join(", ")}): ${reasons.join("; ")}`
3972
3998
  };
3973
3999
  }
3974
4000
  const listed = records.filter((r) => r.startsWith("127.") && !r.startsWith("127.255.255."));
@@ -6418,7 +6444,7 @@ async function checkDnsbl(mailer) {
6418
6444
  label: "DNS block-list checks",
6419
6445
  severity: "warn",
6420
6446
  message: `all ${docs.length} checks errored`,
6421
- hint: "Lookups against block lists are failing \u2014 usually a DNS-resolution problem on the host running mailery, or a list rate-limiting your public resolver. Verify outbound DNS works."
6447
+ hint: "Lookups against block lists are failing \u2014 usually a DNS-resolution problem on the host running mailery, or a list refusing your public resolver (Spamhaus answers 127.255.255.254; set dnsbl.spamhausDqsKey). Verify outbound DNS works."
6422
6448
  };
6423
6449
  }
6424
6450
  return {
@@ -9528,7 +9554,7 @@ function unitToMs2(value, unit) {
9528
9554
  }
9529
9555
 
9530
9556
  // src/server/api/agent.ts
9531
- var VERSION = "0.18.0" ;
9557
+ var VERSION = "0.19.0" ;
9532
9558
  var broadcastCountSchema = z.object({
9533
9559
  recipientCap: z.number().int().positive().max(1e7).nullable().optional()
9534
9560
  });
@@ -11595,7 +11621,7 @@ var DEDUPE_POLICIES = [
11595
11621
  ];
11596
11622
 
11597
11623
  // src/server/index.ts
11598
- var VERSION2 = "0.18.0" ;
11624
+ var VERSION2 = "0.19.0" ;
11599
11625
 
11600
11626
  export { DEDUPE_POLICIES, DEFAULT_BOT_UA_RE, FLOW_STEP_KINDS, FlowOperationError, MIN_AGENT_TOKEN_LENGTH, Mailer, MongoContactAdapter, NullProvider, PREDICATE_KINDS, RESERVED_VAR_KEYS, SEGMENT_FILTER_KINDS, SendGridProvider, TRACKING_SIG_LENGTH, VERSION2 as VERSION, applyTracking, applyWebhookEvent, armFlow, compileMailyTemplate, compileTemplate, computeDeliveryTime, createAdminApiRouter, createAdminRouter, createAgentRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, defineVars, derivePlaintext, disarmFlow, dispatchSend, drainPendingUnsubscribes, ensureIndexes, gateFlow, getCollections, isCanaryGate, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, referencedPaths, renderForContact, renderTemplate, runTick, sendgridInboundParser, sha256Hex, signTrackingToken, signUnsubscribeToken, simulateFlow, stampWatermarkIfNull, sweepStrandedFlowRuns, ungateFlow, validateSenderDomain, varsJsonSchema, verifyTemplate, verifyTrackingToken, verifyUnsubscribeToken };
11601
11627
  //# sourceMappingURL=index.js.map