mailery 0.2.0 → 0.2.2

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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-CWw3Gpbl.cjs';
2
- export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SubscriptionDoc, x as SubscriptionStatus, y as SuppressionDoc, z as SuppressionReason, D as TemplateKind, G as TemplateVersionDoc, W as WebhookEventDoc, I as ensureIndexes, J as getCollections } from './null-CWw3Gpbl.cjs';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-DaisDvB_.cjs';
2
+ export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-DaisDvB_.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, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-CWw3Gpbl.js';
2
- export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SubscriptionDoc, x as SubscriptionStatus, y as SuppressionDoc, z as SuppressionReason, D as TemplateKind, G as TemplateVersionDoc, W as WebhookEventDoc, I as ensureIndexes, J as getCollections } from './null-CWw3Gpbl.js';
1
+ import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as Mailer, T as TemplateDoc, d as SuppressionScope, F as FlowStep, e as SegmentFilter, P as Predicate } from './null-DaisDvB_.js';
2
+ export { f as AuditLogDoc, B as BroadcastDoc, g as BroadcastStatus, h as CircuitBreakerThresholds, i as Collections, j as ContactTagDoc, E as EventDoc, k as FlowDoc, l as FlowGoal, m as FlowRunDoc, n as FlowRunStatus, o as FlowVersionDoc, H as HealthDoc, p as HealthStatus, L as LeadDoc, q as MailerConfig, r as NullProvider, O as OutboxDoc, s as RedisOptions, t as SegmentDefinition, u as SendDoc, v as SendStatus, w as SenderDomainConfig, x as SenderDomainRegistry, y as SenderDomainValidation, z as SubscriptionDoc, D as SubscriptionStatus, G as SuppressionDoc, I as SuppressionReason, J as TemplateKind, K as TemplateVersionDoc, W as WebhookEventDoc, Q as ensureIndexes, U as getCollections, V as validateSenderDomain } from './null-DaisDvB_.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
@@ -2188,6 +2188,25 @@ async function promoteSoftBounces(ctx) {
2188
2188
  // src/server/runner/tick.ts
2189
2189
  var STRANDED_SEND_THRESHOLD_MS = 5 * 60 * 1e3;
2190
2190
  async function runTick(ctx) {
2191
+ await ctx.collections.health.updateOne(
2192
+ { _id: "singleton" },
2193
+ {
2194
+ $set: { updatedAt: /* @__PURE__ */ new Date() },
2195
+ $setOnInsert: {
2196
+ _id: "singleton",
2197
+ windowStartedAt: /* @__PURE__ */ new Date(),
2198
+ windowDurationMs: ctx.config.circuitBreaker.windowMinutes * 60 * 1e3,
2199
+ status: "healthy",
2200
+ trippedAt: null,
2201
+ trippedReason: null,
2202
+ manuallyResumedAt: null,
2203
+ counters: { sent: 0, delivered: 0, bounced: 0, hardBounced: 0, softBounced: 0, complained: 0, failedToSend: 0 },
2204
+ rates: { bounceRate: 0, hardBounceRate: 0, complaintRate: 0, failureRate: 0 }
2205
+ }
2206
+ },
2207
+ { upsert: true }
2208
+ ).catch(() => {
2209
+ });
2191
2210
  await processNewlyFiredEventTriggers(ctx).catch((err) => {
2192
2211
  console.error("mailery: triggers scan failed", err);
2193
2212
  });
@@ -2910,6 +2929,43 @@ var Mailer = class _Mailer {
2910
2929
  }
2911
2930
  };
2912
2931
 
2932
+ // src/server/templates/sender-domain.ts
2933
+ function validateSenderDomain(fromEmail, templateKind, registry) {
2934
+ if (!registry || Object.keys(registry).length === 0) return { ok: true };
2935
+ const domain = extractDomain(fromEmail);
2936
+ if (!domain) {
2937
+ return {
2938
+ ok: false,
2939
+ code: "invalid_email",
2940
+ reason: `invalid fromEmail "${fromEmail}" \u2014 expected "name@domain"`
2941
+ };
2942
+ }
2943
+ const entry = registry[domain];
2944
+ if (!entry) {
2945
+ const known = Object.keys(registry).join(", ");
2946
+ return {
2947
+ ok: false,
2948
+ code: "unregistered_domain",
2949
+ reason: `sender domain "${domain}" is not declared in senderDomains (allowed: ${known})`
2950
+ };
2951
+ }
2952
+ if (entry.kind === "both") return { ok: true };
2953
+ if (entry.kind !== templateKind) {
2954
+ return {
2955
+ ok: false,
2956
+ code: "wrong_kind",
2957
+ reason: `sender domain "${domain}" is configured for ${entry.kind} email, but this template's kind is ${templateKind}`
2958
+ };
2959
+ }
2960
+ return { ok: true };
2961
+ }
2962
+ function extractDomain(email) {
2963
+ if (typeof email !== "string") return null;
2964
+ const at = email.lastIndexOf("@");
2965
+ if (at <= 0 || at === email.length - 1) return null;
2966
+ return email.slice(at + 1).toLowerCase().trim();
2967
+ }
2968
+
2913
2969
  // src/server/index.ts
2914
2970
  init_mongo();
2915
2971
 
@@ -2939,6 +2995,227 @@ var NullProvider = class {
2939
2995
 
2940
2996
  // src/server/index.ts
2941
2997
  init_sendgrid();
2998
+
2999
+ // src/server/api/setup-status.ts
3000
+ async function runSetupChecks(mailer) {
3001
+ const checks = [];
3002
+ checks.push(await checkMongo(mailer));
3003
+ checks.push(await checkQueue(mailer));
3004
+ if (mailer.config.queue.driver !== "noop" && !mailer.config.workerless) {
3005
+ checks.push(await checkWorkersHeartbeat(mailer));
3006
+ }
3007
+ checks.push(await checkCircuitBreaker(mailer));
3008
+ checks.push(...checkFromDefaultsAgainstRegistry(mailer));
3009
+ checks.push(...await checkPublishedTemplates(mailer));
3010
+ checks.push(await checkPostalAddress(mailer));
3011
+ checks.push(await checkDoiTemplate(mailer));
3012
+ const overall = checks.some((c) => c.severity === "error") ? "error" : checks.some((c) => c.severity === "warn") ? "warn" : "ok";
3013
+ return {
3014
+ overall,
3015
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3016
+ checks
3017
+ };
3018
+ }
3019
+ async function checkMongo(mailer) {
3020
+ try {
3021
+ await mailer.db.admin().ping();
3022
+ return { name: "mongo", label: "MongoDB connection", severity: "ok", message: "reachable" };
3023
+ } catch (err) {
3024
+ return {
3025
+ name: "mongo",
3026
+ label: "MongoDB connection",
3027
+ severity: "error",
3028
+ message: `MongoDB ping failed: ${err?.message ?? err}`,
3029
+ hint: "Mailery is configured against an unreachable Mongo. Sends, flow advancement, and admin reads will all fail."
3030
+ };
3031
+ }
3032
+ }
3033
+ async function checkQueue(mailer) {
3034
+ const driver = mailer.config.queue.driver;
3035
+ if (driver === "noop") {
3036
+ return {
3037
+ name: "queue",
3038
+ label: "Queue driver",
3039
+ severity: "ok",
3040
+ message: "driver: noop (synchronous-only mode)"
3041
+ };
3042
+ }
3043
+ try {
3044
+ await mailer.queues.send.getWaitingCount();
3045
+ return { name: "queue", label: "Queue driver", severity: "ok", message: `driver: ${driver}` };
3046
+ } catch (err) {
3047
+ return {
3048
+ name: "queue",
3049
+ label: "Queue driver",
3050
+ severity: "error",
3051
+ message: `${driver} queue is not responding: ${err?.message ?? err}`,
3052
+ hint: driver === "bull" ? "Check Redis connectivity (queue.redis.url)." : "Check the @hokify/agenda + Mongo connection."
3053
+ };
3054
+ }
3055
+ }
3056
+ async function checkWorkersHeartbeat(mailer) {
3057
+ const h = await mailer.collections.health.findOne({ _id: "singleton" });
3058
+ const tickIntervalMs = mailer.config.tickIntervalSeconds * 1e3;
3059
+ const staleAfterMs = Math.max(tickIntervalMs * 3, 3e4);
3060
+ if (!h) {
3061
+ return {
3062
+ name: "workers_heartbeat",
3063
+ label: "Background workers",
3064
+ severity: "warn",
3065
+ message: "no tick has run yet",
3066
+ hint: "If your separate worker process is started, the heartbeat will appear within one tick interval. If you forgot to run `mailer.startWorkers()`, sends will sit queued indefinitely."
3067
+ };
3068
+ }
3069
+ const ageMs = Date.now() - new Date(h.updatedAt).getTime();
3070
+ if (ageMs > staleAfterMs) {
3071
+ return {
3072
+ name: "workers_heartbeat",
3073
+ label: "Background workers",
3074
+ severity: "error",
3075
+ message: `last tick ${humanDuration(ageMs)} ago (expected within ${humanDuration(tickIntervalMs)})`,
3076
+ hint: "Workers appear to be down. Sends and flow advancement are halted. Restart your worker process (`mailer.startWorkers()`)."
3077
+ };
3078
+ }
3079
+ return {
3080
+ name: "workers_heartbeat",
3081
+ label: "Background workers",
3082
+ severity: "ok",
3083
+ message: `last tick ${humanDuration(ageMs)} ago`
3084
+ };
3085
+ }
3086
+ async function checkCircuitBreaker(mailer) {
3087
+ const h = await mailer.collections.health.findOne({ _id: "singleton" });
3088
+ if (!h || h.status === "healthy") {
3089
+ return { name: "circuit_breaker", label: "Circuit breaker", severity: "ok", message: "healthy" };
3090
+ }
3091
+ if (h.status === "degraded") {
3092
+ return {
3093
+ name: "circuit_breaker",
3094
+ label: "Circuit breaker",
3095
+ severity: "warn",
3096
+ message: "degraded (high failure rate)",
3097
+ hint: "Marketing sends still flow but failure rate is above the degraded threshold. Investigate provider errors before they escalate to tripped."
3098
+ };
3099
+ }
3100
+ return {
3101
+ name: "circuit_breaker",
3102
+ label: "Circuit breaker",
3103
+ severity: "error",
3104
+ message: `tripped: ${h.trippedReason ?? "unknown reason"}`,
3105
+ hint: "Marketing sends are held. Investigate the underlying bounce / complaint cause, then POST /api/health/resume."
3106
+ };
3107
+ }
3108
+ function checkFromDefaultsAgainstRegistry(mailer) {
3109
+ const registry = mailer.config.senderDomains;
3110
+ if (!registry || Object.keys(registry).length === 0) return [];
3111
+ const out = [];
3112
+ const from = mailer.config.fromDefaults?.email;
3113
+ const tx = mailer.config.transactionalFromDefaults?.email;
3114
+ if (from) {
3115
+ const r = validateSenderDomain(from, "marketing", registry);
3116
+ if (!r.ok) {
3117
+ out.push({
3118
+ name: "from_defaults_marketing",
3119
+ label: "fromDefaults vs senderDomains",
3120
+ severity: "error",
3121
+ message: r.reason,
3122
+ hint: "New marketing templates that fall back to fromDefaults will fail to publish."
3123
+ });
3124
+ }
3125
+ }
3126
+ if (tx) {
3127
+ const r = validateSenderDomain(tx, "transactional", registry);
3128
+ if (!r.ok) {
3129
+ out.push({
3130
+ name: "transactional_from_defaults",
3131
+ label: "transactionalFromDefaults vs senderDomains",
3132
+ severity: "error",
3133
+ message: r.reason,
3134
+ hint: "New transactional templates that fall back to transactionalFromDefaults will fail to publish."
3135
+ });
3136
+ }
3137
+ } else if (from) {
3138
+ const r = validateSenderDomain(from, "transactional", registry);
3139
+ if (!r.ok) {
3140
+ out.push({
3141
+ name: "transactional_fallback",
3142
+ label: "Transactional fallback",
3143
+ severity: "warn",
3144
+ message: `transactionalFromDefaults is unset and fromDefaults (${from}) is invalid for transactional templates`,
3145
+ hint: 'Set transactionalFromDefaults to a transactional-kind domain, or set senderDomains entry for the existing one to "both".'
3146
+ });
3147
+ }
3148
+ }
3149
+ return out;
3150
+ }
3151
+ async function checkPublishedTemplates(mailer) {
3152
+ const registry = mailer.config.senderDomains;
3153
+ if (!registry || Object.keys(registry).length === 0) return [];
3154
+ const published = await mailer.collections.templates.find({ publishedAt: { $ne: null } }, { projection: { slug: 1, kind: 1, fromEmail: 1 } }).toArray();
3155
+ const broken = [];
3156
+ for (const tpl of published) {
3157
+ const r = validateSenderDomain(tpl.fromEmail, tpl.kind, registry);
3158
+ if (!r.ok) broken.push({ slug: tpl.slug, reason: r.reason });
3159
+ }
3160
+ if (broken.length === 0) return [];
3161
+ const list = broken.slice(0, 5).map((b) => `${b.slug} (${b.reason})`).join("; ");
3162
+ const more = broken.length > 5 ? ` \u2026and ${broken.length - 5} more` : "";
3163
+ return [
3164
+ {
3165
+ name: "published_template_domains",
3166
+ label: "Published templates",
3167
+ severity: "error",
3168
+ message: `${broken.length} published template${broken.length === 1 ? "" : "s"} use a fromEmail that no longer matches senderDomains: ${list}${more}`,
3169
+ hint: "These templates will still send with their stored fromEmail until re-published. Edit each template and republish to surface the validation, or update senderDomains."
3170
+ }
3171
+ ];
3172
+ }
3173
+ async function checkPostalAddress(mailer) {
3174
+ if (mailer.config.senderAddress) {
3175
+ return { name: "postal_address", label: "CAN-SPAM postal address", severity: "ok", message: "set" };
3176
+ }
3177
+ const marketingCount = await mailer.collections.templates.countDocuments({
3178
+ kind: "marketing",
3179
+ publishedAt: { $ne: null }
3180
+ });
3181
+ if (marketingCount === 0) {
3182
+ return { name: "postal_address", label: "CAN-SPAM postal address", severity: "ok", message: "no published marketing templates yet" };
3183
+ }
3184
+ return {
3185
+ name: "postal_address",
3186
+ label: "CAN-SPAM postal address",
3187
+ severity: "warn",
3188
+ message: `${marketingCount} published marketing template${marketingCount === 1 ? "" : "s"} but senderAddress is unset`,
3189
+ hint: "CAN-SPAM requires a postal address in marketing emails. Set `senderAddress` in your Mailer config and reference it via `{{senderAddress}}` in your templates."
3190
+ };
3191
+ }
3192
+ async function checkDoiTemplate(mailer) {
3193
+ if (!mailer.config.requireDoubleOptIn) {
3194
+ return { name: "doi_template", label: "DOI template", severity: "ok", message: "DOI not required" };
3195
+ }
3196
+ const tpl = await mailer.collections.templates.findOne({
3197
+ slug: mailer.config.doiTemplateSlug,
3198
+ publishedAt: { $ne: null }
3199
+ });
3200
+ if (tpl) {
3201
+ return { name: "doi_template", label: "DOI template", severity: "ok", message: `template "${tpl.slug}" published` };
3202
+ }
3203
+ return {
3204
+ name: "doi_template",
3205
+ label: "DOI template",
3206
+ severity: "error",
3207
+ message: `requireDoubleOptIn is true but no published template with slug "${mailer.config.doiTemplateSlug}"`,
3208
+ hint: "New subscriptions will silently fail to send confirmation emails. Create and publish a template with this slug, or unset requireDoubleOptIn."
3209
+ };
3210
+ }
3211
+ function humanDuration(ms) {
3212
+ if (ms < 1e3) return `${ms}ms`;
3213
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
3214
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
3215
+ return `${Math.round(ms / 36e5)}h`;
3216
+ }
3217
+
3218
+ // src/server/api/admin.ts
2942
3219
  var __filename$1 = fileURLToPath(import.meta.url);
2943
3220
  var __dirname$1 = path.dirname(__filename$1);
2944
3221
  function defaultSpaDir() {
@@ -3160,6 +3437,13 @@ function apiRouter(mailer) {
3160
3437
  res.json(rows);
3161
3438
  })
3162
3439
  );
3440
+ r.get(
3441
+ "/setup-status",
3442
+ asyncHandler(async (_req, res) => {
3443
+ const status = await runSetupChecks(mailer);
3444
+ res.json(status);
3445
+ })
3446
+ );
3163
3447
  r.get(
3164
3448
  "/health",
3165
3449
  asyncHandler(async (_req, res) => {
@@ -3333,6 +3617,15 @@ function apiRouter(mailer) {
3333
3617
  if (kind !== "marketing" && kind !== "transactional") {
3334
3618
  return res.status(400).json({ error: "validation_failed", message: "kind must be marketing or transactional" });
3335
3619
  }
3620
+ const resolvedFromEmail = fromEmail ?? (kind === "transactional" ? mailer.config.transactionalFromDefaults?.email : void 0) ?? mailer.config.fromDefaults?.email ?? "noreply@example.com";
3621
+ const senderCheck = validateSenderDomain(resolvedFromEmail, kind, mailer.config.senderDomains);
3622
+ if (!senderCheck.ok) {
3623
+ return res.status(400).json({
3624
+ error: "sender_domain_invalid",
3625
+ code: senderCheck.code,
3626
+ message: senderCheck.reason
3627
+ });
3628
+ }
3336
3629
  const now = /* @__PURE__ */ new Date();
3337
3630
  try {
3338
3631
  await c.templates.insertOne({
@@ -3340,8 +3633,8 @@ function apiRouter(mailer) {
3340
3633
  name,
3341
3634
  description: "",
3342
3635
  kind,
3343
- fromName: fromName ?? mailer.config.fromDefaults?.name ?? "Mailery",
3344
- fromEmail: fromEmail ?? mailer.config.fromDefaults?.email ?? "noreply@example.com",
3636
+ fromName: fromName ?? (kind === "transactional" ? mailer.config.transactionalFromDefaults?.name : void 0) ?? mailer.config.fromDefaults?.name ?? "Mailery",
3637
+ fromEmail: resolvedFromEmail,
3345
3638
  replyTo: null,
3346
3639
  providerOverride: null,
3347
3640
  subject: subject ?? `Untitled \u2014 ${name}`,
@@ -3399,6 +3692,18 @@ function apiRouter(mailer) {
3399
3692
  if (typeof fromEmail === "string") set.fromEmail = fromEmail;
3400
3693
  if (typeof replyTo === "string" || replyTo === null) set.replyTo = replyTo;
3401
3694
  if (kind === "marketing" || kind === "transactional") set.kind = kind;
3695
+ if (typeof fromEmail === "string" || kind === "marketing" || kind === "transactional") {
3696
+ const resultingKind = set.kind ?? tpl.kind;
3697
+ const resultingFromEmail = set.fromEmail ?? tpl.fromEmail;
3698
+ const senderCheck = validateSenderDomain(resultingFromEmail, resultingKind, mailer.config.senderDomains);
3699
+ if (!senderCheck.ok) {
3700
+ return res.status(400).json({
3701
+ error: "sender_domain_invalid",
3702
+ code: senderCheck.code,
3703
+ message: senderCheck.reason
3704
+ });
3705
+ }
3706
+ }
3402
3707
  if (typeof trackOpens === "boolean") set.trackOpens = trackOpens;
3403
3708
  if (typeof trackClicks === "boolean") set.trackClicks = trackClicks;
3404
3709
  await c.templates.updateOne({ _id: tpl._id }, { $set: set });
@@ -3417,6 +3722,14 @@ function apiRouter(mailer) {
3417
3722
  if (!tpl) return res.status(404).json({ error: "not_found" });
3418
3723
  const draft = tpl.draft;
3419
3724
  if (!draft) return res.status(400).json({ error: "no_draft" });
3725
+ const senderCheck = validateSenderDomain(tpl.fromEmail, tpl.kind, mailer.config.senderDomains);
3726
+ if (!senderCheck.ok) {
3727
+ return res.status(400).json({
3728
+ error: "sender_domain_invalid",
3729
+ code: senderCheck.code,
3730
+ message: senderCheck.reason
3731
+ });
3732
+ }
3420
3733
  let compiled;
3421
3734
  if (draft.editorJson) {
3422
3735
  compiled = await compileMailyTemplate(draft.editorJson);
@@ -4027,6 +4340,6 @@ var DEDUPE_POLICIES = [
4027
4340
  // src/server/index.ts
4028
4341
  var VERSION = "0.1.0";
4029
4342
 
4030
- export { DEDUPE_POLICIES, FLOW_STEP_KINDS, Mailer, MongoContactAdapter, NullProvider, PREDICATE_KINDS, SEGMENT_FILTER_KINDS, SendGridProvider, VERSION, applyTracking, applyWebhookEvent, compileMailyTemplate, compileTemplate, createAdminRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, derivePlaintext, dispatchSend, ensureIndexes, getCollections, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, renderTemplate, runTick, sha256Hex, signUnsubscribeToken, sweepStrandedFlowRuns, verifyUnsubscribeToken };
4343
+ export { DEDUPE_POLICIES, FLOW_STEP_KINDS, Mailer, MongoContactAdapter, NullProvider, PREDICATE_KINDS, SEGMENT_FILTER_KINDS, SendGridProvider, VERSION, applyTracking, applyWebhookEvent, compileMailyTemplate, compileTemplate, createAdminRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, derivePlaintext, dispatchSend, ensureIndexes, getCollections, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, renderTemplate, runTick, sha256Hex, signUnsubscribeToken, sweepStrandedFlowRuns, validateSenderDomain, verifyUnsubscribeToken };
4031
4344
  //# sourceMappingURL=index.js.map
4032
4345
  //# sourceMappingURL=index.js.map