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/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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
1703
|
-
bounceRate:
|
|
1704
|
-
hardBounceRate:
|
|
1705
|
-
complaintRate:
|
|
1706
|
-
failureRate:
|
|
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: { ...
|
|
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: { ...
|
|
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
|
|
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
|
|
1775
|
-
hardBounceRate: c.hardBounced
|
|
1776
|
-
complaintRate: c.complained
|
|
1777
|
-
|
|
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 (
|
|
1788
|
-
trippedReason = `hard bounce rate ${
|
|
1789
|
-
} else if (
|
|
1790
|
-
trippedReason = `complaint rate ${
|
|
1791
|
-
} else if (
|
|
1792
|
-
trippedReason = `combined bounce rate ${
|
|
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 (
|
|
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,8 +3893,8 @@ 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);
|
|
3881
|
-
const lookup2 = queryName ? await queryDnsbl(resolver, queryName) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
|
|
3896
|
+
const queryName = buildQueryName(p.target, p.targetKind, spamhausQueryHost(p.list.host, cfg.spamhausDqsKey));
|
|
3897
|
+
const lookup2 = queryName ? await queryDnsbl(resolver, queryName, p.list.host) : { result: "error", returnCodes: [], errorMessage: "unsupported target format" };
|
|
3882
3898
|
if (lookup2.transient) return;
|
|
3883
3899
|
if (lookup2.result === "listed") listedCount++;
|
|
3884
3900
|
await ctx.collections.dnsblChecks.updateOne(
|
|
@@ -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);
|
|
@@ -3935,10 +3955,10 @@ function buildQueryName(target, kind, listHost) {
|
|
|
3935
3955
|
return `${domain}.${listHost}`;
|
|
3936
3956
|
}
|
|
3937
3957
|
var TRANSIENT_DNS_CODES = /* @__PURE__ */ new Set(["ESERVFAIL", "EREFUSED", "ETIMEOUT", "ETIMEDOUT", "ECONNRESET", "ECONNREFUSED"]);
|
|
3938
|
-
async function queryDnsbl(resolver, query) {
|
|
3958
|
+
async function queryDnsbl(resolver, query, listHost) {
|
|
3939
3959
|
try {
|
|
3940
3960
|
const records = await resolver.resolve4(query);
|
|
3941
|
-
return interpretRecords(records);
|
|
3961
|
+
return interpretRecords(records, listHost);
|
|
3942
3962
|
} catch (err) {
|
|
3943
3963
|
const code = err?.code;
|
|
3944
3964
|
if (code === "ENOTFOUND" || code === "ENODATA") {
|
|
@@ -3959,16 +3979,29 @@ async function queryDnsbl(resolver, query) {
|
|
|
3959
3979
|
};
|
|
3960
3980
|
}
|
|
3961
3981
|
}
|
|
3962
|
-
|
|
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
|
+
};
|
|
3987
|
+
function interpretRecords(records, listHost) {
|
|
3963
3988
|
if (!records || records.length === 0) {
|
|
3964
3989
|
return { result: "clean", returnCodes: [], errorMessage: null };
|
|
3965
3990
|
}
|
|
3991
|
+
if (listHost && REGISTRABLE_ONLY_LISTS.has(listHost) && records.every((r) => r === "127.0.0.1")) {
|
|
3992
|
+
return {
|
|
3993
|
+
result: "error",
|
|
3994
|
+
returnCodes: records,
|
|
3995
|
+
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`
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3966
3998
|
const errorish = records.filter((r) => r.startsWith("127.255.255."));
|
|
3967
3999
|
if (errorish.length === records.length) {
|
|
4000
|
+
const reasons = [...new Set(records.map((r) => REFUSAL_REASONS[r] ?? "unrecognised refusal code"))];
|
|
3968
4001
|
return {
|
|
3969
4002
|
result: "error",
|
|
3970
4003
|
returnCodes: records,
|
|
3971
|
-
errorMessage: `list
|
|
4004
|
+
errorMessage: `not a listing \u2014 the list refused the query (${records.join(", ")}): ${reasons.join("; ")}`
|
|
3972
4005
|
};
|
|
3973
4006
|
}
|
|
3974
4007
|
const listed = records.filter((r) => r.startsWith("127.") && !r.startsWith("127.255.255."));
|
|
@@ -6418,7 +6451,7 @@ async function checkDnsbl(mailer) {
|
|
|
6418
6451
|
label: "DNS block-list checks",
|
|
6419
6452
|
severity: "warn",
|
|
6420
6453
|
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
|
|
6454
|
+
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
6455
|
};
|
|
6423
6456
|
}
|
|
6424
6457
|
return {
|
|
@@ -9528,7 +9561,7 @@ function unitToMs2(value, unit) {
|
|
|
9528
9561
|
}
|
|
9529
9562
|
|
|
9530
9563
|
// src/server/api/agent.ts
|
|
9531
|
-
var VERSION = "0.
|
|
9564
|
+
var VERSION = "0.19.1" ;
|
|
9532
9565
|
var broadcastCountSchema = z.object({
|
|
9533
9566
|
recipientCap: z.number().int().positive().max(1e7).nullable().optional()
|
|
9534
9567
|
});
|
|
@@ -11595,7 +11628,7 @@ var DEDUPE_POLICIES = [
|
|
|
11595
11628
|
];
|
|
11596
11629
|
|
|
11597
11630
|
// src/server/index.ts
|
|
11598
|
-
var VERSION2 = "0.
|
|
11631
|
+
var VERSION2 = "0.19.1" ;
|
|
11599
11632
|
|
|
11600
11633
|
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
11634
|
//# sourceMappingURL=index.js.map
|