mailery 0.19.1 → 0.20.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 +54 -26
- 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 +54 -26
- package/dist/index.js.map +1 -1
- package/dist/{null-DxTHQSrJ.d.cts → null-B3N6bTpc.d.cts} +33 -5
- package/dist/{null-DxTHQSrJ.d.ts → null-B3N6bTpc.d.ts} +33 -5
- package/dist/testing.cjs +52 -24
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +52 -24
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Db, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
1
|
+
import { Db, MongoClientOptions, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import Handlebars from 'handlebars';
|
|
4
4
|
import IORedis from 'ioredis';
|
|
@@ -765,10 +765,31 @@ interface BotFilterConfig {
|
|
|
765
765
|
*/
|
|
766
766
|
minOpenDelayMs?: number;
|
|
767
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* A MongoDB connection mailery opens itself, on its own driver: `Mailer.init`
|
|
770
|
+
* connects, and `mailer.stop()` closes it. For hosts whose code is on another
|
|
771
|
+
* `mongodb` major (every Mongoose 8 app bundles driver 6), where sharing a
|
|
772
|
+
* `Db` is not possible.
|
|
773
|
+
*/
|
|
774
|
+
interface MailerMongoConnection {
|
|
775
|
+
uri: string;
|
|
776
|
+
/** Database name. Defaults to the one named in `uri`. */
|
|
777
|
+
dbName?: string;
|
|
778
|
+
clientOptions?: MongoClientOptions;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Builds the contact adapter on the database mailery connected to. Use it with
|
|
782
|
+
* `mongo`, when the host has no `Db` of its own to hand the adapter:
|
|
783
|
+
* `adapter: (db) => new MongoContactAdapter({ db, collection: 'users' })`.
|
|
784
|
+
*/
|
|
785
|
+
type ContactAdapterFactory = (db: Db) => ContactAdapter;
|
|
768
786
|
interface MailerConfig {
|
|
769
|
-
db
|
|
787
|
+
/** An open database on the host's own client. Give exactly one of `db` or `mongo`. */
|
|
788
|
+
db?: Db;
|
|
789
|
+
/** A connection mailery opens and owns. Give exactly one of `db` or `mongo`. */
|
|
790
|
+
mongo?: MailerMongoConnection;
|
|
770
791
|
collectionPrefix?: string;
|
|
771
|
-
adapter: ContactAdapter;
|
|
792
|
+
adapter: ContactAdapter | ContactAdapterFactory;
|
|
772
793
|
/**
|
|
773
794
|
* Optional host-provided template variables. `resolve(contact)` runs at
|
|
774
795
|
* send/preview/test render time; its result is merged into the template
|
|
@@ -980,7 +1001,12 @@ interface MailerConfig {
|
|
|
980
1001
|
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'>> & {
|
|
981
1002
|
circuitBreaker: CircuitBreakerThresholds;
|
|
982
1003
|
broadcastStopRules: BroadcastStopRules;
|
|
983
|
-
} &
|
|
1004
|
+
} & StorageResolvedConfig;
|
|
1005
|
+
/** `MailerConfig` once `Mailer.init` has opened storage: one `db`, one built adapter. */
|
|
1006
|
+
type StorageResolvedConfig = Omit<MailerConfig, 'db' | 'mongo' | 'adapter'> & {
|
|
1007
|
+
db: Db;
|
|
1008
|
+
adapter: ContactAdapter;
|
|
1009
|
+
};
|
|
984
1010
|
|
|
985
1011
|
/**
|
|
986
1012
|
* Mongo collection helpers + indexes for every mailer-owned collection.
|
|
@@ -1717,6 +1743,8 @@ declare class Mailer {
|
|
|
1717
1743
|
readonly config: ResolvedConfig;
|
|
1718
1744
|
readonly events: EventRegistry;
|
|
1719
1745
|
private queueDriver;
|
|
1746
|
+
/** The client `Mailer.init` opened from `config.mongo`; closed by `stop()`. Null when the host gave `db`. */
|
|
1747
|
+
private ownedClient;
|
|
1720
1748
|
private workersStarted;
|
|
1721
1749
|
private runnerContext;
|
|
1722
1750
|
private constructor();
|
|
@@ -1869,4 +1897,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1869
1897
|
reset(): void;
|
|
1870
1898
|
}
|
|
1871
1899
|
|
|
1872
|
-
export { type
|
|
1900
|
+
export { type TemplateVersionDoc 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 RedisOptions as G, type HealthDoc as H, type SegmentDefinition as I, type SendDoc as J, type SendStatus as K, type LeadDoc as L, type MailProvider as M, type NormalizedEvent as N, type OutboxDoc as O, type Predicate as P, type SenderDomainConfig as Q, type RunnerContext as R, type SendArgs as S, type TemplateDoc as T, type SenderDomainRegistry as U, type SenderDomainValidation as V, type SubscriptionDoc as W, type SubscriptionStatus as X, type SuppressionDoc as Y, type SuppressionReason as Z, type TemplateKind as _, type Contact as a, type VarsAdapter as a0, type VarsResolveInfo as a1, type WebhookEventDoc as a2, defineVars as a3, ensureIndexes as a4, getCollections as a5, validateSenderDomain as a6, varsJsonSchema as a7, type FlowTrigger as a8, type QueueDriverConfig as a9, 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 ContactAdapterFactory as p, type ContactTagDoc as q, type FlowGoal as r, type FlowRunDoc as s, type FlowRunStatus as t, type FlowVersionDoc as u, type HealthStatus as v, type MailerConfig as w, type MailerMongoConnection as x, NullProvider as y, RESERVED_VAR_KEYS as z };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Db, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
1
|
+
import { Db, MongoClientOptions, ObjectId, Collection, ClientSession } from 'mongodb';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import Handlebars from 'handlebars';
|
|
4
4
|
import IORedis from 'ioredis';
|
|
@@ -765,10 +765,31 @@ interface BotFilterConfig {
|
|
|
765
765
|
*/
|
|
766
766
|
minOpenDelayMs?: number;
|
|
767
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* A MongoDB connection mailery opens itself, on its own driver: `Mailer.init`
|
|
770
|
+
* connects, and `mailer.stop()` closes it. For hosts whose code is on another
|
|
771
|
+
* `mongodb` major (every Mongoose 8 app bundles driver 6), where sharing a
|
|
772
|
+
* `Db` is not possible.
|
|
773
|
+
*/
|
|
774
|
+
interface MailerMongoConnection {
|
|
775
|
+
uri: string;
|
|
776
|
+
/** Database name. Defaults to the one named in `uri`. */
|
|
777
|
+
dbName?: string;
|
|
778
|
+
clientOptions?: MongoClientOptions;
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Builds the contact adapter on the database mailery connected to. Use it with
|
|
782
|
+
* `mongo`, when the host has no `Db` of its own to hand the adapter:
|
|
783
|
+
* `adapter: (db) => new MongoContactAdapter({ db, collection: 'users' })`.
|
|
784
|
+
*/
|
|
785
|
+
type ContactAdapterFactory = (db: Db) => ContactAdapter;
|
|
768
786
|
interface MailerConfig {
|
|
769
|
-
db
|
|
787
|
+
/** An open database on the host's own client. Give exactly one of `db` or `mongo`. */
|
|
788
|
+
db?: Db;
|
|
789
|
+
/** A connection mailery opens and owns. Give exactly one of `db` or `mongo`. */
|
|
790
|
+
mongo?: MailerMongoConnection;
|
|
770
791
|
collectionPrefix?: string;
|
|
771
|
-
adapter: ContactAdapter;
|
|
792
|
+
adapter: ContactAdapter | ContactAdapterFactory;
|
|
772
793
|
/**
|
|
773
794
|
* Optional host-provided template variables. `resolve(contact)` runs at
|
|
774
795
|
* send/preview/test render time; its result is merged into the template
|
|
@@ -980,7 +1001,12 @@ interface MailerConfig {
|
|
|
980
1001
|
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'>> & {
|
|
981
1002
|
circuitBreaker: CircuitBreakerThresholds;
|
|
982
1003
|
broadcastStopRules: BroadcastStopRules;
|
|
983
|
-
} &
|
|
1004
|
+
} & StorageResolvedConfig;
|
|
1005
|
+
/** `MailerConfig` once `Mailer.init` has opened storage: one `db`, one built adapter. */
|
|
1006
|
+
type StorageResolvedConfig = Omit<MailerConfig, 'db' | 'mongo' | 'adapter'> & {
|
|
1007
|
+
db: Db;
|
|
1008
|
+
adapter: ContactAdapter;
|
|
1009
|
+
};
|
|
984
1010
|
|
|
985
1011
|
/**
|
|
986
1012
|
* Mongo collection helpers + indexes for every mailer-owned collection.
|
|
@@ -1717,6 +1743,8 @@ declare class Mailer {
|
|
|
1717
1743
|
readonly config: ResolvedConfig;
|
|
1718
1744
|
readonly events: EventRegistry;
|
|
1719
1745
|
private queueDriver;
|
|
1746
|
+
/** The client `Mailer.init` opened from `config.mongo`; closed by `stop()`. Null when the host gave `db`. */
|
|
1747
|
+
private ownedClient;
|
|
1720
1748
|
private workersStarted;
|
|
1721
1749
|
private runnerContext;
|
|
1722
1750
|
private constructor();
|
|
@@ -1869,4 +1897,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1869
1897
|
reset(): void;
|
|
1870
1898
|
}
|
|
1871
1899
|
|
|
1872
|
-
export { type
|
|
1900
|
+
export { type TemplateVersionDoc 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 RedisOptions as G, type HealthDoc as H, type SegmentDefinition as I, type SendDoc as J, type SendStatus as K, type LeadDoc as L, type MailProvider as M, type NormalizedEvent as N, type OutboxDoc as O, type Predicate as P, type SenderDomainConfig as Q, type RunnerContext as R, type SendArgs as S, type TemplateDoc as T, type SenderDomainRegistry as U, type SenderDomainValidation as V, type SubscriptionDoc as W, type SubscriptionStatus as X, type SuppressionDoc as Y, type SuppressionReason as Z, type TemplateKind as _, type Contact as a, type VarsAdapter as a0, type VarsResolveInfo as a1, type WebhookEventDoc as a2, defineVars as a3, ensureIndexes as a4, getCollections as a5, validateSenderDomain as a6, varsJsonSchema as a7, type FlowTrigger as a8, type QueueDriverConfig as a9, 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 ContactAdapterFactory as p, type ContactTagDoc as q, type FlowGoal as r, type FlowRunDoc as s, type FlowRunStatus as t, type FlowVersionDoc as u, type HealthStatus as v, type MailerConfig as w, type MailerMongoConnection as x, NullProvider as y, RESERVED_VAR_KEYS as z };
|
package/dist/testing.cjs
CHANGED
|
@@ -16140,7 +16140,7 @@ function derivePlaintext(html) {
|
|
|
16140
16140
|
var PLAIN_TEXT = { noEscape: true };
|
|
16141
16141
|
async function renderTemplate(template, ctx, opts = {}) {
|
|
16142
16142
|
const hb = makeHandlebars(opts.helpers);
|
|
16143
|
-
const subject = hb.compile(template.subject)(ctx);
|
|
16143
|
+
const subject = hb.compile(template.subject, PLAIN_TEXT)(ctx).replace(/[\r\n]+/g, " ");
|
|
16144
16144
|
const preheader = hb.compile(template.preheader)(ctx);
|
|
16145
16145
|
let html;
|
|
16146
16146
|
if (template.body.html) {
|
|
@@ -18479,6 +18479,7 @@ async function processScheduledBroadcasts2(ctx) {
|
|
|
18479
18479
|
}
|
|
18480
18480
|
|
|
18481
18481
|
// src/server/mailer.ts
|
|
18482
|
+
var STORAGE_REQUIRED = "Mailer.init: give exactly one of `db` (an open Db) or `mongo` (a connection mailery opens)";
|
|
18482
18483
|
var Mailer = class _Mailer {
|
|
18483
18484
|
db;
|
|
18484
18485
|
collections;
|
|
@@ -18488,6 +18489,8 @@ var Mailer = class _Mailer {
|
|
|
18488
18489
|
config;
|
|
18489
18490
|
events;
|
|
18490
18491
|
queueDriver;
|
|
18492
|
+
/** The client `Mailer.init` opened from `config.mongo`; closed by `stop()`. Null when the host gave `db`. */
|
|
18493
|
+
ownedClient;
|
|
18491
18494
|
workersStarted = false;
|
|
18492
18495
|
runnerContext;
|
|
18493
18496
|
constructor(args) {
|
|
@@ -18497,6 +18500,7 @@ var Mailer = class _Mailer {
|
|
|
18497
18500
|
this.adapter = args.adapter;
|
|
18498
18501
|
this.providers = args.providers;
|
|
18499
18502
|
this.queueDriver = args.queueDriver;
|
|
18503
|
+
this.ownedClient = args.ownedClient;
|
|
18500
18504
|
this.queues = args.queueDriver.queues;
|
|
18501
18505
|
this.events = args.events;
|
|
18502
18506
|
this.runnerContext = {
|
|
@@ -18593,36 +18597,58 @@ var Mailer = class _Mailer {
|
|
|
18593
18597
|
});
|
|
18594
18598
|
}
|
|
18595
18599
|
static async init(input) {
|
|
18596
|
-
|
|
18597
|
-
if (!resolveProvider(
|
|
18600
|
+
if (input.db == null === (input.mongo == null)) throw new Error(STORAGE_REQUIRED);
|
|
18601
|
+
if (!resolveProvider(input.providers, input.defaultProvider)) {
|
|
18598
18602
|
throw new Error(
|
|
18599
|
-
`defaultProvider "${
|
|
18603
|
+
`defaultProvider "${input.defaultProvider}" is not a registered provider. Registered: ${registeredProviderNames(input.providers).join(", ") || "(none)"}`
|
|
18600
18604
|
);
|
|
18601
18605
|
}
|
|
18602
|
-
if (
|
|
18606
|
+
if (input.defaultTransactionalProvider != null && !resolveProvider(input.providers, input.defaultTransactionalProvider)) {
|
|
18603
18607
|
throw new Error(
|
|
18604
|
-
`defaultTransactionalProvider "${
|
|
18608
|
+
`defaultTransactionalProvider "${input.defaultTransactionalProvider}" is not a registered provider. Registered: ${registeredProviderNames(input.providers).join(", ") || "(none)"}`
|
|
18605
18609
|
);
|
|
18606
18610
|
}
|
|
18607
|
-
if (
|
|
18611
|
+
if (input.varsAdapter) {
|
|
18608
18612
|
const { assertNoReservedVarKeys: assertNoReservedVarKeys2 } = await Promise.resolve().then(() => (init_vars(), vars_exports));
|
|
18609
|
-
assertNoReservedVarKeys2(
|
|
18610
|
-
}
|
|
18611
|
-
|
|
18612
|
-
|
|
18613
|
-
|
|
18614
|
-
|
|
18615
|
-
await
|
|
18616
|
-
|
|
18617
|
-
|
|
18618
|
-
|
|
18619
|
-
|
|
18620
|
-
|
|
18621
|
-
|
|
18622
|
-
|
|
18623
|
-
|
|
18624
|
-
|
|
18625
|
-
|
|
18613
|
+
assertNoReservedVarKeys2(input.varsAdapter);
|
|
18614
|
+
}
|
|
18615
|
+
let ownedClient = null;
|
|
18616
|
+
let db;
|
|
18617
|
+
if (input.mongo) {
|
|
18618
|
+
const { MongoClient: MongoClient2 } = await import('mongodb');
|
|
18619
|
+
ownedClient = await MongoClient2.connect(input.mongo.uri, input.mongo.clientOptions);
|
|
18620
|
+
db = ownedClient.db(input.mongo.dbName);
|
|
18621
|
+
} else if (input.db) {
|
|
18622
|
+
db = input.db;
|
|
18623
|
+
} else {
|
|
18624
|
+
throw new Error(STORAGE_REQUIRED);
|
|
18625
|
+
}
|
|
18626
|
+
try {
|
|
18627
|
+
const config = resolveConfig({
|
|
18628
|
+
...input,
|
|
18629
|
+
db,
|
|
18630
|
+
adapter: typeof input.adapter === "function" ? input.adapter(db) : input.adapter
|
|
18631
|
+
});
|
|
18632
|
+
const collections = getCollections(config.db, config.collectionPrefix);
|
|
18633
|
+
await ensureIndexes(config.db, config.collectionPrefix);
|
|
18634
|
+
const queueDriver = await createQueueDriver(config.queue, config.db);
|
|
18635
|
+
if (!config.workerless && config.queue.driver !== "noop") {
|
|
18636
|
+
await queueDriver.scheduleRepeatingTick(config.tickIntervalSeconds);
|
|
18637
|
+
}
|
|
18638
|
+
return new _Mailer({
|
|
18639
|
+
config,
|
|
18640
|
+
db: config.db,
|
|
18641
|
+
collections,
|
|
18642
|
+
adapter: config.adapter,
|
|
18643
|
+
providers: config.providers,
|
|
18644
|
+
queueDriver,
|
|
18645
|
+
events: new EventRegistry(),
|
|
18646
|
+
ownedClient
|
|
18647
|
+
});
|
|
18648
|
+
} catch (err) {
|
|
18649
|
+
await ownedClient?.close();
|
|
18650
|
+
throw err;
|
|
18651
|
+
}
|
|
18626
18652
|
}
|
|
18627
18653
|
// -------------------------------------------------------------------------
|
|
18628
18654
|
// Event registration + firing
|
|
@@ -19095,6 +19121,8 @@ var Mailer = class _Mailer {
|
|
|
19095
19121
|
async stop() {
|
|
19096
19122
|
await this.queueDriver.close();
|
|
19097
19123
|
this.workersStarted = false;
|
|
19124
|
+
await this.ownedClient?.close();
|
|
19125
|
+
this.ownedClient = null;
|
|
19098
19126
|
}
|
|
19099
19127
|
/** Used internally by the admin router and tests; not part of the public API. */
|
|
19100
19128
|
getRunnerContext() {
|