mailery 0.17.1 → 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 +1548 -353
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -5
- package/dist/index.d.ts +29 -5
- package/dist/index.js +1548 -353
- package/dist/index.js.map +1 -1
- package/dist/{null-DhkTG7mq.d.cts → null-DxTHQSrJ.d.cts} +164 -8
- package/dist/{null-DhkTG7mq.d.ts → null-DxTHQSrJ.d.ts} +164 -8
- package/dist/testing.cjs +808 -210
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +5 -2
- package/dist/testing.d.ts +5 -2
- package/dist/testing.js +808 -210
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -32,17 +32,41 @@ interface AdapterFilter {
|
|
|
32
32
|
createdAfter?: Date;
|
|
33
33
|
createdBefore?: Date;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Order for `ContactAdapter.query`. `field` names a field on the host's
|
|
37
|
+
* contact record (for MongoContactAdapter, a document path such as
|
|
38
|
+
* `updatedAt`), not a key of the `Contact` projection. Ties break on the
|
|
39
|
+
* contact id, ascending, so the order is total and pagination is stable.
|
|
40
|
+
*/
|
|
41
|
+
interface AdapterSort {
|
|
42
|
+
field: string;
|
|
43
|
+
direction: 'asc' | 'desc';
|
|
44
|
+
}
|
|
35
45
|
interface ContactAdapter {
|
|
36
46
|
getById(externalId: string): Promise<Contact | null>;
|
|
37
47
|
getByEmail(email: string): Promise<Contact | null>;
|
|
38
48
|
getBatch(externalIds: string[]): Promise<Map<string, Contact>>;
|
|
49
|
+
/**
|
|
50
|
+
* One page of contacts matching `filter`. Without `opts.sort` the order is
|
|
51
|
+
* the adapter's own and must be stable across calls (MongoContactAdapter:
|
|
52
|
+
* by id). `opts.sort` is honoured only by adapters that declare
|
|
53
|
+
* `supportsSort: true`; mailery never passes it to one that does not.
|
|
54
|
+
*/
|
|
39
55
|
query(filter: AdapterFilter, opts: {
|
|
40
56
|
limit: number;
|
|
41
57
|
cursor?: string;
|
|
58
|
+
sort?: AdapterSort;
|
|
42
59
|
}): Promise<{
|
|
43
60
|
contacts: Contact[];
|
|
44
61
|
nextCursor?: string;
|
|
45
62
|
}>;
|
|
63
|
+
/**
|
|
64
|
+
* True when `query` honours `opts.sort` (with an opaque cursor that
|
|
65
|
+
* encodes the sort position). Optional: adapters written before 0.18 omit
|
|
66
|
+
* it, and a broadcast with an `order` is then refused at create time
|
|
67
|
+
* rather than silently sent in id order.
|
|
68
|
+
*/
|
|
69
|
+
readonly supportsSort?: boolean;
|
|
46
70
|
count(filter: AdapterFilter): Promise<number>;
|
|
47
71
|
addTags?(externalId: string, tags: string[]): Promise<void>;
|
|
48
72
|
removeTags?(externalId: string, tags: string[]): Promise<void>;
|
|
@@ -448,7 +472,13 @@ type QueueDriverConfig = {
|
|
|
448
472
|
* server-shaped interfaces.
|
|
449
473
|
*/
|
|
450
474
|
type SubscriptionStatus = 'subscribed' | 'pending_doi' | 'unsubscribed' | 'bounced' | 'complained';
|
|
451
|
-
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed' | 'cancelled'
|
|
475
|
+
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed' | 'cancelled'
|
|
476
|
+
/**
|
|
477
|
+
* A broadcast send parked because its broadcast is paused (stop rule,
|
|
478
|
+
* circuit breaker, operator). Never dispatched while held; resuming the
|
|
479
|
+
* broadcast re-queues it.
|
|
480
|
+
*/
|
|
481
|
+
| 'held';
|
|
452
482
|
type TemplateKind = 'transactional' | 'marketing';
|
|
453
483
|
/**
|
|
454
484
|
* How a template's body goes on the wire.
|
|
@@ -466,7 +496,12 @@ type TemplateBodyFormat = 'multipart' | 'text_only';
|
|
|
466
496
|
type SuppressionScope = 'all' | 'marketing' | 'transactional';
|
|
467
497
|
type SuppressionReason = 'unsubscribed' | 'hard_bounce' | 'complaint' | 'manual' | 'list_cleaning' | 'gdpr_forget';
|
|
468
498
|
type FlowRunStatus = 'active' | 'completed' | 'exited' | 'failed';
|
|
469
|
-
|
|
499
|
+
/**
|
|
500
|
+
* `paused` — dispatch stopped with the broadcast re-openable: a wave reached
|
|
501
|
+
* its `recipientCap`, a stop rule or the circuit breaker fired, or an
|
|
502
|
+
* operator paused it. `pauseReason` says which; `resume` re-opens it.
|
|
503
|
+
*/
|
|
504
|
+
type BroadcastStatus = 'draft' | 'scheduled' | 'sending' | 'paused' | 'sent' | 'cancelled' | 'failed';
|
|
470
505
|
type HealthStatus = 'healthy' | 'degraded' | 'tripped';
|
|
471
506
|
type FlowGoal = 'activation' | 'conversion' | 'retention' | 'reactivation' | 'transactional' | 'broadcast';
|
|
472
507
|
|
|
@@ -588,6 +623,15 @@ interface DnsblConfig {
|
|
|
588
623
|
dedicatedIps?: string[];
|
|
589
624
|
/** Hours between automatic runs. Default 24. Set to 0 to disable scheduled runs. */
|
|
590
625
|
intervalHours?: number;
|
|
626
|
+
/**
|
|
627
|
+
* Spamhaus Data Query Service key (free at spamhaus.com for low volume).
|
|
628
|
+
* Spamhaus refuses queries that arrive through public or shared resolvers —
|
|
629
|
+
* Google, Cloudflare, and cloud VPC resolvers such as AWS's — answering
|
|
630
|
+
* 127.255.255.254 instead of a verdict. With a key, every `*.spamhaus.org`
|
|
631
|
+
* list is queried as `<key>.<zone>.dq.spamhaus.net`, which answers from
|
|
632
|
+
* anywhere. Rows and the UI keep the public list name; the key is never stored.
|
|
633
|
+
*/
|
|
634
|
+
spamhausDqsKey?: string;
|
|
591
635
|
}
|
|
592
636
|
interface MailTesterConfig {
|
|
593
637
|
/** API key from your Mail-Tester paid plan. */
|
|
@@ -678,6 +722,25 @@ interface CircuitBreakerThresholds {
|
|
|
678
722
|
windowMinutes: number;
|
|
679
723
|
minSendsBeforeEval: number;
|
|
680
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Automatic stop rules for one broadcast. A breach pauses the broadcast
|
|
727
|
+
* (`pauseReason.code: 'stop_rule'`): no more enqueues, and its queued sends
|
|
728
|
+
* are held until an explicit resume. Rates are measured over the
|
|
729
|
+
* broadcast's sends with a known outcome (delivered or bounced) and only
|
|
730
|
+
* once `minSample` of them are in. A breach is `rate > threshold`.
|
|
731
|
+
*/
|
|
732
|
+
interface BroadcastStopRules {
|
|
733
|
+
/** Default true. */
|
|
734
|
+
enabled: boolean;
|
|
735
|
+
/** Hard bounces, % of outcomes. Default 2. */
|
|
736
|
+
hardBounceRatePct: number;
|
|
737
|
+
/** Complaints (spam reports), % of outcomes. Default 0.1. */
|
|
738
|
+
complaintRatePct: number;
|
|
739
|
+
/** Unsubscribes attributed to the broadcast, % of outcomes. Default 1. */
|
|
740
|
+
unsubscribeRatePct: number;
|
|
741
|
+
/** Outcomes required before any rule is evaluated. Default 100. */
|
|
742
|
+
minSample: number;
|
|
743
|
+
}
|
|
681
744
|
interface BotFilterConfig {
|
|
682
745
|
/**
|
|
683
746
|
* User agents matching this pattern are treated as automated. Applied to
|
|
@@ -840,6 +903,12 @@ interface MailerConfig {
|
|
|
840
903
|
broadcastConfirmationThreshold?: number;
|
|
841
904
|
broadcastEnqueueBatchSize?: number;
|
|
842
905
|
broadcastEnqueueMaxWaiting?: number;
|
|
906
|
+
/**
|
|
907
|
+
* Defaults for every broadcast's automatic stop rules; a broadcast can
|
|
908
|
+
* override any of them (`stopRules` on create/patch/resume). See
|
|
909
|
+
* `BroadcastStopRules`.
|
|
910
|
+
*/
|
|
911
|
+
broadcastStopRules?: Partial<BroadcastStopRules>;
|
|
843
912
|
workerless?: boolean;
|
|
844
913
|
tickIntervalSeconds?: number;
|
|
845
914
|
sendConcurrency?: number;
|
|
@@ -884,16 +953,33 @@ interface MailerConfig {
|
|
|
884
953
|
getAdminActor?: (req: any) => string;
|
|
885
954
|
onCircuitBreakerTrip?: (info: {
|
|
886
955
|
reason: string;
|
|
887
|
-
rates: Record<string, number>;
|
|
956
|
+
rates: Record<string, number | null>;
|
|
888
957
|
}) => Promise<void> | void;
|
|
889
958
|
onSendFailure?: (info: {
|
|
890
959
|
send: any;
|
|
891
960
|
error: Error;
|
|
892
961
|
}) => Promise<void> | void;
|
|
962
|
+
/**
|
|
963
|
+
* Called when a broadcast is paused by a stop rule, the circuit breaker, a
|
|
964
|
+
* reached cap, or an operator. Alerting belongs here: a stop-rule pause
|
|
965
|
+
* waits for a human.
|
|
966
|
+
*/
|
|
967
|
+
onBroadcastPaused?: (info: {
|
|
968
|
+
broadcastId: string;
|
|
969
|
+
slug: string;
|
|
970
|
+
reason: {
|
|
971
|
+
code: string;
|
|
972
|
+
message: string;
|
|
973
|
+
at: Date;
|
|
974
|
+
details?: Record<string, unknown>;
|
|
975
|
+
};
|
|
976
|
+
heldSends: number;
|
|
977
|
+
}) => Promise<void> | void;
|
|
893
978
|
handlebarsHelpers?: Record<string, Handlebars.HelperDelegate>;
|
|
894
979
|
}
|
|
895
980
|
type ResolvedConfig = Required<Pick<MailerConfig, 'collectionPrefix' | 'requireDoubleOptIn' | 'unsubscribeTokenLifetimeDays' | 'transactionalRespectUnsubscribe' | 'unsubscribeWriteTimeoutMs' | 'doiTemplateSlug' | 'doiTokenLifetimeDays' | 'broadcastConfirmationThreshold' | 'broadcastEnqueueBatchSize' | 'broadcastEnqueueMaxWaiting' | 'workerless' | 'tickIntervalSeconds' | 'sendConcurrency' | 'sendRatePerSecond' | 'softBouncePromotionThreshold' | 'softBouncePromotionWindowDays' | 'webhookRetryAttempts' | 'sendRetryAttempts' | 'trackOpens' | 'trackClicks' | 'storeTrackingIp' | 'storeRenderedBody' | 'requireSignedTrackingUrls' | 'trackingUrlLifetimeDays'>> & {
|
|
896
981
|
circuitBreaker: CircuitBreakerThresholds;
|
|
982
|
+
broadcastStopRules: BroadcastStopRules;
|
|
897
983
|
} & MailerConfig;
|
|
898
984
|
|
|
899
985
|
/**
|
|
@@ -1168,6 +1254,12 @@ interface SendDoc {
|
|
|
1168
1254
|
updatedAt: Date;
|
|
1169
1255
|
sentAt: Date | null;
|
|
1170
1256
|
deliveredAt: Date | null;
|
|
1257
|
+
/**
|
|
1258
|
+
* Broadcast sends: the moment the send was meant to go (scheduledAt, or
|
|
1259
|
+
* the recipient-local slot). A held send is re-queued with the delay that
|
|
1260
|
+
* remains until then.
|
|
1261
|
+
*/
|
|
1262
|
+
notBefore?: Date | null;
|
|
1171
1263
|
}
|
|
1172
1264
|
interface SuppressionDoc {
|
|
1173
1265
|
_id?: ObjectId;
|
|
@@ -1197,6 +1289,43 @@ interface BroadcastDoc {
|
|
|
1197
1289
|
recipientCount: number | null;
|
|
1198
1290
|
/** When true, dispatch fires each recipient at their local-timezone equivalent of `scheduledAt`. */
|
|
1199
1291
|
respectRecipientTimezone?: boolean;
|
|
1292
|
+
/**
|
|
1293
|
+
* Native waves: the most send rows this broadcast may have. Dispatch stops
|
|
1294
|
+
* enqueueing when the broadcast's send rows (every status, earlier waves
|
|
1295
|
+
* included) reach it, and parks the broadcast in `paused` with
|
|
1296
|
+
* `pauseReason.code === 'cap_reached'` if eligible recipients remain.
|
|
1297
|
+
* Raising it and resuming sends the next slice. Null or absent: no cap.
|
|
1298
|
+
*/
|
|
1299
|
+
recipientCap?: number | null;
|
|
1300
|
+
/** Order recipients are taken in (host-side sort). Null or absent: the adapter's own order. */
|
|
1301
|
+
order?: BroadcastOrder | null;
|
|
1302
|
+
/** Set while `status === 'paused'`. */
|
|
1303
|
+
pausedAt?: Date | null;
|
|
1304
|
+
pauseReason?: BroadcastPauseReason | null;
|
|
1305
|
+
/** Lease held by the one dispatcher allowed to enqueue for this broadcast. */
|
|
1306
|
+
dispatchLeaseId?: string | null;
|
|
1307
|
+
/** Bumped on every (re)start of dispatch; part of the dispatch job id. */
|
|
1308
|
+
dispatchGeneration?: number;
|
|
1309
|
+
/** Why dispatch set status 'failed'. */
|
|
1310
|
+
failureReason?: string | null;
|
|
1311
|
+
/** Per-broadcast overrides of `MailerConfig.broadcastStopRules`. */
|
|
1312
|
+
stopRules?: Partial<{
|
|
1313
|
+
enabled: boolean;
|
|
1314
|
+
hardBounceRatePct: number;
|
|
1315
|
+
complaintRatePct: number;
|
|
1316
|
+
unsubscribeRatePct: number;
|
|
1317
|
+
minSample: number;
|
|
1318
|
+
}> | null;
|
|
1319
|
+
/**
|
|
1320
|
+
* First stop-rule breach seen after the broadcast had nothing left to
|
|
1321
|
+
* hold (status 'sent', no queued sends) — recorded, since there was
|
|
1322
|
+
* nothing to pause.
|
|
1323
|
+
*/
|
|
1324
|
+
stopRuleBreach?: {
|
|
1325
|
+
at: Date;
|
|
1326
|
+
sample: number;
|
|
1327
|
+
breaches: StopRuleBreach[];
|
|
1328
|
+
} | null;
|
|
1200
1329
|
stats: {
|
|
1201
1330
|
sent: number;
|
|
1202
1331
|
delivered: number;
|
|
@@ -1210,6 +1339,29 @@ interface BroadcastDoc {
|
|
|
1210
1339
|
createdBy: string;
|
|
1211
1340
|
updatedAt: Date;
|
|
1212
1341
|
}
|
|
1342
|
+
interface BroadcastOrder {
|
|
1343
|
+
/** A host field, e.g. `updatedAt` for "most recently active first" with `direction: 'desc'`. */
|
|
1344
|
+
field: string;
|
|
1345
|
+
direction: 'asc' | 'desc';
|
|
1346
|
+
}
|
|
1347
|
+
interface StopRuleBreach {
|
|
1348
|
+
rule: 'hardBounceRatePct' | 'complaintRatePct' | 'unsubscribeRatePct';
|
|
1349
|
+
count: number;
|
|
1350
|
+
ratePct: number;
|
|
1351
|
+
thresholdPct: number;
|
|
1352
|
+
}
|
|
1353
|
+
interface BroadcastPauseReason {
|
|
1354
|
+
/**
|
|
1355
|
+
* cap_reached — the wave's recipientCap is spent; raise it and resume.
|
|
1356
|
+
* stop_rule — a per-broadcast bounce/complaint/unsubscribe threshold was crossed.
|
|
1357
|
+
* circuit_breaker — the sender domain's marketing circuit breaker tripped.
|
|
1358
|
+
* manual — paused by an operator.
|
|
1359
|
+
*/
|
|
1360
|
+
code: 'cap_reached' | 'stop_rule' | 'circuit_breaker' | 'manual';
|
|
1361
|
+
message: string;
|
|
1362
|
+
at: Date;
|
|
1363
|
+
details?: Record<string, unknown>;
|
|
1364
|
+
}
|
|
1213
1365
|
interface OutboxDoc {
|
|
1214
1366
|
_id?: ObjectId;
|
|
1215
1367
|
payload: {
|
|
@@ -1281,11 +1433,15 @@ interface HealthDoc {
|
|
|
1281
1433
|
complained: number;
|
|
1282
1434
|
failedToSend: number;
|
|
1283
1435
|
};
|
|
1436
|
+
/**
|
|
1437
|
+
* Bounce/complaint rates are per send in the window; failureRate is failed
|
|
1438
|
+
* attempts over all attempts. Null when the window has no denominator yet.
|
|
1439
|
+
*/
|
|
1284
1440
|
rates: {
|
|
1285
|
-
bounceRate: number;
|
|
1286
|
-
hardBounceRate: number;
|
|
1287
|
-
complaintRate: number;
|
|
1288
|
-
failureRate: number;
|
|
1441
|
+
bounceRate: number | null;
|
|
1442
|
+
hardBounceRate: number | null;
|
|
1443
|
+
complaintRate: number | null;
|
|
1444
|
+
failureRate: number | null;
|
|
1289
1445
|
};
|
|
1290
1446
|
status: HealthStatus;
|
|
1291
1447
|
trippedAt: Date | null;
|
|
@@ -1713,4 +1869,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1713
1869
|
reset(): void;
|
|
1714
1870
|
}
|
|
1715
1871
|
|
|
1716
|
-
export {
|
|
1872
|
+
export { type VarsResolveInfo as $, type AdapterFilter as A, type BotFilterConfig as B, type ContactAdapter as C, type DeliveryWindow as D, type EventDoc as E, type FlowStep as F, type SendDoc as G, type HealthDoc as H, type SendStatus as I, type SenderDomainConfig as J, type SenderDomainRegistry as K, type LeadDoc as L, type MailProvider as M, type NormalizedEvent as N, type OutboxDoc as O, type Predicate as P, type SenderDomainValidation as Q, type RunnerContext as R, type SendArgs as S, type TemplateDoc as T, type SubscriptionDoc as U, type SubscriptionStatus as V, type SuppressionDoc as W, type SuppressionReason as X, type TemplateKind as Y, type TemplateVersionDoc as Z, type VarsAdapter as _, type Contact as a, type WebhookEventDoc as a0, defineVars as a1, ensureIndexes as a2, getCollections as a3, validateSenderDomain as a4, varsJsonSchema as a5, type FlowTrigger as a6, type QueueDriverConfig as a7, type AdapterSort as b, type SendResult as c, type MailTesterFeedback as d, Mailer as e, type Collections as f, type FlowDoc as g, type SuppressionScope as h, type SegmentFilter as i, type AuditLogDoc as j, type BroadcastDoc as k, type BroadcastOrder as l, type BroadcastPauseReason as m, type BroadcastStatus as n, type CircuitBreakerThresholds as o, type ContactTagDoc as p, type FlowGoal as q, type FlowRunDoc as r, type FlowRunStatus as s, type FlowVersionDoc as t, type HealthStatus as u, type MailerConfig as v, NullProvider as w, RESERVED_VAR_KEYS as x, type RedisOptions as y, type SegmentDefinition as z };
|
|
@@ -32,17 +32,41 @@ interface AdapterFilter {
|
|
|
32
32
|
createdAfter?: Date;
|
|
33
33
|
createdBefore?: Date;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Order for `ContactAdapter.query`. `field` names a field on the host's
|
|
37
|
+
* contact record (for MongoContactAdapter, a document path such as
|
|
38
|
+
* `updatedAt`), not a key of the `Contact` projection. Ties break on the
|
|
39
|
+
* contact id, ascending, so the order is total and pagination is stable.
|
|
40
|
+
*/
|
|
41
|
+
interface AdapterSort {
|
|
42
|
+
field: string;
|
|
43
|
+
direction: 'asc' | 'desc';
|
|
44
|
+
}
|
|
35
45
|
interface ContactAdapter {
|
|
36
46
|
getById(externalId: string): Promise<Contact | null>;
|
|
37
47
|
getByEmail(email: string): Promise<Contact | null>;
|
|
38
48
|
getBatch(externalIds: string[]): Promise<Map<string, Contact>>;
|
|
49
|
+
/**
|
|
50
|
+
* One page of contacts matching `filter`. Without `opts.sort` the order is
|
|
51
|
+
* the adapter's own and must be stable across calls (MongoContactAdapter:
|
|
52
|
+
* by id). `opts.sort` is honoured only by adapters that declare
|
|
53
|
+
* `supportsSort: true`; mailery never passes it to one that does not.
|
|
54
|
+
*/
|
|
39
55
|
query(filter: AdapterFilter, opts: {
|
|
40
56
|
limit: number;
|
|
41
57
|
cursor?: string;
|
|
58
|
+
sort?: AdapterSort;
|
|
42
59
|
}): Promise<{
|
|
43
60
|
contacts: Contact[];
|
|
44
61
|
nextCursor?: string;
|
|
45
62
|
}>;
|
|
63
|
+
/**
|
|
64
|
+
* True when `query` honours `opts.sort` (with an opaque cursor that
|
|
65
|
+
* encodes the sort position). Optional: adapters written before 0.18 omit
|
|
66
|
+
* it, and a broadcast with an `order` is then refused at create time
|
|
67
|
+
* rather than silently sent in id order.
|
|
68
|
+
*/
|
|
69
|
+
readonly supportsSort?: boolean;
|
|
46
70
|
count(filter: AdapterFilter): Promise<number>;
|
|
47
71
|
addTags?(externalId: string, tags: string[]): Promise<void>;
|
|
48
72
|
removeTags?(externalId: string, tags: string[]): Promise<void>;
|
|
@@ -448,7 +472,13 @@ type QueueDriverConfig = {
|
|
|
448
472
|
* server-shaped interfaces.
|
|
449
473
|
*/
|
|
450
474
|
type SubscriptionStatus = 'subscribed' | 'pending_doi' | 'unsubscribed' | 'bounced' | 'complained';
|
|
451
|
-
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed' | 'cancelled'
|
|
475
|
+
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed' | 'cancelled'
|
|
476
|
+
/**
|
|
477
|
+
* A broadcast send parked because its broadcast is paused (stop rule,
|
|
478
|
+
* circuit breaker, operator). Never dispatched while held; resuming the
|
|
479
|
+
* broadcast re-queues it.
|
|
480
|
+
*/
|
|
481
|
+
| 'held';
|
|
452
482
|
type TemplateKind = 'transactional' | 'marketing';
|
|
453
483
|
/**
|
|
454
484
|
* How a template's body goes on the wire.
|
|
@@ -466,7 +496,12 @@ type TemplateBodyFormat = 'multipart' | 'text_only';
|
|
|
466
496
|
type SuppressionScope = 'all' | 'marketing' | 'transactional';
|
|
467
497
|
type SuppressionReason = 'unsubscribed' | 'hard_bounce' | 'complaint' | 'manual' | 'list_cleaning' | 'gdpr_forget';
|
|
468
498
|
type FlowRunStatus = 'active' | 'completed' | 'exited' | 'failed';
|
|
469
|
-
|
|
499
|
+
/**
|
|
500
|
+
* `paused` — dispatch stopped with the broadcast re-openable: a wave reached
|
|
501
|
+
* its `recipientCap`, a stop rule or the circuit breaker fired, or an
|
|
502
|
+
* operator paused it. `pauseReason` says which; `resume` re-opens it.
|
|
503
|
+
*/
|
|
504
|
+
type BroadcastStatus = 'draft' | 'scheduled' | 'sending' | 'paused' | 'sent' | 'cancelled' | 'failed';
|
|
470
505
|
type HealthStatus = 'healthy' | 'degraded' | 'tripped';
|
|
471
506
|
type FlowGoal = 'activation' | 'conversion' | 'retention' | 'reactivation' | 'transactional' | 'broadcast';
|
|
472
507
|
|
|
@@ -588,6 +623,15 @@ interface DnsblConfig {
|
|
|
588
623
|
dedicatedIps?: string[];
|
|
589
624
|
/** Hours between automatic runs. Default 24. Set to 0 to disable scheduled runs. */
|
|
590
625
|
intervalHours?: number;
|
|
626
|
+
/**
|
|
627
|
+
* Spamhaus Data Query Service key (free at spamhaus.com for low volume).
|
|
628
|
+
* Spamhaus refuses queries that arrive through public or shared resolvers —
|
|
629
|
+
* Google, Cloudflare, and cloud VPC resolvers such as AWS's — answering
|
|
630
|
+
* 127.255.255.254 instead of a verdict. With a key, every `*.spamhaus.org`
|
|
631
|
+
* list is queried as `<key>.<zone>.dq.spamhaus.net`, which answers from
|
|
632
|
+
* anywhere. Rows and the UI keep the public list name; the key is never stored.
|
|
633
|
+
*/
|
|
634
|
+
spamhausDqsKey?: string;
|
|
591
635
|
}
|
|
592
636
|
interface MailTesterConfig {
|
|
593
637
|
/** API key from your Mail-Tester paid plan. */
|
|
@@ -678,6 +722,25 @@ interface CircuitBreakerThresholds {
|
|
|
678
722
|
windowMinutes: number;
|
|
679
723
|
minSendsBeforeEval: number;
|
|
680
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Automatic stop rules for one broadcast. A breach pauses the broadcast
|
|
727
|
+
* (`pauseReason.code: 'stop_rule'`): no more enqueues, and its queued sends
|
|
728
|
+
* are held until an explicit resume. Rates are measured over the
|
|
729
|
+
* broadcast's sends with a known outcome (delivered or bounced) and only
|
|
730
|
+
* once `minSample` of them are in. A breach is `rate > threshold`.
|
|
731
|
+
*/
|
|
732
|
+
interface BroadcastStopRules {
|
|
733
|
+
/** Default true. */
|
|
734
|
+
enabled: boolean;
|
|
735
|
+
/** Hard bounces, % of outcomes. Default 2. */
|
|
736
|
+
hardBounceRatePct: number;
|
|
737
|
+
/** Complaints (spam reports), % of outcomes. Default 0.1. */
|
|
738
|
+
complaintRatePct: number;
|
|
739
|
+
/** Unsubscribes attributed to the broadcast, % of outcomes. Default 1. */
|
|
740
|
+
unsubscribeRatePct: number;
|
|
741
|
+
/** Outcomes required before any rule is evaluated. Default 100. */
|
|
742
|
+
minSample: number;
|
|
743
|
+
}
|
|
681
744
|
interface BotFilterConfig {
|
|
682
745
|
/**
|
|
683
746
|
* User agents matching this pattern are treated as automated. Applied to
|
|
@@ -840,6 +903,12 @@ interface MailerConfig {
|
|
|
840
903
|
broadcastConfirmationThreshold?: number;
|
|
841
904
|
broadcastEnqueueBatchSize?: number;
|
|
842
905
|
broadcastEnqueueMaxWaiting?: number;
|
|
906
|
+
/**
|
|
907
|
+
* Defaults for every broadcast's automatic stop rules; a broadcast can
|
|
908
|
+
* override any of them (`stopRules` on create/patch/resume). See
|
|
909
|
+
* `BroadcastStopRules`.
|
|
910
|
+
*/
|
|
911
|
+
broadcastStopRules?: Partial<BroadcastStopRules>;
|
|
843
912
|
workerless?: boolean;
|
|
844
913
|
tickIntervalSeconds?: number;
|
|
845
914
|
sendConcurrency?: number;
|
|
@@ -884,16 +953,33 @@ interface MailerConfig {
|
|
|
884
953
|
getAdminActor?: (req: any) => string;
|
|
885
954
|
onCircuitBreakerTrip?: (info: {
|
|
886
955
|
reason: string;
|
|
887
|
-
rates: Record<string, number>;
|
|
956
|
+
rates: Record<string, number | null>;
|
|
888
957
|
}) => Promise<void> | void;
|
|
889
958
|
onSendFailure?: (info: {
|
|
890
959
|
send: any;
|
|
891
960
|
error: Error;
|
|
892
961
|
}) => Promise<void> | void;
|
|
962
|
+
/**
|
|
963
|
+
* Called when a broadcast is paused by a stop rule, the circuit breaker, a
|
|
964
|
+
* reached cap, or an operator. Alerting belongs here: a stop-rule pause
|
|
965
|
+
* waits for a human.
|
|
966
|
+
*/
|
|
967
|
+
onBroadcastPaused?: (info: {
|
|
968
|
+
broadcastId: string;
|
|
969
|
+
slug: string;
|
|
970
|
+
reason: {
|
|
971
|
+
code: string;
|
|
972
|
+
message: string;
|
|
973
|
+
at: Date;
|
|
974
|
+
details?: Record<string, unknown>;
|
|
975
|
+
};
|
|
976
|
+
heldSends: number;
|
|
977
|
+
}) => Promise<void> | void;
|
|
893
978
|
handlebarsHelpers?: Record<string, Handlebars.HelperDelegate>;
|
|
894
979
|
}
|
|
895
980
|
type ResolvedConfig = Required<Pick<MailerConfig, 'collectionPrefix' | 'requireDoubleOptIn' | 'unsubscribeTokenLifetimeDays' | 'transactionalRespectUnsubscribe' | 'unsubscribeWriteTimeoutMs' | 'doiTemplateSlug' | 'doiTokenLifetimeDays' | 'broadcastConfirmationThreshold' | 'broadcastEnqueueBatchSize' | 'broadcastEnqueueMaxWaiting' | 'workerless' | 'tickIntervalSeconds' | 'sendConcurrency' | 'sendRatePerSecond' | 'softBouncePromotionThreshold' | 'softBouncePromotionWindowDays' | 'webhookRetryAttempts' | 'sendRetryAttempts' | 'trackOpens' | 'trackClicks' | 'storeTrackingIp' | 'storeRenderedBody' | 'requireSignedTrackingUrls' | 'trackingUrlLifetimeDays'>> & {
|
|
896
981
|
circuitBreaker: CircuitBreakerThresholds;
|
|
982
|
+
broadcastStopRules: BroadcastStopRules;
|
|
897
983
|
} & MailerConfig;
|
|
898
984
|
|
|
899
985
|
/**
|
|
@@ -1168,6 +1254,12 @@ interface SendDoc {
|
|
|
1168
1254
|
updatedAt: Date;
|
|
1169
1255
|
sentAt: Date | null;
|
|
1170
1256
|
deliveredAt: Date | null;
|
|
1257
|
+
/**
|
|
1258
|
+
* Broadcast sends: the moment the send was meant to go (scheduledAt, or
|
|
1259
|
+
* the recipient-local slot). A held send is re-queued with the delay that
|
|
1260
|
+
* remains until then.
|
|
1261
|
+
*/
|
|
1262
|
+
notBefore?: Date | null;
|
|
1171
1263
|
}
|
|
1172
1264
|
interface SuppressionDoc {
|
|
1173
1265
|
_id?: ObjectId;
|
|
@@ -1197,6 +1289,43 @@ interface BroadcastDoc {
|
|
|
1197
1289
|
recipientCount: number | null;
|
|
1198
1290
|
/** When true, dispatch fires each recipient at their local-timezone equivalent of `scheduledAt`. */
|
|
1199
1291
|
respectRecipientTimezone?: boolean;
|
|
1292
|
+
/**
|
|
1293
|
+
* Native waves: the most send rows this broadcast may have. Dispatch stops
|
|
1294
|
+
* enqueueing when the broadcast's send rows (every status, earlier waves
|
|
1295
|
+
* included) reach it, and parks the broadcast in `paused` with
|
|
1296
|
+
* `pauseReason.code === 'cap_reached'` if eligible recipients remain.
|
|
1297
|
+
* Raising it and resuming sends the next slice. Null or absent: no cap.
|
|
1298
|
+
*/
|
|
1299
|
+
recipientCap?: number | null;
|
|
1300
|
+
/** Order recipients are taken in (host-side sort). Null or absent: the adapter's own order. */
|
|
1301
|
+
order?: BroadcastOrder | null;
|
|
1302
|
+
/** Set while `status === 'paused'`. */
|
|
1303
|
+
pausedAt?: Date | null;
|
|
1304
|
+
pauseReason?: BroadcastPauseReason | null;
|
|
1305
|
+
/** Lease held by the one dispatcher allowed to enqueue for this broadcast. */
|
|
1306
|
+
dispatchLeaseId?: string | null;
|
|
1307
|
+
/** Bumped on every (re)start of dispatch; part of the dispatch job id. */
|
|
1308
|
+
dispatchGeneration?: number;
|
|
1309
|
+
/** Why dispatch set status 'failed'. */
|
|
1310
|
+
failureReason?: string | null;
|
|
1311
|
+
/** Per-broadcast overrides of `MailerConfig.broadcastStopRules`. */
|
|
1312
|
+
stopRules?: Partial<{
|
|
1313
|
+
enabled: boolean;
|
|
1314
|
+
hardBounceRatePct: number;
|
|
1315
|
+
complaintRatePct: number;
|
|
1316
|
+
unsubscribeRatePct: number;
|
|
1317
|
+
minSample: number;
|
|
1318
|
+
}> | null;
|
|
1319
|
+
/**
|
|
1320
|
+
* First stop-rule breach seen after the broadcast had nothing left to
|
|
1321
|
+
* hold (status 'sent', no queued sends) — recorded, since there was
|
|
1322
|
+
* nothing to pause.
|
|
1323
|
+
*/
|
|
1324
|
+
stopRuleBreach?: {
|
|
1325
|
+
at: Date;
|
|
1326
|
+
sample: number;
|
|
1327
|
+
breaches: StopRuleBreach[];
|
|
1328
|
+
} | null;
|
|
1200
1329
|
stats: {
|
|
1201
1330
|
sent: number;
|
|
1202
1331
|
delivered: number;
|
|
@@ -1210,6 +1339,29 @@ interface BroadcastDoc {
|
|
|
1210
1339
|
createdBy: string;
|
|
1211
1340
|
updatedAt: Date;
|
|
1212
1341
|
}
|
|
1342
|
+
interface BroadcastOrder {
|
|
1343
|
+
/** A host field, e.g. `updatedAt` for "most recently active first" with `direction: 'desc'`. */
|
|
1344
|
+
field: string;
|
|
1345
|
+
direction: 'asc' | 'desc';
|
|
1346
|
+
}
|
|
1347
|
+
interface StopRuleBreach {
|
|
1348
|
+
rule: 'hardBounceRatePct' | 'complaintRatePct' | 'unsubscribeRatePct';
|
|
1349
|
+
count: number;
|
|
1350
|
+
ratePct: number;
|
|
1351
|
+
thresholdPct: number;
|
|
1352
|
+
}
|
|
1353
|
+
interface BroadcastPauseReason {
|
|
1354
|
+
/**
|
|
1355
|
+
* cap_reached — the wave's recipientCap is spent; raise it and resume.
|
|
1356
|
+
* stop_rule — a per-broadcast bounce/complaint/unsubscribe threshold was crossed.
|
|
1357
|
+
* circuit_breaker — the sender domain's marketing circuit breaker tripped.
|
|
1358
|
+
* manual — paused by an operator.
|
|
1359
|
+
*/
|
|
1360
|
+
code: 'cap_reached' | 'stop_rule' | 'circuit_breaker' | 'manual';
|
|
1361
|
+
message: string;
|
|
1362
|
+
at: Date;
|
|
1363
|
+
details?: Record<string, unknown>;
|
|
1364
|
+
}
|
|
1213
1365
|
interface OutboxDoc {
|
|
1214
1366
|
_id?: ObjectId;
|
|
1215
1367
|
payload: {
|
|
@@ -1281,11 +1433,15 @@ interface HealthDoc {
|
|
|
1281
1433
|
complained: number;
|
|
1282
1434
|
failedToSend: number;
|
|
1283
1435
|
};
|
|
1436
|
+
/**
|
|
1437
|
+
* Bounce/complaint rates are per send in the window; failureRate is failed
|
|
1438
|
+
* attempts over all attempts. Null when the window has no denominator yet.
|
|
1439
|
+
*/
|
|
1284
1440
|
rates: {
|
|
1285
|
-
bounceRate: number;
|
|
1286
|
-
hardBounceRate: number;
|
|
1287
|
-
complaintRate: number;
|
|
1288
|
-
failureRate: number;
|
|
1441
|
+
bounceRate: number | null;
|
|
1442
|
+
hardBounceRate: number | null;
|
|
1443
|
+
complaintRate: number | null;
|
|
1444
|
+
failureRate: number | null;
|
|
1289
1445
|
};
|
|
1290
1446
|
status: HealthStatus;
|
|
1291
1447
|
trippedAt: Date | null;
|
|
@@ -1713,4 +1869,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1713
1869
|
reset(): void;
|
|
1714
1870
|
}
|
|
1715
1871
|
|
|
1716
|
-
export {
|
|
1872
|
+
export { type VarsResolveInfo as $, type AdapterFilter as A, type BotFilterConfig as B, type ContactAdapter as C, type DeliveryWindow as D, type EventDoc as E, type FlowStep as F, type SendDoc as G, type HealthDoc as H, type SendStatus as I, type SenderDomainConfig as J, type SenderDomainRegistry as K, type LeadDoc as L, type MailProvider as M, type NormalizedEvent as N, type OutboxDoc as O, type Predicate as P, type SenderDomainValidation as Q, type RunnerContext as R, type SendArgs as S, type TemplateDoc as T, type SubscriptionDoc as U, type SubscriptionStatus as V, type SuppressionDoc as W, type SuppressionReason as X, type TemplateKind as Y, type TemplateVersionDoc as Z, type VarsAdapter as _, type Contact as a, type WebhookEventDoc as a0, defineVars as a1, ensureIndexes as a2, getCollections as a3, validateSenderDomain as a4, varsJsonSchema as a5, type FlowTrigger as a6, type QueueDriverConfig as a7, type AdapterSort as b, type SendResult as c, type MailTesterFeedback as d, Mailer as e, type Collections as f, type FlowDoc as g, type SuppressionScope as h, type SegmentFilter as i, type AuditLogDoc as j, type BroadcastDoc as k, type BroadcastOrder as l, type BroadcastPauseReason as m, type BroadcastStatus as n, type CircuitBreakerThresholds as o, type ContactTagDoc as p, type FlowGoal as q, type FlowRunDoc as r, type FlowRunStatus as s, type FlowVersionDoc as t, type HealthStatus as u, type MailerConfig as v, NullProvider as w, RESERVED_VAR_KEYS as x, type RedisOptions as y, type SegmentDefinition as z };
|