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/admin/spa/index-B4zQqLvr.js.map +1 -1
- package/dist/index.cjs +51 -25
- 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 +51 -25
- 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 +48 -22
- 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 +48 -22
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1717,11 +1717,11 @@ var ZERO_COUNTERS = {
|
|
|
1717
1717
|
complained: 0,
|
|
1718
1718
|
failedToSend: 0
|
|
1719
1719
|
};
|
|
1720
|
-
var
|
|
1721
|
-
bounceRate:
|
|
1722
|
-
hardBounceRate:
|
|
1723
|
-
complaintRate:
|
|
1724
|
-
failureRate:
|
|
1720
|
+
var EMPTY_RATES = {
|
|
1721
|
+
bounceRate: null,
|
|
1722
|
+
hardBounceRate: null,
|
|
1723
|
+
complaintRate: null,
|
|
1724
|
+
failureRate: null
|
|
1725
1725
|
};
|
|
1726
1726
|
async function recordHealthCounter(ctx, counter2, dims, by = 1) {
|
|
1727
1727
|
const windowMs = ctx.config.circuitBreaker.windowMinutes * 60 * 1e3;
|
|
@@ -1751,7 +1751,7 @@ async function upsertCounter(ctx, _id, senderDomain, kind, counter2, by, windowM
|
|
|
1751
1751
|
trippedAt: null,
|
|
1752
1752
|
trippedReason: null,
|
|
1753
1753
|
manuallyResumedAt: null,
|
|
1754
|
-
rates: { ...
|
|
1754
|
+
rates: { ...EMPTY_RATES }
|
|
1755
1755
|
},
|
|
1756
1756
|
$set: { updatedAt: /* @__PURE__ */ new Date() }
|
|
1757
1757
|
},
|
|
@@ -1778,7 +1778,7 @@ async function evaluateHealth(ctx) {
|
|
|
1778
1778
|
windowStartedAt: /* @__PURE__ */ new Date(),
|
|
1779
1779
|
windowDurationMs: windowMs,
|
|
1780
1780
|
counters: { ...ZERO_COUNTERS },
|
|
1781
|
-
rates: { ...
|
|
1781
|
+
rates: { ...EMPTY_RATES },
|
|
1782
1782
|
status: "healthy",
|
|
1783
1783
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1784
1784
|
}
|
|
@@ -1786,13 +1786,21 @@ async function evaluateHealth(ctx) {
|
|
|
1786
1786
|
);
|
|
1787
1787
|
continue;
|
|
1788
1788
|
}
|
|
1789
|
-
const c = doc.counters;
|
|
1790
|
-
const
|
|
1789
|
+
const c = { ...ZERO_COUNTERS, ...doc.counters };
|
|
1790
|
+
const perSent = (n) => c.sent > 0 ? n / c.sent : null;
|
|
1791
|
+
const attempts = c.sent + c.failedToSend;
|
|
1791
1792
|
const rates = {
|
|
1792
|
-
bounceRate: c.bounced
|
|
1793
|
-
hardBounceRate: c.hardBounced
|
|
1794
|
-
complaintRate: c.complained
|
|
1795
|
-
|
|
1793
|
+
bounceRate: perSent(c.bounced),
|
|
1794
|
+
hardBounceRate: perSent(c.hardBounced),
|
|
1795
|
+
complaintRate: perSent(c.complained),
|
|
1796
|
+
// Failed attempts over all attempts, so it stays within 0–100%.
|
|
1797
|
+
failureRate: attempts > 0 ? c.failedToSend / attempts : null
|
|
1798
|
+
};
|
|
1799
|
+
const pct2 = {
|
|
1800
|
+
bounce: (rates.bounceRate ?? 0) * 100,
|
|
1801
|
+
hardBounce: (rates.hardBounceRate ?? 0) * 100,
|
|
1802
|
+
complaint: (rates.complaintRate ?? 0) * 100,
|
|
1803
|
+
failure: (rates.failureRate ?? 0) * 100
|
|
1796
1804
|
};
|
|
1797
1805
|
await ctx.collections.health.updateOne(
|
|
1798
1806
|
{ _id: doc._id },
|
|
@@ -1802,12 +1810,12 @@ async function evaluateHealth(ctx) {
|
|
|
1802
1810
|
if (c.sent < cb.minSendsBeforeEval) continue;
|
|
1803
1811
|
if (doc.status === "tripped") continue;
|
|
1804
1812
|
let trippedReason = null;
|
|
1805
|
-
if (
|
|
1806
|
-
trippedReason = `hard bounce rate ${
|
|
1807
|
-
} else if (
|
|
1808
|
-
trippedReason = `complaint rate ${
|
|
1809
|
-
} else if (
|
|
1810
|
-
trippedReason = `combined bounce rate ${
|
|
1813
|
+
if (pct2.hardBounce >= cb.hardBounceRatePctTrip) {
|
|
1814
|
+
trippedReason = `hard bounce rate ${pct2.hardBounce.toFixed(2)}% >= ${cb.hardBounceRatePctTrip}%`;
|
|
1815
|
+
} else if (pct2.complaint >= cb.complaintRatePctTrip) {
|
|
1816
|
+
trippedReason = `complaint rate ${pct2.complaint.toFixed(2)}% >= ${cb.complaintRatePctTrip}%`;
|
|
1817
|
+
} else if (pct2.bounce >= cb.combinedBounceRatePctTrip) {
|
|
1818
|
+
trippedReason = `combined bounce rate ${pct2.bounce.toFixed(2)}% >= ${cb.combinedBounceRatePctTrip}%`;
|
|
1811
1819
|
}
|
|
1812
1820
|
if (trippedReason) {
|
|
1813
1821
|
const result = await ctx.collections.health.updateOne(
|
|
@@ -1842,7 +1850,7 @@ async function evaluateHealth(ctx) {
|
|
|
1842
1850
|
}
|
|
1843
1851
|
continue;
|
|
1844
1852
|
}
|
|
1845
|
-
if (
|
|
1853
|
+
if (pct2.failure >= cb.failedToSendRatePctDegrade) {
|
|
1846
1854
|
if (doc.status !== "degraded") {
|
|
1847
1855
|
await ctx.collections.health.updateOne(
|
|
1848
1856
|
{ _id: doc._id },
|
|
@@ -2804,6 +2812,10 @@ async function dispatchSend(sendId, ctx) {
|
|
|
2804
2812
|
await markFailed(send._id, "contact_missing", ctx);
|
|
2805
2813
|
return;
|
|
2806
2814
|
}
|
|
2815
|
+
if (!send.emailAtSend?.trim() || !contact.email?.trim()) {
|
|
2816
|
+
await markFailed(send._id, "no_recipient: the contact has no email address", ctx);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2807
2819
|
const run = send.flowRunId ? await ctx.collections.flowRuns.findOne({ _id: send.flowRunId }) : null;
|
|
2808
2820
|
if (run && run.status === "exited" && run.exitReason?.startsWith("aborted_by_host")) {
|
|
2809
2821
|
await ctx.collections.sends.updateOne(
|
|
@@ -3000,6 +3012,10 @@ async function processOneRunStep(runId, ctx) {
|
|
|
3000
3012
|
await exitFlowRun(run, "contact_missing", ctx);
|
|
3001
3013
|
return;
|
|
3002
3014
|
}
|
|
3015
|
+
if (!contact.email?.trim()) {
|
|
3016
|
+
await exitFlowRun(run, "contact_no_email", ctx);
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3003
3019
|
const sub = await ctx.collections.subscriptions.findOne({ externalId: run.externalId });
|
|
3004
3020
|
if (sub && sub.status !== "subscribed") {
|
|
3005
3021
|
await exitFlowRun(run, sub.status, ctx);
|
|
@@ -3895,7 +3911,7 @@ async function runDnsblChecks(ctx, opts = {}) {
|
|
|
3895
3911
|
}
|
|
3896
3912
|
let listedCount = 0;
|
|
3897
3913
|
async function processPair(p) {
|
|
3898
|
-
const queryName = buildQueryName(p.target, p.targetKind, p.list.host);
|
|
3914
|
+
const queryName = buildQueryName(p.target, p.targetKind, spamhausQueryHost(p.list.host, cfg.spamhausDqsKey));
|
|
3899
3915
|
const lookup2 = queryName ? await queryDnsbl(resolver, queryName) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
|
|
3900
3916
|
if (lookup2.transient) return;
|
|
3901
3917
|
if (lookup2.result === "listed") listedCount++;
|
|
@@ -3942,6 +3958,10 @@ function collectTargets(ctx, cfg) {
|
|
|
3942
3958
|
ips: (cfg.dedicatedIps ?? []).filter((ip) => net__default.default.isIP(ip) !== 0)
|
|
3943
3959
|
};
|
|
3944
3960
|
}
|
|
3961
|
+
function spamhausQueryHost(listHost, dqsKey) {
|
|
3962
|
+
const zone = dqsKey ? /^([a-z0-9-]+)\.spamhaus\.org$/i.exec(listHost)?.[1] : void 0;
|
|
3963
|
+
return zone ? `${dqsKey}.${zone}.dq.spamhaus.net` : listHost;
|
|
3964
|
+
}
|
|
3945
3965
|
function buildQueryName(target, kind, listHost) {
|
|
3946
3966
|
if (kind === "ip") {
|
|
3947
3967
|
const v = net__default.default.isIP(target);
|
|
@@ -3977,16 +3997,22 @@ async function queryDnsbl(resolver, query) {
|
|
|
3977
3997
|
};
|
|
3978
3998
|
}
|
|
3979
3999
|
}
|
|
4000
|
+
var REFUSAL_REASONS = {
|
|
4001
|
+
"127.255.255.252": "malformed query name for this list",
|
|
4002
|
+
"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",
|
|
4003
|
+
"127.255.255.255": "this resolver has exceeded the free query limit"
|
|
4004
|
+
};
|
|
3980
4005
|
function interpretRecords(records) {
|
|
3981
4006
|
if (!records || records.length === 0) {
|
|
3982
4007
|
return { result: "clean", returnCodes: [], errorMessage: null };
|
|
3983
4008
|
}
|
|
3984
4009
|
const errorish = records.filter((r) => r.startsWith("127.255.255."));
|
|
3985
4010
|
if (errorish.length === records.length) {
|
|
4011
|
+
const reasons = [...new Set(records.map((r) => REFUSAL_REASONS[r] ?? "unrecognised refusal code"))];
|
|
3986
4012
|
return {
|
|
3987
4013
|
result: "error",
|
|
3988
4014
|
returnCodes: records,
|
|
3989
|
-
errorMessage: `list
|
|
4015
|
+
errorMessage: `not a listing \u2014 the list refused the query (${records.join(", ")}): ${reasons.join("; ")}`
|
|
3990
4016
|
};
|
|
3991
4017
|
}
|
|
3992
4018
|
const listed = records.filter((r) => r.startsWith("127.") && !r.startsWith("127.255.255."));
|
|
@@ -6436,7 +6462,7 @@ async function checkDnsbl(mailer) {
|
|
|
6436
6462
|
label: "DNS block-list checks",
|
|
6437
6463
|
severity: "warn",
|
|
6438
6464
|
message: `all ${docs.length} checks errored`,
|
|
6439
|
-
hint: "Lookups against block lists are failing \u2014 usually a DNS-resolution problem on the host running mailery, or a list
|
|
6465
|
+
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."
|
|
6440
6466
|
};
|
|
6441
6467
|
}
|
|
6442
6468
|
return {
|
|
@@ -9546,7 +9572,7 @@ function unitToMs2(value, unit) {
|
|
|
9546
9572
|
}
|
|
9547
9573
|
|
|
9548
9574
|
// src/server/api/agent.ts
|
|
9549
|
-
var VERSION = "0.
|
|
9575
|
+
var VERSION = "0.19.0" ;
|
|
9550
9576
|
var broadcastCountSchema = zod.z.object({
|
|
9551
9577
|
recipientCap: zod.z.number().int().positive().max(1e7).nullable().optional()
|
|
9552
9578
|
});
|
|
@@ -11613,7 +11639,7 @@ var DEDUPE_POLICIES = [
|
|
|
11613
11639
|
];
|
|
11614
11640
|
|
|
11615
11641
|
// src/server/index.ts
|
|
11616
|
-
var VERSION2 = "0.
|
|
11642
|
+
var VERSION2 = "0.19.0" ;
|
|
11617
11643
|
|
|
11618
11644
|
exports.DEDUPE_POLICIES = DEDUPE_POLICIES;
|
|
11619
11645
|
exports.DEFAULT_BOT_UA_RE = DEFAULT_BOT_UA_RE;
|