mailery 0.17.1 → 0.18.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.cjs +1501 -332
- 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 +1501 -332
- package/dist/index.js.map +1 -1
- package/dist/{null-DhkTG7mq.d.cts → null-BFipn5tF.d.cts} +146 -3
- package/dist/{null-DhkTG7mq.d.ts → null-BFipn5tF.d.ts} +146 -3
- package/dist/testing.cjs +760 -188
- 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 +760 -188
- 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
|
|
|
@@ -678,6 +713,25 @@ interface CircuitBreakerThresholds {
|
|
|
678
713
|
windowMinutes: number;
|
|
679
714
|
minSendsBeforeEval: number;
|
|
680
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Automatic stop rules for one broadcast. A breach pauses the broadcast
|
|
718
|
+
* (`pauseReason.code: 'stop_rule'`): no more enqueues, and its queued sends
|
|
719
|
+
* are held until an explicit resume. Rates are measured over the
|
|
720
|
+
* broadcast's sends with a known outcome (delivered or bounced) and only
|
|
721
|
+
* once `minSample` of them are in. A breach is `rate > threshold`.
|
|
722
|
+
*/
|
|
723
|
+
interface BroadcastStopRules {
|
|
724
|
+
/** Default true. */
|
|
725
|
+
enabled: boolean;
|
|
726
|
+
/** Hard bounces, % of outcomes. Default 2. */
|
|
727
|
+
hardBounceRatePct: number;
|
|
728
|
+
/** Complaints (spam reports), % of outcomes. Default 0.1. */
|
|
729
|
+
complaintRatePct: number;
|
|
730
|
+
/** Unsubscribes attributed to the broadcast, % of outcomes. Default 1. */
|
|
731
|
+
unsubscribeRatePct: number;
|
|
732
|
+
/** Outcomes required before any rule is evaluated. Default 100. */
|
|
733
|
+
minSample: number;
|
|
734
|
+
}
|
|
681
735
|
interface BotFilterConfig {
|
|
682
736
|
/**
|
|
683
737
|
* User agents matching this pattern are treated as automated. Applied to
|
|
@@ -840,6 +894,12 @@ interface MailerConfig {
|
|
|
840
894
|
broadcastConfirmationThreshold?: number;
|
|
841
895
|
broadcastEnqueueBatchSize?: number;
|
|
842
896
|
broadcastEnqueueMaxWaiting?: number;
|
|
897
|
+
/**
|
|
898
|
+
* Defaults for every broadcast's automatic stop rules; a broadcast can
|
|
899
|
+
* override any of them (`stopRules` on create/patch/resume). See
|
|
900
|
+
* `BroadcastStopRules`.
|
|
901
|
+
*/
|
|
902
|
+
broadcastStopRules?: Partial<BroadcastStopRules>;
|
|
843
903
|
workerless?: boolean;
|
|
844
904
|
tickIntervalSeconds?: number;
|
|
845
905
|
sendConcurrency?: number;
|
|
@@ -890,10 +950,27 @@ interface MailerConfig {
|
|
|
890
950
|
send: any;
|
|
891
951
|
error: Error;
|
|
892
952
|
}) => Promise<void> | void;
|
|
953
|
+
/**
|
|
954
|
+
* Called when a broadcast is paused by a stop rule, the circuit breaker, a
|
|
955
|
+
* reached cap, or an operator. Alerting belongs here: a stop-rule pause
|
|
956
|
+
* waits for a human.
|
|
957
|
+
*/
|
|
958
|
+
onBroadcastPaused?: (info: {
|
|
959
|
+
broadcastId: string;
|
|
960
|
+
slug: string;
|
|
961
|
+
reason: {
|
|
962
|
+
code: string;
|
|
963
|
+
message: string;
|
|
964
|
+
at: Date;
|
|
965
|
+
details?: Record<string, unknown>;
|
|
966
|
+
};
|
|
967
|
+
heldSends: number;
|
|
968
|
+
}) => Promise<void> | void;
|
|
893
969
|
handlebarsHelpers?: Record<string, Handlebars.HelperDelegate>;
|
|
894
970
|
}
|
|
895
971
|
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
972
|
circuitBreaker: CircuitBreakerThresholds;
|
|
973
|
+
broadcastStopRules: BroadcastStopRules;
|
|
897
974
|
} & MailerConfig;
|
|
898
975
|
|
|
899
976
|
/**
|
|
@@ -1168,6 +1245,12 @@ interface SendDoc {
|
|
|
1168
1245
|
updatedAt: Date;
|
|
1169
1246
|
sentAt: Date | null;
|
|
1170
1247
|
deliveredAt: Date | null;
|
|
1248
|
+
/**
|
|
1249
|
+
* Broadcast sends: the moment the send was meant to go (scheduledAt, or
|
|
1250
|
+
* the recipient-local slot). A held send is re-queued with the delay that
|
|
1251
|
+
* remains until then.
|
|
1252
|
+
*/
|
|
1253
|
+
notBefore?: Date | null;
|
|
1171
1254
|
}
|
|
1172
1255
|
interface SuppressionDoc {
|
|
1173
1256
|
_id?: ObjectId;
|
|
@@ -1197,6 +1280,43 @@ interface BroadcastDoc {
|
|
|
1197
1280
|
recipientCount: number | null;
|
|
1198
1281
|
/** When true, dispatch fires each recipient at their local-timezone equivalent of `scheduledAt`. */
|
|
1199
1282
|
respectRecipientTimezone?: boolean;
|
|
1283
|
+
/**
|
|
1284
|
+
* Native waves: the most send rows this broadcast may have. Dispatch stops
|
|
1285
|
+
* enqueueing when the broadcast's send rows (every status, earlier waves
|
|
1286
|
+
* included) reach it, and parks the broadcast in `paused` with
|
|
1287
|
+
* `pauseReason.code === 'cap_reached'` if eligible recipients remain.
|
|
1288
|
+
* Raising it and resuming sends the next slice. Null or absent: no cap.
|
|
1289
|
+
*/
|
|
1290
|
+
recipientCap?: number | null;
|
|
1291
|
+
/** Order recipients are taken in (host-side sort). Null or absent: the adapter's own order. */
|
|
1292
|
+
order?: BroadcastOrder | null;
|
|
1293
|
+
/** Set while `status === 'paused'`. */
|
|
1294
|
+
pausedAt?: Date | null;
|
|
1295
|
+
pauseReason?: BroadcastPauseReason | null;
|
|
1296
|
+
/** Lease held by the one dispatcher allowed to enqueue for this broadcast. */
|
|
1297
|
+
dispatchLeaseId?: string | null;
|
|
1298
|
+
/** Bumped on every (re)start of dispatch; part of the dispatch job id. */
|
|
1299
|
+
dispatchGeneration?: number;
|
|
1300
|
+
/** Why dispatch set status 'failed'. */
|
|
1301
|
+
failureReason?: string | null;
|
|
1302
|
+
/** Per-broadcast overrides of `MailerConfig.broadcastStopRules`. */
|
|
1303
|
+
stopRules?: Partial<{
|
|
1304
|
+
enabled: boolean;
|
|
1305
|
+
hardBounceRatePct: number;
|
|
1306
|
+
complaintRatePct: number;
|
|
1307
|
+
unsubscribeRatePct: number;
|
|
1308
|
+
minSample: number;
|
|
1309
|
+
}> | null;
|
|
1310
|
+
/**
|
|
1311
|
+
* First stop-rule breach seen after the broadcast had nothing left to
|
|
1312
|
+
* hold (status 'sent', no queued sends) — recorded, since there was
|
|
1313
|
+
* nothing to pause.
|
|
1314
|
+
*/
|
|
1315
|
+
stopRuleBreach?: {
|
|
1316
|
+
at: Date;
|
|
1317
|
+
sample: number;
|
|
1318
|
+
breaches: StopRuleBreach[];
|
|
1319
|
+
} | null;
|
|
1200
1320
|
stats: {
|
|
1201
1321
|
sent: number;
|
|
1202
1322
|
delivered: number;
|
|
@@ -1210,6 +1330,29 @@ interface BroadcastDoc {
|
|
|
1210
1330
|
createdBy: string;
|
|
1211
1331
|
updatedAt: Date;
|
|
1212
1332
|
}
|
|
1333
|
+
interface BroadcastOrder {
|
|
1334
|
+
/** A host field, e.g. `updatedAt` for "most recently active first" with `direction: 'desc'`. */
|
|
1335
|
+
field: string;
|
|
1336
|
+
direction: 'asc' | 'desc';
|
|
1337
|
+
}
|
|
1338
|
+
interface StopRuleBreach {
|
|
1339
|
+
rule: 'hardBounceRatePct' | 'complaintRatePct' | 'unsubscribeRatePct';
|
|
1340
|
+
count: number;
|
|
1341
|
+
ratePct: number;
|
|
1342
|
+
thresholdPct: number;
|
|
1343
|
+
}
|
|
1344
|
+
interface BroadcastPauseReason {
|
|
1345
|
+
/**
|
|
1346
|
+
* cap_reached — the wave's recipientCap is spent; raise it and resume.
|
|
1347
|
+
* stop_rule — a per-broadcast bounce/complaint/unsubscribe threshold was crossed.
|
|
1348
|
+
* circuit_breaker — the sender domain's marketing circuit breaker tripped.
|
|
1349
|
+
* manual — paused by an operator.
|
|
1350
|
+
*/
|
|
1351
|
+
code: 'cap_reached' | 'stop_rule' | 'circuit_breaker' | 'manual';
|
|
1352
|
+
message: string;
|
|
1353
|
+
at: Date;
|
|
1354
|
+
details?: Record<string, unknown>;
|
|
1355
|
+
}
|
|
1213
1356
|
interface OutboxDoc {
|
|
1214
1357
|
_id?: ObjectId;
|
|
1215
1358
|
payload: {
|
|
@@ -1713,4 +1856,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1713
1856
|
reset(): void;
|
|
1714
1857
|
}
|
|
1715
1858
|
|
|
1716
|
-
export {
|
|
1859
|
+
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
|
|
|
@@ -678,6 +713,25 @@ interface CircuitBreakerThresholds {
|
|
|
678
713
|
windowMinutes: number;
|
|
679
714
|
minSendsBeforeEval: number;
|
|
680
715
|
}
|
|
716
|
+
/**
|
|
717
|
+
* Automatic stop rules for one broadcast. A breach pauses the broadcast
|
|
718
|
+
* (`pauseReason.code: 'stop_rule'`): no more enqueues, and its queued sends
|
|
719
|
+
* are held until an explicit resume. Rates are measured over the
|
|
720
|
+
* broadcast's sends with a known outcome (delivered or bounced) and only
|
|
721
|
+
* once `minSample` of them are in. A breach is `rate > threshold`.
|
|
722
|
+
*/
|
|
723
|
+
interface BroadcastStopRules {
|
|
724
|
+
/** Default true. */
|
|
725
|
+
enabled: boolean;
|
|
726
|
+
/** Hard bounces, % of outcomes. Default 2. */
|
|
727
|
+
hardBounceRatePct: number;
|
|
728
|
+
/** Complaints (spam reports), % of outcomes. Default 0.1. */
|
|
729
|
+
complaintRatePct: number;
|
|
730
|
+
/** Unsubscribes attributed to the broadcast, % of outcomes. Default 1. */
|
|
731
|
+
unsubscribeRatePct: number;
|
|
732
|
+
/** Outcomes required before any rule is evaluated. Default 100. */
|
|
733
|
+
minSample: number;
|
|
734
|
+
}
|
|
681
735
|
interface BotFilterConfig {
|
|
682
736
|
/**
|
|
683
737
|
* User agents matching this pattern are treated as automated. Applied to
|
|
@@ -840,6 +894,12 @@ interface MailerConfig {
|
|
|
840
894
|
broadcastConfirmationThreshold?: number;
|
|
841
895
|
broadcastEnqueueBatchSize?: number;
|
|
842
896
|
broadcastEnqueueMaxWaiting?: number;
|
|
897
|
+
/**
|
|
898
|
+
* Defaults for every broadcast's automatic stop rules; a broadcast can
|
|
899
|
+
* override any of them (`stopRules` on create/patch/resume). See
|
|
900
|
+
* `BroadcastStopRules`.
|
|
901
|
+
*/
|
|
902
|
+
broadcastStopRules?: Partial<BroadcastStopRules>;
|
|
843
903
|
workerless?: boolean;
|
|
844
904
|
tickIntervalSeconds?: number;
|
|
845
905
|
sendConcurrency?: number;
|
|
@@ -890,10 +950,27 @@ interface MailerConfig {
|
|
|
890
950
|
send: any;
|
|
891
951
|
error: Error;
|
|
892
952
|
}) => Promise<void> | void;
|
|
953
|
+
/**
|
|
954
|
+
* Called when a broadcast is paused by a stop rule, the circuit breaker, a
|
|
955
|
+
* reached cap, or an operator. Alerting belongs here: a stop-rule pause
|
|
956
|
+
* waits for a human.
|
|
957
|
+
*/
|
|
958
|
+
onBroadcastPaused?: (info: {
|
|
959
|
+
broadcastId: string;
|
|
960
|
+
slug: string;
|
|
961
|
+
reason: {
|
|
962
|
+
code: string;
|
|
963
|
+
message: string;
|
|
964
|
+
at: Date;
|
|
965
|
+
details?: Record<string, unknown>;
|
|
966
|
+
};
|
|
967
|
+
heldSends: number;
|
|
968
|
+
}) => Promise<void> | void;
|
|
893
969
|
handlebarsHelpers?: Record<string, Handlebars.HelperDelegate>;
|
|
894
970
|
}
|
|
895
971
|
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
972
|
circuitBreaker: CircuitBreakerThresholds;
|
|
973
|
+
broadcastStopRules: BroadcastStopRules;
|
|
897
974
|
} & MailerConfig;
|
|
898
975
|
|
|
899
976
|
/**
|
|
@@ -1168,6 +1245,12 @@ interface SendDoc {
|
|
|
1168
1245
|
updatedAt: Date;
|
|
1169
1246
|
sentAt: Date | null;
|
|
1170
1247
|
deliveredAt: Date | null;
|
|
1248
|
+
/**
|
|
1249
|
+
* Broadcast sends: the moment the send was meant to go (scheduledAt, or
|
|
1250
|
+
* the recipient-local slot). A held send is re-queued with the delay that
|
|
1251
|
+
* remains until then.
|
|
1252
|
+
*/
|
|
1253
|
+
notBefore?: Date | null;
|
|
1171
1254
|
}
|
|
1172
1255
|
interface SuppressionDoc {
|
|
1173
1256
|
_id?: ObjectId;
|
|
@@ -1197,6 +1280,43 @@ interface BroadcastDoc {
|
|
|
1197
1280
|
recipientCount: number | null;
|
|
1198
1281
|
/** When true, dispatch fires each recipient at their local-timezone equivalent of `scheduledAt`. */
|
|
1199
1282
|
respectRecipientTimezone?: boolean;
|
|
1283
|
+
/**
|
|
1284
|
+
* Native waves: the most send rows this broadcast may have. Dispatch stops
|
|
1285
|
+
* enqueueing when the broadcast's send rows (every status, earlier waves
|
|
1286
|
+
* included) reach it, and parks the broadcast in `paused` with
|
|
1287
|
+
* `pauseReason.code === 'cap_reached'` if eligible recipients remain.
|
|
1288
|
+
* Raising it and resuming sends the next slice. Null or absent: no cap.
|
|
1289
|
+
*/
|
|
1290
|
+
recipientCap?: number | null;
|
|
1291
|
+
/** Order recipients are taken in (host-side sort). Null or absent: the adapter's own order. */
|
|
1292
|
+
order?: BroadcastOrder | null;
|
|
1293
|
+
/** Set while `status === 'paused'`. */
|
|
1294
|
+
pausedAt?: Date | null;
|
|
1295
|
+
pauseReason?: BroadcastPauseReason | null;
|
|
1296
|
+
/** Lease held by the one dispatcher allowed to enqueue for this broadcast. */
|
|
1297
|
+
dispatchLeaseId?: string | null;
|
|
1298
|
+
/** Bumped on every (re)start of dispatch; part of the dispatch job id. */
|
|
1299
|
+
dispatchGeneration?: number;
|
|
1300
|
+
/** Why dispatch set status 'failed'. */
|
|
1301
|
+
failureReason?: string | null;
|
|
1302
|
+
/** Per-broadcast overrides of `MailerConfig.broadcastStopRules`. */
|
|
1303
|
+
stopRules?: Partial<{
|
|
1304
|
+
enabled: boolean;
|
|
1305
|
+
hardBounceRatePct: number;
|
|
1306
|
+
complaintRatePct: number;
|
|
1307
|
+
unsubscribeRatePct: number;
|
|
1308
|
+
minSample: number;
|
|
1309
|
+
}> | null;
|
|
1310
|
+
/**
|
|
1311
|
+
* First stop-rule breach seen after the broadcast had nothing left to
|
|
1312
|
+
* hold (status 'sent', no queued sends) — recorded, since there was
|
|
1313
|
+
* nothing to pause.
|
|
1314
|
+
*/
|
|
1315
|
+
stopRuleBreach?: {
|
|
1316
|
+
at: Date;
|
|
1317
|
+
sample: number;
|
|
1318
|
+
breaches: StopRuleBreach[];
|
|
1319
|
+
} | null;
|
|
1200
1320
|
stats: {
|
|
1201
1321
|
sent: number;
|
|
1202
1322
|
delivered: number;
|
|
@@ -1210,6 +1330,29 @@ interface BroadcastDoc {
|
|
|
1210
1330
|
createdBy: string;
|
|
1211
1331
|
updatedAt: Date;
|
|
1212
1332
|
}
|
|
1333
|
+
interface BroadcastOrder {
|
|
1334
|
+
/** A host field, e.g. `updatedAt` for "most recently active first" with `direction: 'desc'`. */
|
|
1335
|
+
field: string;
|
|
1336
|
+
direction: 'asc' | 'desc';
|
|
1337
|
+
}
|
|
1338
|
+
interface StopRuleBreach {
|
|
1339
|
+
rule: 'hardBounceRatePct' | 'complaintRatePct' | 'unsubscribeRatePct';
|
|
1340
|
+
count: number;
|
|
1341
|
+
ratePct: number;
|
|
1342
|
+
thresholdPct: number;
|
|
1343
|
+
}
|
|
1344
|
+
interface BroadcastPauseReason {
|
|
1345
|
+
/**
|
|
1346
|
+
* cap_reached — the wave's recipientCap is spent; raise it and resume.
|
|
1347
|
+
* stop_rule — a per-broadcast bounce/complaint/unsubscribe threshold was crossed.
|
|
1348
|
+
* circuit_breaker — the sender domain's marketing circuit breaker tripped.
|
|
1349
|
+
* manual — paused by an operator.
|
|
1350
|
+
*/
|
|
1351
|
+
code: 'cap_reached' | 'stop_rule' | 'circuit_breaker' | 'manual';
|
|
1352
|
+
message: string;
|
|
1353
|
+
at: Date;
|
|
1354
|
+
details?: Record<string, unknown>;
|
|
1355
|
+
}
|
|
1213
1356
|
interface OutboxDoc {
|
|
1214
1357
|
_id?: ObjectId;
|
|
1215
1358
|
payload: {
|
|
@@ -1713,4 +1856,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1713
1856
|
reset(): void;
|
|
1714
1857
|
}
|
|
1715
1858
|
|
|
1716
|
-
export {
|
|
1859
|
+
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 };
|