mailery 0.18.0 → 0.19.1
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/admin/spa/index-B4zQqLvr.js.map +1 -1
- package/dist/index.cjs +62 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +62 -29
- package/dist/index.js.map +1 -1
- package/dist/{null-BFipn5tF.d.cts → null-DxTHQSrJ.d.cts} +18 -5
- package/dist/{null-BFipn5tF.d.ts → null-DxTHQSrJ.d.ts} +18 -5
- package/dist/testing.cjs +59 -26
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +59 -26
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Db } from 'mongodb';
|
|
2
|
-
import { C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, N as NormalizedEvent, F as FlowStep, a6 as FlowTrigger, q as FlowGoal, Y as TemplateKind, T as TemplateDoc, g as FlowDoc, D as DeliveryWindow, R as RunnerContext, e as Mailer, a7 as QueueDriverConfig } from './null-
|
|
3
|
-
export { w as NullProvider } from './null-
|
|
2
|
+
import { C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, N as NormalizedEvent, F as FlowStep, a6 as FlowTrigger, q as FlowGoal, Y as TemplateKind, T as TemplateDoc, g as FlowDoc, D as DeliveryWindow, R as RunnerContext, e as Mailer, a7 as QueueDriverConfig } from './null-DxTHQSrJ.cjs';
|
|
3
|
+
export { w as NullProvider } from './null-DxTHQSrJ.cjs';
|
|
4
4
|
import 'zod';
|
|
5
5
|
import 'handlebars';
|
|
6
6
|
import 'ioredis';
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Db } from 'mongodb';
|
|
2
|
-
import { C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, N as NormalizedEvent, F as FlowStep, a6 as FlowTrigger, q as FlowGoal, Y as TemplateKind, T as TemplateDoc, g as FlowDoc, D as DeliveryWindow, R as RunnerContext, e as Mailer, a7 as QueueDriverConfig } from './null-
|
|
3
|
-
export { w as NullProvider } from './null-
|
|
2
|
+
import { C as ContactAdapter, a as Contact, A as AdapterFilter, b as AdapterSort, M as MailProvider, S as SendArgs, c as SendResult, N as NormalizedEvent, F as FlowStep, a6 as FlowTrigger, q as FlowGoal, Y as TemplateKind, T as TemplateDoc, g as FlowDoc, D as DeliveryWindow, R as RunnerContext, e as Mailer, a7 as QueueDriverConfig } from './null-DxTHQSrJ.js';
|
|
3
|
+
export { w as NullProvider } from './null-DxTHQSrJ.js';
|
|
4
4
|
import 'zod';
|
|
5
5
|
import 'handlebars';
|
|
6
6
|
import 'ioredis';
|
package/dist/testing.js
CHANGED
|
@@ -15401,11 +15401,11 @@ var ZERO_COUNTERS = {
|
|
|
15401
15401
|
complained: 0,
|
|
15402
15402
|
failedToSend: 0
|
|
15403
15403
|
};
|
|
15404
|
-
var
|
|
15405
|
-
bounceRate:
|
|
15406
|
-
hardBounceRate:
|
|
15407
|
-
complaintRate:
|
|
15408
|
-
failureRate:
|
|
15404
|
+
var EMPTY_RATES = {
|
|
15405
|
+
bounceRate: null,
|
|
15406
|
+
hardBounceRate: null,
|
|
15407
|
+
complaintRate: null,
|
|
15408
|
+
failureRate: null
|
|
15409
15409
|
};
|
|
15410
15410
|
async function recordHealthCounter(ctx, counter2, dims, by = 1) {
|
|
15411
15411
|
const windowMs = ctx.config.circuitBreaker.windowMinutes * 60 * 1e3;
|
|
@@ -15435,7 +15435,7 @@ async function upsertCounter(ctx, _id, senderDomain, kind, counter2, by, windowM
|
|
|
15435
15435
|
trippedAt: null,
|
|
15436
15436
|
trippedReason: null,
|
|
15437
15437
|
manuallyResumedAt: null,
|
|
15438
|
-
rates: { ...
|
|
15438
|
+
rates: { ...EMPTY_RATES }
|
|
15439
15439
|
},
|
|
15440
15440
|
$set: { updatedAt: /* @__PURE__ */ new Date() }
|
|
15441
15441
|
},
|
|
@@ -15462,7 +15462,7 @@ async function evaluateHealth(ctx) {
|
|
|
15462
15462
|
windowStartedAt: /* @__PURE__ */ new Date(),
|
|
15463
15463
|
windowDurationMs: windowMs,
|
|
15464
15464
|
counters: { ...ZERO_COUNTERS },
|
|
15465
|
-
rates: { ...
|
|
15465
|
+
rates: { ...EMPTY_RATES },
|
|
15466
15466
|
status: "healthy",
|
|
15467
15467
|
updatedAt: /* @__PURE__ */ new Date()
|
|
15468
15468
|
}
|
|
@@ -15470,13 +15470,21 @@ async function evaluateHealth(ctx) {
|
|
|
15470
15470
|
);
|
|
15471
15471
|
continue;
|
|
15472
15472
|
}
|
|
15473
|
-
const c = doc.counters;
|
|
15474
|
-
const
|
|
15473
|
+
const c = { ...ZERO_COUNTERS, ...doc.counters };
|
|
15474
|
+
const perSent = (n) => c.sent > 0 ? n / c.sent : null;
|
|
15475
|
+
const attempts = c.sent + c.failedToSend;
|
|
15475
15476
|
const rates = {
|
|
15476
|
-
bounceRate: c.bounced
|
|
15477
|
-
hardBounceRate: c.hardBounced
|
|
15478
|
-
complaintRate: c.complained
|
|
15479
|
-
|
|
15477
|
+
bounceRate: perSent(c.bounced),
|
|
15478
|
+
hardBounceRate: perSent(c.hardBounced),
|
|
15479
|
+
complaintRate: perSent(c.complained),
|
|
15480
|
+
// Failed attempts over all attempts, so it stays within 0–100%.
|
|
15481
|
+
failureRate: attempts > 0 ? c.failedToSend / attempts : null
|
|
15482
|
+
};
|
|
15483
|
+
const pct2 = {
|
|
15484
|
+
bounce: (rates.bounceRate ?? 0) * 100,
|
|
15485
|
+
hardBounce: (rates.hardBounceRate ?? 0) * 100,
|
|
15486
|
+
complaint: (rates.complaintRate ?? 0) * 100,
|
|
15487
|
+
failure: (rates.failureRate ?? 0) * 100
|
|
15480
15488
|
};
|
|
15481
15489
|
await ctx.collections.health.updateOne(
|
|
15482
15490
|
{ _id: doc._id },
|
|
@@ -15486,12 +15494,12 @@ async function evaluateHealth(ctx) {
|
|
|
15486
15494
|
if (c.sent < cb.minSendsBeforeEval) continue;
|
|
15487
15495
|
if (doc.status === "tripped") continue;
|
|
15488
15496
|
let trippedReason = null;
|
|
15489
|
-
if (
|
|
15490
|
-
trippedReason = `hard bounce rate ${
|
|
15491
|
-
} else if (
|
|
15492
|
-
trippedReason = `complaint rate ${
|
|
15493
|
-
} else if (
|
|
15494
|
-
trippedReason = `combined bounce rate ${
|
|
15497
|
+
if (pct2.hardBounce >= cb.hardBounceRatePctTrip) {
|
|
15498
|
+
trippedReason = `hard bounce rate ${pct2.hardBounce.toFixed(2)}% >= ${cb.hardBounceRatePctTrip}%`;
|
|
15499
|
+
} else if (pct2.complaint >= cb.complaintRatePctTrip) {
|
|
15500
|
+
trippedReason = `complaint rate ${pct2.complaint.toFixed(2)}% >= ${cb.complaintRatePctTrip}%`;
|
|
15501
|
+
} else if (pct2.bounce >= cb.combinedBounceRatePctTrip) {
|
|
15502
|
+
trippedReason = `combined bounce rate ${pct2.bounce.toFixed(2)}% >= ${cb.combinedBounceRatePctTrip}%`;
|
|
15495
15503
|
}
|
|
15496
15504
|
if (trippedReason) {
|
|
15497
15505
|
const result = await ctx.collections.health.updateOne(
|
|
@@ -15526,7 +15534,7 @@ async function evaluateHealth(ctx) {
|
|
|
15526
15534
|
}
|
|
15527
15535
|
continue;
|
|
15528
15536
|
}
|
|
15529
|
-
if (
|
|
15537
|
+
if (pct2.failure >= cb.failedToSendRatePctDegrade) {
|
|
15530
15538
|
if (doc.status !== "degraded") {
|
|
15531
15539
|
await ctx.collections.health.updateOne(
|
|
15532
15540
|
{ _id: doc._id },
|
|
@@ -16429,6 +16437,10 @@ async function dispatchSend(sendId, ctx) {
|
|
|
16429
16437
|
await markFailed(send._id, "contact_missing", ctx);
|
|
16430
16438
|
return;
|
|
16431
16439
|
}
|
|
16440
|
+
if (!send.emailAtSend?.trim() || !contact.email?.trim()) {
|
|
16441
|
+
await markFailed(send._id, "no_recipient: the contact has no email address", ctx);
|
|
16442
|
+
return;
|
|
16443
|
+
}
|
|
16432
16444
|
const run = send.flowRunId ? await ctx.collections.flowRuns.findOne({ _id: send.flowRunId }) : null;
|
|
16433
16445
|
if (run && run.status === "exited" && run.exitReason?.startsWith("aborted_by_host")) {
|
|
16434
16446
|
await ctx.collections.sends.updateOne(
|
|
@@ -16625,6 +16637,10 @@ async function processOneRunStep(runId, ctx) {
|
|
|
16625
16637
|
await exitFlowRun(run, "contact_missing", ctx);
|
|
16626
16638
|
return;
|
|
16627
16639
|
}
|
|
16640
|
+
if (!contact.email?.trim()) {
|
|
16641
|
+
await exitFlowRun(run, "contact_no_email", ctx);
|
|
16642
|
+
return;
|
|
16643
|
+
}
|
|
16628
16644
|
const sub = await ctx.collections.subscriptions.findOne({ externalId: run.externalId });
|
|
16629
16645
|
if (sub && sub.status !== "subscribed") {
|
|
16630
16646
|
await exitFlowRun(run, sub.status, ctx);
|
|
@@ -17493,8 +17509,8 @@ async function runDnsblChecks(ctx, opts = {}) {
|
|
|
17493
17509
|
}
|
|
17494
17510
|
let listedCount = 0;
|
|
17495
17511
|
async function processPair(p) {
|
|
17496
|
-
const queryName = buildQueryName(p.target, p.targetKind, p.list.host);
|
|
17497
|
-
const lookup2 = queryName ? await queryDnsbl(resolver, queryName) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
|
|
17512
|
+
const queryName = buildQueryName(p.target, p.targetKind, spamhausQueryHost(p.list.host, cfg.spamhausDqsKey));
|
|
17513
|
+
const lookup2 = queryName ? await queryDnsbl(resolver, queryName, p.list.host) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
|
|
17498
17514
|
if (lookup2.transient) return;
|
|
17499
17515
|
if (lookup2.result === "listed") listedCount++;
|
|
17500
17516
|
await ctx.collections.dnsblChecks.updateOne(
|
|
@@ -17540,6 +17556,10 @@ function collectTargets(ctx, cfg) {
|
|
|
17540
17556
|
ips: (cfg.dedicatedIps ?? []).filter((ip) => net.isIP(ip) !== 0)
|
|
17541
17557
|
};
|
|
17542
17558
|
}
|
|
17559
|
+
function spamhausQueryHost(listHost, dqsKey) {
|
|
17560
|
+
const zone = dqsKey ? /^([a-z0-9-]+)\.spamhaus\.org$/i.exec(listHost)?.[1] : void 0;
|
|
17561
|
+
return zone ? `${dqsKey}.${zone}.dq.spamhaus.net` : listHost;
|
|
17562
|
+
}
|
|
17543
17563
|
function buildQueryName(target, kind, listHost) {
|
|
17544
17564
|
if (kind === "ip") {
|
|
17545
17565
|
const v = net.isIP(target);
|
|
@@ -17551,10 +17571,10 @@ function buildQueryName(target, kind, listHost) {
|
|
|
17551
17571
|
return `${domain}.${listHost}`;
|
|
17552
17572
|
}
|
|
17553
17573
|
var TRANSIENT_DNS_CODES = /* @__PURE__ */ new Set(["ESERVFAIL", "EREFUSED", "ETIMEOUT", "ETIMEDOUT", "ECONNRESET", "ECONNREFUSED"]);
|
|
17554
|
-
async function queryDnsbl(resolver, query) {
|
|
17574
|
+
async function queryDnsbl(resolver, query, listHost) {
|
|
17555
17575
|
try {
|
|
17556
17576
|
const records = await resolver.resolve4(query);
|
|
17557
|
-
return interpretRecords(records);
|
|
17577
|
+
return interpretRecords(records, listHost);
|
|
17558
17578
|
} catch (err) {
|
|
17559
17579
|
const code = err?.code;
|
|
17560
17580
|
if (code === "ENOTFOUND" || code === "ENODATA") {
|
|
@@ -17575,16 +17595,29 @@ async function queryDnsbl(resolver, query) {
|
|
|
17575
17595
|
};
|
|
17576
17596
|
}
|
|
17577
17597
|
}
|
|
17578
|
-
|
|
17598
|
+
var REFUSAL_REASONS = {
|
|
17599
|
+
"127.255.255.252": "malformed query name for this list",
|
|
17600
|
+
"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",
|
|
17601
|
+
"127.255.255.255": "this resolver has exceeded the free query limit"
|
|
17602
|
+
};
|
|
17603
|
+
function interpretRecords(records, listHost) {
|
|
17579
17604
|
if (!records || records.length === 0) {
|
|
17580
17605
|
return { result: "clean", returnCodes: [], errorMessage: null };
|
|
17581
17606
|
}
|
|
17607
|
+
if (listHost && REGISTRABLE_ONLY_LISTS.has(listHost) && records.every((r) => r === "127.0.0.1")) {
|
|
17608
|
+
return {
|
|
17609
|
+
result: "error",
|
|
17610
|
+
returnCodes: records,
|
|
17611
|
+
errorMessage: `${listHost} refused the query (127.0.0.1): queried through a public or shared DNS resolver, or over the free query limit. Use a private resolver`
|
|
17612
|
+
};
|
|
17613
|
+
}
|
|
17582
17614
|
const errorish = records.filter((r) => r.startsWith("127.255.255."));
|
|
17583
17615
|
if (errorish.length === records.length) {
|
|
17616
|
+
const reasons = [...new Set(records.map((r) => REFUSAL_REASONS[r] ?? "unrecognised refusal code"))];
|
|
17584
17617
|
return {
|
|
17585
17618
|
result: "error",
|
|
17586
17619
|
returnCodes: records,
|
|
17587
|
-
errorMessage: `list
|
|
17620
|
+
errorMessage: `not a listing \u2014 the list refused the query (${records.join(", ")}): ${reasons.join("; ")}`
|
|
17588
17621
|
};
|
|
17589
17622
|
}
|
|
17590
17623
|
const listed = records.filter((r) => r.startsWith("127.") && !r.startsWith("127.255.255."));
|