mailery 0.15.0 → 0.16.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/README.md +1 -0
- package/dist/index.cjs +1370 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +417 -114
- package/dist/index.d.ts +417 -114
- package/dist/index.js +1356 -10
- package/dist/index.js.map +1 -1
- package/dist/{null-CnhsvKvy.d.cts → null-CDlseQxO.d.cts} +1 -1
- package/dist/{null-CnhsvKvy.d.ts → null-CDlseQxO.d.ts} +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RunnerContext, N as NormalizedEvent, C as ContactAdapter, a as Contact, A as AdapterFilter, M as MailProvider, S as SendArgs, b as SendResult, c as MailTesterFeedback, d as Mailer, T as TemplateDoc, e as
|
|
2
|
-
export {
|
|
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 MailTesterFeedback, d as Mailer, T as TemplateDoc, F as FlowStep, e as Collections, f as FlowDoc, g as SuppressionScope, D as DeliveryWindow, h as SegmentFilter, P as Predicate } from './null-CDlseQxO.cjs';
|
|
2
|
+
export { i as AuditLogDoc, B as BotFilterConfig, j as BroadcastDoc, k as BroadcastStatus, l as CircuitBreakerThresholds, m as ContactTagDoc, E as EventDoc, n as FlowGoal, o as FlowRunDoc, p as FlowRunStatus, q as FlowVersionDoc, H as HealthDoc, r as HealthStatus, L as LeadDoc, s as MailerConfig, t as NullProvider, O as OutboxDoc, u as RESERVED_VAR_KEYS, v as RedisOptions, w as SegmentDefinition, x as SendDoc, y as SendStatus, z as SenderDomainConfig, G as SenderDomainRegistry, I as SenderDomainValidation, J as SubscriptionDoc, K as SubscriptionStatus, Q as SuppressionDoc, U as SuppressionReason, V as TemplateKind, W as TemplateVersionDoc, X as VarsAdapter, Y as VarsResolveInfo, Z as WebhookEventDoc, _ as defineVars, $ as ensureIndexes, a0 as getCollections, a1 as validateSenderDomain, a2 as varsJsonSchema } from './null-CDlseQxO.cjs';
|
|
3
3
|
import { ObjectId, Db, Filter } from 'mongodb';
|
|
4
4
|
import { Request, Router } from 'express';
|
|
5
5
|
import Handlebars from 'handlebars';
|
|
@@ -271,6 +271,14 @@ interface AdminRouterOptions {
|
|
|
271
271
|
mailTesterClient?: MailTesterClient;
|
|
272
272
|
}
|
|
273
273
|
declare function createAdminRouter(mailer: Mailer, opts?: AdminRouterOptions): Router;
|
|
274
|
+
/**
|
|
275
|
+
* The JSON API alone, without the SPA shell or the static assets. Exported
|
|
276
|
+
* so `createAgentRouter` can offer the same endpoints under bearer-token
|
|
277
|
+
* auth; hosts mounting the SPA should keep using `createAdminRouter`.
|
|
278
|
+
*
|
|
279
|
+
* Expects `(req as any).actor` to be set by whatever sits in front of it.
|
|
280
|
+
*/
|
|
281
|
+
declare function createAdminApiRouter(mailer: Mailer, opts?: AdminRouterOptions): Router;
|
|
274
282
|
|
|
275
283
|
/**
|
|
276
284
|
* Async handler wrapper for the *public* router.
|
|
@@ -318,6 +326,412 @@ interface RouteLogger {
|
|
|
318
326
|
info?: (fields: Record<string, unknown>, msg?: string) => void;
|
|
319
327
|
}
|
|
320
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Template render pipeline.
|
|
331
|
+
*
|
|
332
|
+
* authorMjml + handlebarsContext
|
|
333
|
+
* ↓ Handlebars render → MJML with substituted vars
|
|
334
|
+
* ↓ mjml-core compile → HTML
|
|
335
|
+
* ↓ html-to-text derive → plain text alternative
|
|
336
|
+
* ↓ applyTracking(sendId) → tracked HTML with rewritten links + open pixel
|
|
337
|
+
*
|
|
338
|
+
* Subject and preheader run through Handlebars too. Plain text is auto-derived
|
|
339
|
+
* unless the template explicitly overrides it.
|
|
340
|
+
*/
|
|
341
|
+
|
|
342
|
+
interface CompileResult {
|
|
343
|
+
html: string;
|
|
344
|
+
plainText: string;
|
|
345
|
+
errors: Array<{
|
|
346
|
+
line?: number;
|
|
347
|
+
message: string;
|
|
348
|
+
tagName?: string;
|
|
349
|
+
formattedMessage?: string;
|
|
350
|
+
}>;
|
|
351
|
+
}
|
|
352
|
+
/** Compile MJML → HTML, then derive plain text. Called at template publish time. */
|
|
353
|
+
declare function compileTemplate(mjml: string): Promise<CompileResult>;
|
|
354
|
+
/**
|
|
355
|
+
* Compile Maily editor JSON (Tiptap-shape) → HTML via `@maily-to/render`, then
|
|
356
|
+
* derive plain text. Called at template publish time when the template was
|
|
357
|
+
* authored via the WYSIWYG editor.
|
|
358
|
+
*/
|
|
359
|
+
declare function compileMailyTemplate(content: unknown): Promise<CompileResult>;
|
|
360
|
+
/** Auto-derive plain text from compiled HTML. */
|
|
361
|
+
declare function derivePlaintext(html: string): string;
|
|
362
|
+
interface RenderContext {
|
|
363
|
+
/**
|
|
364
|
+
* Host-resolved variables (varsAdapter) live at the context root, so a
|
|
365
|
+
* schema key `user` renders as `{{user.name}}`. Reserved keys below always
|
|
366
|
+
* win over resolved keys.
|
|
367
|
+
*/
|
|
368
|
+
[resolvedVar: string]: unknown;
|
|
369
|
+
contact: Contact;
|
|
370
|
+
vars: Record<string, unknown>;
|
|
371
|
+
/** Properties of the event that triggered the flow run ({{event.*}}). Empty outside flow sends. */
|
|
372
|
+
event?: Record<string, unknown>;
|
|
373
|
+
/** URL the recipient hits to one-click unsubscribe. */
|
|
374
|
+
unsubscribeUrl: string;
|
|
375
|
+
/** URL to view this email in a browser (when implemented). */
|
|
376
|
+
viewInBrowserUrl?: string;
|
|
377
|
+
/** URL for the preference center (when implemented). */
|
|
378
|
+
preferenceCenterUrl?: string;
|
|
379
|
+
/** Configured sender postal address (CAN-SPAM). */
|
|
380
|
+
senderAddress?: string;
|
|
381
|
+
}
|
|
382
|
+
interface RenderedTemplate {
|
|
383
|
+
subject: string;
|
|
384
|
+
preheader: string;
|
|
385
|
+
html: string;
|
|
386
|
+
plainText: string;
|
|
387
|
+
fromName: string;
|
|
388
|
+
fromEmail: string;
|
|
389
|
+
replyTo: string | null;
|
|
390
|
+
}
|
|
391
|
+
interface RenderOptions {
|
|
392
|
+
/** Extra Handlebars helpers contributed by the host. */
|
|
393
|
+
helpers?: Record<string, Handlebars.HelperDelegate>;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Render a published template against a contact + vars context. Returns the
|
|
397
|
+
* substituted subject/preheader/html/plainText. Tracking is NOT applied here —
|
|
398
|
+
* that step needs the send id and runs separately via `applyTracking`.
|
|
399
|
+
*/
|
|
400
|
+
declare function renderTemplate(template: TemplateDoc, ctx: RenderContext, opts?: RenderOptions): Promise<RenderedTemplate>;
|
|
401
|
+
interface TrackingOptions {
|
|
402
|
+
sendId: string;
|
|
403
|
+
publicUrl: string;
|
|
404
|
+
trackOpens: boolean;
|
|
405
|
+
trackClicks: boolean;
|
|
406
|
+
/** URL that must NOT be rewritten (e.g. the resolved unsubscribe URL). */
|
|
407
|
+
preserveUrls?: string[];
|
|
408
|
+
/**
|
|
409
|
+
* HMAC key for tracking-URL signatures — pass `config.unsubscribeSecret`.
|
|
410
|
+
*
|
|
411
|
+
* When set, every generated `/m/open` and `/m/click` URL carries a truncated
|
|
412
|
+
* HMAC so it cannot be forged or enumerated from a neighbouring ObjectId.
|
|
413
|
+
* When omitted the legacy unsigned shape is emitted; that path exists for
|
|
414
|
+
* preview/test renders that never reach the tracking endpoints, not as a
|
|
415
|
+
* supported production mode.
|
|
416
|
+
*/
|
|
417
|
+
signingSecret?: string;
|
|
418
|
+
}
|
|
419
|
+
interface TrackingResult {
|
|
420
|
+
html: string;
|
|
421
|
+
/** linkId → original URL map; persist on `mailer_sends.links` for click resolution. */
|
|
422
|
+
links: Array<{
|
|
423
|
+
linkId: string;
|
|
424
|
+
url: string;
|
|
425
|
+
}>;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Rewrite `<a href>` in `html` to /m/click/<sendId>/<linkId>/<sig> and append
|
|
429
|
+
* an open pixel at /m/open/<sendId>.<sig>.png. Returns the modified HTML plus
|
|
430
|
+
* the link map to persist on the send document.
|
|
431
|
+
*
|
|
432
|
+
* The `<sig>` components are 12-character truncated HMACs (see
|
|
433
|
+
* `signTrackingToken`) and are present whenever `opts.signingSecret` is set.
|
|
434
|
+
* Without them a Mongo ObjectId is the only thing standing between an attacker
|
|
435
|
+
* and a forged open — and ObjectIds are a timestamp plus a per-process counter,
|
|
436
|
+
* so one received email hands out its neighbours.
|
|
437
|
+
*/
|
|
438
|
+
declare function applyTracking(html: string, opts: TrackingOptions): TrackingResult;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Agent router — the mailery surface for automation.
|
|
442
|
+
*
|
|
443
|
+
* Everything an operator does by hand in the admin SPA to take an email
|
|
444
|
+
* program from "deployed" to "safely on" — render a template as a real
|
|
445
|
+
* contact and check it, send it through the real pipeline and watch delivery,
|
|
446
|
+
* ask what a flow would do to a contact, walk a canary run step by step,
|
|
447
|
+
* gate and arm a flow — is reachable here as JSON, behind a bearer token,
|
|
448
|
+
* with no browser session. It is built for an AI agent or a CI job to drive,
|
|
449
|
+
* which shapes three things:
|
|
450
|
+
*
|
|
451
|
+
* - Every answer is structured. A verification is a list of named checks
|
|
452
|
+
* with pass/warn/fail, not a rendered page to squint at.
|
|
453
|
+
* - Every operation that could touch a real person is guarded by the
|
|
454
|
+
* `testContacts` pattern: test sends, event firing, run stepping and
|
|
455
|
+
* resets only apply to contacts whose email matches it. A router with no
|
|
456
|
+
* pattern configured refuses those routes outright rather than assuming.
|
|
457
|
+
* - Nothing here can enable a flow without stamping its trigger watermark
|
|
458
|
+
* (see runner/arm.ts), so an agent cannot replay a month of signups.
|
|
459
|
+
*
|
|
460
|
+
* Mount it beside the admin router, on a path the host's session middleware
|
|
461
|
+
* does not cover, and give it the same JSON body the SPA would:
|
|
462
|
+
*
|
|
463
|
+
* app.use('/admin/mailer/agent', createAgentRouter(mailer, {
|
|
464
|
+
* tokens: [{ token: process.env.MAILERY_AGENT_TOKEN!, actor: 'agent:claude' }],
|
|
465
|
+
* testContacts: /^qa\+.*@example\.com$/i,
|
|
466
|
+
* }))
|
|
467
|
+
*
|
|
468
|
+
* `GET /` describes every route so a client can discover the surface. The
|
|
469
|
+
* full admin JSON API (docs/reference/admin-api.md) is mounted under `/api`
|
|
470
|
+
* with the token's actor, so reads and existing operations need no second
|
|
471
|
+
* auth path.
|
|
472
|
+
*/
|
|
473
|
+
|
|
474
|
+
interface AgentToken {
|
|
475
|
+
/** The bearer token. At least 24 characters; generate it, never type it. */
|
|
476
|
+
token: string;
|
|
477
|
+
/** Audit actor recorded for everything done with this token, e.g. `agent:claude`. */
|
|
478
|
+
actor: string;
|
|
479
|
+
}
|
|
480
|
+
interface AgentRouterOptions {
|
|
481
|
+
/** Required. The router refuses to construct without at least one token. */
|
|
482
|
+
tokens: AgentToken[];
|
|
483
|
+
/**
|
|
484
|
+
* Which contacts may be test-sent to, stepped through flows, fired events
|
|
485
|
+
* for, or reset. A regular expression over the email address, or a
|
|
486
|
+
* predicate. Routes that need it answer 403 when it is not configured —
|
|
487
|
+
* the safe default for a surface an automated caller drives.
|
|
488
|
+
*/
|
|
489
|
+
testContacts?: RegExp | ((email: string) => boolean);
|
|
490
|
+
/** Structured logger for failures. Defaults to console. */
|
|
491
|
+
logger?: RouteLogger;
|
|
492
|
+
/** Passed through to the admin JSON API (tests inject a stub). */
|
|
493
|
+
mailTesterClient?: AdminRouterOptions['mailTesterClient'];
|
|
494
|
+
}
|
|
495
|
+
declare const MIN_AGENT_TOKEN_LENGTH = 24;
|
|
496
|
+
type Check = {
|
|
497
|
+
id: string;
|
|
498
|
+
status: 'pass' | 'warn' | 'fail';
|
|
499
|
+
detail?: unknown;
|
|
500
|
+
};
|
|
501
|
+
declare function createAgentRouter(mailer: Mailer, opts: AgentRouterOptions): Router;
|
|
502
|
+
interface VerifyOptions {
|
|
503
|
+
eventProperties?: Record<string, unknown>;
|
|
504
|
+
vars?: Record<string, unknown>;
|
|
505
|
+
includeRendered?: boolean;
|
|
506
|
+
varsSchema?: Record<string, unknown> | null;
|
|
507
|
+
}
|
|
508
|
+
interface VerifyReport {
|
|
509
|
+
ok: boolean;
|
|
510
|
+
template: {
|
|
511
|
+
slug: string;
|
|
512
|
+
kind: TemplateDoc['kind'];
|
|
513
|
+
name: string;
|
|
514
|
+
};
|
|
515
|
+
contact: {
|
|
516
|
+
externalId: string;
|
|
517
|
+
email: string;
|
|
518
|
+
};
|
|
519
|
+
checks: Check[];
|
|
520
|
+
links: {
|
|
521
|
+
total: number;
|
|
522
|
+
sample: string[];
|
|
523
|
+
};
|
|
524
|
+
rendered: {
|
|
525
|
+
subject: string;
|
|
526
|
+
preheader: string;
|
|
527
|
+
htmlBytes: number;
|
|
528
|
+
textLength: number;
|
|
529
|
+
fromEmail: string;
|
|
530
|
+
} | {
|
|
531
|
+
subject: string;
|
|
532
|
+
preheader: string;
|
|
533
|
+
html: string;
|
|
534
|
+
plainText: string;
|
|
535
|
+
fromEmail: string;
|
|
536
|
+
fromName: string;
|
|
537
|
+
replyTo: string | null;
|
|
538
|
+
} | null;
|
|
539
|
+
}
|
|
540
|
+
declare function verifyTemplate(mailer: Mailer, tpl: TemplateDoc, contact: Contact, opts?: VerifyOptions): Promise<VerifyReport>;
|
|
541
|
+
interface RenderForContactOptions {
|
|
542
|
+
reason: 'preview' | 'test';
|
|
543
|
+
eventProperties?: Record<string, unknown>;
|
|
544
|
+
vars?: Record<string, unknown>;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Render a published template for one real contact the way a send would:
|
|
548
|
+
* host vars resolved through the varsAdapter, the contact at the root, a
|
|
549
|
+
* genuinely signed unsubscribe URL. The one difference from a send is that
|
|
550
|
+
* no tracking is applied — nothing here is queued.
|
|
551
|
+
*/
|
|
552
|
+
declare function renderForContact(mailer: Mailer, tpl: TemplateDoc, contact: Contact, opts: RenderForContactOptions): Promise<{
|
|
553
|
+
rendered: RenderedTemplate;
|
|
554
|
+
resolved: Record<string, unknown>;
|
|
555
|
+
unsubscribeUrl: string;
|
|
556
|
+
/** The exact object the template was rendered against. */
|
|
557
|
+
context: Record<string, unknown>;
|
|
558
|
+
}>;
|
|
559
|
+
/**
|
|
560
|
+
* Every dotted path a Handlebars source references outside `#each`/`#with`
|
|
561
|
+
* blocks (whose paths are relative to the iterated item and cannot be
|
|
562
|
+
* resolved against the root context) and outside HTML comments. Helper
|
|
563
|
+
* names, literals, hash keys, `@data` variables and Handlebars comments are
|
|
564
|
+
* skipped.
|
|
565
|
+
*/
|
|
566
|
+
declare function referencedPaths(source: string, helperNames?: Iterable<string>): string[];
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Enabling a flow, done safely — and running it against test contacts only.
|
|
570
|
+
*
|
|
571
|
+
* The trigger scan starts from `flow.lastTriggerScanAt ?? flow.createdAt`
|
|
572
|
+
* (triggers.ts). A flow that has never been enabled has a null watermark, so a
|
|
573
|
+
* bare `enabled: true` replays every matching event since the flow document
|
|
574
|
+
* was created: every signup of the past month gets the welcome email in one
|
|
575
|
+
* tick, a week or more late. That is the single most dangerous write in the
|
|
576
|
+
* admin surface, and until 0.16 it was one click (publish, or resume).
|
|
577
|
+
*
|
|
578
|
+
* `armFlow` is the path that flips `enabled` on. It stamps the watermark
|
|
579
|
+
* (default: now) in the SAME update, reports how many events it is choosing
|
|
580
|
+
* to skip, and writes an audit row. `publish` and `resume` in the admin API
|
|
581
|
+
* go through `stampWatermarkIfNull` for the same guarantee.
|
|
582
|
+
*
|
|
583
|
+
* `gateFlow` publishes a canary version whose first step exits anyone
|
|
584
|
+
* without a tag, so the real runner can exercise the real steps in
|
|
585
|
+
* production against test contacts while every real contact enters and
|
|
586
|
+
* exits at step 0 with no send. `ungateFlow` restores the newest ungated
|
|
587
|
+
* version. Runs pin their version, so a contact mid-canary finishes on it.
|
|
588
|
+
*/
|
|
589
|
+
|
|
590
|
+
/** A typed failure the HTTP layer can map to a status code without guessing. */
|
|
591
|
+
declare class FlowOperationError extends Error {
|
|
592
|
+
readonly code: string;
|
|
593
|
+
readonly status: number;
|
|
594
|
+
constructor(code: string, message: string, status?: number);
|
|
595
|
+
}
|
|
596
|
+
interface ArmFlowOptions {
|
|
597
|
+
/** Audit actor, e.g. `agent:claude` or `human:jeff@example.com`. */
|
|
598
|
+
actor: string;
|
|
599
|
+
/**
|
|
600
|
+
* Watermark to stamp. Defaults to now, which means only events fired AFTER
|
|
601
|
+
* this call enter the flow. Pass an earlier instant only when you mean for
|
|
602
|
+
* the events after it to enter — the result says how many that is.
|
|
603
|
+
*/
|
|
604
|
+
since?: Date;
|
|
605
|
+
}
|
|
606
|
+
interface ArmFlowResult {
|
|
607
|
+
slug: string;
|
|
608
|
+
version: number;
|
|
609
|
+
/** True when this call flipped `enabled` on. */
|
|
610
|
+
armed: boolean;
|
|
611
|
+
/** True when the flow was already enabled; nothing was written. */
|
|
612
|
+
alreadyEnabled: boolean;
|
|
613
|
+
/** The watermark now on the flow. */
|
|
614
|
+
watermark: Date | null;
|
|
615
|
+
eventName: string | null;
|
|
616
|
+
/** Events for the trigger name that fired before the watermark and will never enter. */
|
|
617
|
+
skippedEvents: number;
|
|
618
|
+
/**
|
|
619
|
+
* Events that WILL enter on the next tick: everything after the watermark,
|
|
620
|
+
* plus anything created inside the scanner's 30-second overlap window just
|
|
621
|
+
* before it.
|
|
622
|
+
*/
|
|
623
|
+
pendingEvents: number;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Stamp `lastTriggerScanAt` when it is null. Used by every code path that
|
|
627
|
+
* turns `enabled` on so a first enable never replays history. A flow that
|
|
628
|
+
* has scanned before keeps its watermark: pausing and resuming deliberately
|
|
629
|
+
* lets the events fired during the pause enter.
|
|
630
|
+
*/
|
|
631
|
+
declare function stampWatermarkIfNull(collections: Collections, flow: FlowDoc, now?: Date): Promise<Date | null>;
|
|
632
|
+
declare function armFlow(mailer: Mailer, slug: string, opts: ArmFlowOptions): Promise<ArmFlowResult>;
|
|
633
|
+
/** The inverse: `enabled: false`. In-flight runs continue (that is what pause means). */
|
|
634
|
+
declare function disarmFlow(mailer: Mailer, slug: string, actor: string): Promise<{
|
|
635
|
+
slug: string;
|
|
636
|
+
disarmed: boolean;
|
|
637
|
+
}>;
|
|
638
|
+
/**
|
|
639
|
+
* The gate step. `canaryGate` is a marker the runner ignores (it evaluates
|
|
640
|
+
* the condition like any other) and this module uses to recognise its own
|
|
641
|
+
* work when removing it.
|
|
642
|
+
*/
|
|
643
|
+
type CanaryGateStep = Extract<FlowStep, {
|
|
644
|
+
type: 'condition';
|
|
645
|
+
}> & {
|
|
646
|
+
canaryGate: true;
|
|
647
|
+
};
|
|
648
|
+
declare function isCanaryGate(step: unknown): step is CanaryGateStep;
|
|
649
|
+
interface GateFlowResult {
|
|
650
|
+
slug: string;
|
|
651
|
+
version: number;
|
|
652
|
+
tag: string;
|
|
653
|
+
enabled: boolean;
|
|
654
|
+
}
|
|
655
|
+
declare function gateFlow(mailer: Mailer, slug: string, opts: {
|
|
656
|
+
tag: string;
|
|
657
|
+
actor: string;
|
|
658
|
+
}): Promise<GateFlowResult>;
|
|
659
|
+
interface UngateFlowResult {
|
|
660
|
+
slug: string;
|
|
661
|
+
version: number;
|
|
662
|
+
/** The version whose steps were restored. */
|
|
663
|
+
restoredFrom: number;
|
|
664
|
+
enabled: boolean;
|
|
665
|
+
}
|
|
666
|
+
declare function ungateFlow(mailer: Mailer, slug: string, opts: {
|
|
667
|
+
actor: string;
|
|
668
|
+
}): Promise<UngateFlowResult>;
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Flow simulation: "what would happen to this contact if the trigger fired
|
|
672
|
+
* now?" — answered by walking the published steps against the contact's real
|
|
673
|
+
* state (tags, fields, events, sends, subscription) with a virtual clock and
|
|
674
|
+
* WITHOUT writing anything.
|
|
675
|
+
*
|
|
676
|
+
* This is the dry run an operator wants before arming a flow: it shows the
|
|
677
|
+
* branch a contact takes, every gate's verdict, the sends and the wall-clock
|
|
678
|
+
* moment each would go out (waits and delivery windows applied), and where
|
|
679
|
+
* the run ends. It reads the same predicate evaluator the runner uses, so a
|
|
680
|
+
* gate that passes here passes in production — for the state as it is at the
|
|
681
|
+
* moment of the call. Events that would arrive during the run are of course
|
|
682
|
+
* not known, which is why `path[].at` is labelled a projection.
|
|
683
|
+
*/
|
|
684
|
+
|
|
685
|
+
interface SimulateOptions {
|
|
686
|
+
/** Virtual "now" the simulated run enters at. Defaults to the real now. */
|
|
687
|
+
at?: Date;
|
|
688
|
+
/** Properties of the simulated trigger event ({{event.*}}, triggerProperty* predicates). */
|
|
689
|
+
eventProperties?: Record<string, unknown>;
|
|
690
|
+
/** Steps to walk. Defaults to the flow's live steps. */
|
|
691
|
+
steps?: FlowStep[];
|
|
692
|
+
}
|
|
693
|
+
interface SimulatedStep {
|
|
694
|
+
/** Projected wall-clock moment the step is reached. */
|
|
695
|
+
at: Date;
|
|
696
|
+
stepIndex: number;
|
|
697
|
+
branchPath: Array<number | 'true' | 'false'>;
|
|
698
|
+
type: FlowStep['type'];
|
|
699
|
+
outcome: 'waited' | 'passed' | 'skipped_next' | 'exited' | 'branch_true' | 'branch_false' | 'send' | 'send_deferred' | 'tagged' | 'event_fired' | 'webhook' | 'completed';
|
|
700
|
+
detail?: Record<string, unknown>;
|
|
701
|
+
}
|
|
702
|
+
interface SimulationResult {
|
|
703
|
+
flow: {
|
|
704
|
+
slug: string;
|
|
705
|
+
version: number;
|
|
706
|
+
enabled: boolean;
|
|
707
|
+
};
|
|
708
|
+
contact: {
|
|
709
|
+
externalId: string;
|
|
710
|
+
email: string;
|
|
711
|
+
};
|
|
712
|
+
enteredAt: Date;
|
|
713
|
+
/** Whether the trigger scan would create a run at all, and why not. */
|
|
714
|
+
wouldEnter: {
|
|
715
|
+
ok: boolean;
|
|
716
|
+
reasons: string[];
|
|
717
|
+
};
|
|
718
|
+
path: SimulatedStep[];
|
|
719
|
+
sends: Array<{
|
|
720
|
+
templateSlug: string;
|
|
721
|
+
at: Date;
|
|
722
|
+
stepIndex: number;
|
|
723
|
+
branchPath: Array<number | 'true' | 'false'>;
|
|
724
|
+
}>;
|
|
725
|
+
terminal: {
|
|
726
|
+
kind: 'completed' | 'exited' | 'truncated';
|
|
727
|
+
reason: string;
|
|
728
|
+
at: Date;
|
|
729
|
+
};
|
|
730
|
+
/** Projected time from entry to the terminal step. */
|
|
731
|
+
durationMs: number;
|
|
732
|
+
}
|
|
733
|
+
declare function simulateFlow(flow: FlowDoc, contact: Contact, ctx: RunnerContext, opts?: SimulateOptions): Promise<SimulationResult>;
|
|
734
|
+
|
|
321
735
|
/**
|
|
322
736
|
* Inbound DMARC aggregate-report webhook.
|
|
323
737
|
*
|
|
@@ -486,117 +900,6 @@ interface PublicRouterOptions {
|
|
|
486
900
|
}
|
|
487
901
|
declare function createPublicRouter(mailer: Mailer, opts?: PublicRouterOptions): Router;
|
|
488
902
|
|
|
489
|
-
/**
|
|
490
|
-
* Template render pipeline.
|
|
491
|
-
*
|
|
492
|
-
* authorMjml + handlebarsContext
|
|
493
|
-
* ↓ Handlebars render → MJML with substituted vars
|
|
494
|
-
* ↓ mjml-core compile → HTML
|
|
495
|
-
* ↓ html-to-text derive → plain text alternative
|
|
496
|
-
* ↓ applyTracking(sendId) → tracked HTML with rewritten links + open pixel
|
|
497
|
-
*
|
|
498
|
-
* Subject and preheader run through Handlebars too. Plain text is auto-derived
|
|
499
|
-
* unless the template explicitly overrides it.
|
|
500
|
-
*/
|
|
501
|
-
|
|
502
|
-
interface CompileResult {
|
|
503
|
-
html: string;
|
|
504
|
-
plainText: string;
|
|
505
|
-
errors: Array<{
|
|
506
|
-
line?: number;
|
|
507
|
-
message: string;
|
|
508
|
-
tagName?: string;
|
|
509
|
-
formattedMessage?: string;
|
|
510
|
-
}>;
|
|
511
|
-
}
|
|
512
|
-
/** Compile MJML → HTML, then derive plain text. Called at template publish time. */
|
|
513
|
-
declare function compileTemplate(mjml: string): Promise<CompileResult>;
|
|
514
|
-
/**
|
|
515
|
-
* Compile Maily editor JSON (Tiptap-shape) → HTML via `@maily-to/render`, then
|
|
516
|
-
* derive plain text. Called at template publish time when the template was
|
|
517
|
-
* authored via the WYSIWYG editor.
|
|
518
|
-
*/
|
|
519
|
-
declare function compileMailyTemplate(content: unknown): Promise<CompileResult>;
|
|
520
|
-
/** Auto-derive plain text from compiled HTML. */
|
|
521
|
-
declare function derivePlaintext(html: string): string;
|
|
522
|
-
interface RenderContext {
|
|
523
|
-
/**
|
|
524
|
-
* Host-resolved variables (varsAdapter) live at the context root, so a
|
|
525
|
-
* schema key `user` renders as `{{user.name}}`. Reserved keys below always
|
|
526
|
-
* win over resolved keys.
|
|
527
|
-
*/
|
|
528
|
-
[resolvedVar: string]: unknown;
|
|
529
|
-
contact: Contact;
|
|
530
|
-
vars: Record<string, unknown>;
|
|
531
|
-
/** Properties of the event that triggered the flow run ({{event.*}}). Empty outside flow sends. */
|
|
532
|
-
event?: Record<string, unknown>;
|
|
533
|
-
/** URL the recipient hits to one-click unsubscribe. */
|
|
534
|
-
unsubscribeUrl: string;
|
|
535
|
-
/** URL to view this email in a browser (when implemented). */
|
|
536
|
-
viewInBrowserUrl?: string;
|
|
537
|
-
/** URL for the preference center (when implemented). */
|
|
538
|
-
preferenceCenterUrl?: string;
|
|
539
|
-
/** Configured sender postal address (CAN-SPAM). */
|
|
540
|
-
senderAddress?: string;
|
|
541
|
-
}
|
|
542
|
-
interface RenderedTemplate {
|
|
543
|
-
subject: string;
|
|
544
|
-
preheader: string;
|
|
545
|
-
html: string;
|
|
546
|
-
plainText: string;
|
|
547
|
-
fromName: string;
|
|
548
|
-
fromEmail: string;
|
|
549
|
-
replyTo: string | null;
|
|
550
|
-
}
|
|
551
|
-
interface RenderOptions {
|
|
552
|
-
/** Extra Handlebars helpers contributed by the host. */
|
|
553
|
-
helpers?: Record<string, Handlebars.HelperDelegate>;
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Render a published template against a contact + vars context. Returns the
|
|
557
|
-
* substituted subject/preheader/html/plainText. Tracking is NOT applied here —
|
|
558
|
-
* that step needs the send id and runs separately via `applyTracking`.
|
|
559
|
-
*/
|
|
560
|
-
declare function renderTemplate(template: TemplateDoc, ctx: RenderContext, opts?: RenderOptions): Promise<RenderedTemplate>;
|
|
561
|
-
interface TrackingOptions {
|
|
562
|
-
sendId: string;
|
|
563
|
-
publicUrl: string;
|
|
564
|
-
trackOpens: boolean;
|
|
565
|
-
trackClicks: boolean;
|
|
566
|
-
/** URL that must NOT be rewritten (e.g. the resolved unsubscribe URL). */
|
|
567
|
-
preserveUrls?: string[];
|
|
568
|
-
/**
|
|
569
|
-
* HMAC key for tracking-URL signatures — pass `config.unsubscribeSecret`.
|
|
570
|
-
*
|
|
571
|
-
* When set, every generated `/m/open` and `/m/click` URL carries a truncated
|
|
572
|
-
* HMAC so it cannot be forged or enumerated from a neighbouring ObjectId.
|
|
573
|
-
* When omitted the legacy unsigned shape is emitted; that path exists for
|
|
574
|
-
* preview/test renders that never reach the tracking endpoints, not as a
|
|
575
|
-
* supported production mode.
|
|
576
|
-
*/
|
|
577
|
-
signingSecret?: string;
|
|
578
|
-
}
|
|
579
|
-
interface TrackingResult {
|
|
580
|
-
html: string;
|
|
581
|
-
/** linkId → original URL map; persist on `mailer_sends.links` for click resolution. */
|
|
582
|
-
links: Array<{
|
|
583
|
-
linkId: string;
|
|
584
|
-
url: string;
|
|
585
|
-
}>;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Rewrite `<a href>` in `html` to /m/click/<sendId>/<linkId>/<sig> and append
|
|
589
|
-
* an open pixel at /m/open/<sendId>.<sig>.png. Returns the modified HTML plus
|
|
590
|
-
* the link map to persist on the send document.
|
|
591
|
-
*
|
|
592
|
-
* The `<sig>` components are 12-character truncated HMACs (see
|
|
593
|
-
* `signTrackingToken`) and are present whenever `opts.signingSecret` is set.
|
|
594
|
-
* Without them a Mongo ObjectId is the only thing standing between an attacker
|
|
595
|
-
* and a forged open — and ObjectIds are a timestamp plus a per-process counter,
|
|
596
|
-
* so one received email hands out its neighbours.
|
|
597
|
-
*/
|
|
598
|
-
declare function applyTracking(html: string, opts: TrackingOptions): TrackingResult;
|
|
599
|
-
|
|
600
903
|
/**
|
|
601
904
|
* HMAC-signed tokens for unsubscribe + preference-center URLs.
|
|
602
905
|
*
|
|
@@ -750,4 +1053,4 @@ declare const DEDUPE_POLICIES: readonly DedupePolicyOption[];
|
|
|
750
1053
|
*/
|
|
751
1054
|
declare const VERSION: string;
|
|
752
1055
|
|
|
753
|
-
export { AdapterFilter, type AdminRouterOptions, Contact, ContactAdapter, DEDUPE_POLICIES, DEFAULT_BOT_UA_RE, type DedupePolicyOption, DeliveryWindow, type DmarcInboundOptions, type DrainPendingUnsubsOptions, type DrainPendingUnsubsResult, FLOW_STEP_KINDS, FlowStep, type FlowStepKindOption, type InboundAttachment, type InboundParser, MailProvider, Mailer, MongoContactAdapter, type MongoContactAdapterOptions, NormalizedEvent, PREDICATE_KINDS, Predicate, type PredicateKind, type PredicateKindOption, type PublicRouterOptions, type RouteLogger, SEGMENT_FILTER_KINDS, SegmentFilter, type SegmentFilterKind, type SegmentFilterKindOption, SendArgs, SendGridProvider, type SendGridProviderOptions, SendResult, SuppressionScope, TRACKING_SIG_LENGTH, TemplateDoc, type TrackingScope, type TrackingTokenParams, VERSION, applyTracking, applyWebhookEvent, compileMailyTemplate, compileTemplate, computeDeliveryTime, createAdminRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, derivePlaintext, dispatchSend, drainPendingUnsubscribes, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, renderTemplate, runTick, sendgridInboundParser, sha256Hex, signTrackingToken, signUnsubscribeToken, sweepStrandedFlowRuns, verifyTrackingToken, verifyUnsubscribeToken };
|
|
1056
|
+
export { AdapterFilter, type AdminRouterOptions, type AgentRouterOptions, type AgentToken, type ArmFlowOptions, type ArmFlowResult, Collections, Contact, ContactAdapter, DEDUPE_POLICIES, DEFAULT_BOT_UA_RE, type DedupePolicyOption, DeliveryWindow, type DmarcInboundOptions, type DrainPendingUnsubsOptions, type DrainPendingUnsubsResult, FLOW_STEP_KINDS, FlowDoc, FlowOperationError, FlowStep, type FlowStepKindOption, type GateFlowResult, type InboundAttachment, type InboundParser, MIN_AGENT_TOKEN_LENGTH, MailProvider, Mailer, MongoContactAdapter, type MongoContactAdapterOptions, NormalizedEvent, PREDICATE_KINDS, Predicate, type PredicateKind, type PredicateKindOption, type PublicRouterOptions, type RouteLogger, SEGMENT_FILTER_KINDS, SegmentFilter, type SegmentFilterKind, type SegmentFilterKindOption, SendArgs, SendGridProvider, type SendGridProviderOptions, SendResult, type SimulateOptions, type SimulatedStep, type SimulationResult, SuppressionScope, TRACKING_SIG_LENGTH, TemplateDoc, type TrackingScope, type TrackingTokenParams, type UngateFlowResult, VERSION, type VerifyOptions, type VerifyReport, applyTracking, applyWebhookEvent, armFlow, compileMailyTemplate, compileTemplate, computeDeliveryTime, createAdminApiRouter, createAdminRouter, createAgentRouter, createPublicRouter, defaultFlowStep, defaultPredicate, defaultSegmentFilter, derivePlaintext, disarmFlow, dispatchSend, drainPendingUnsubscribes, gateFlow, isCanaryGate, predicateKind, processNewlyFiredEventTriggers, processOneRunStep, referencedPaths, renderForContact, renderTemplate, runTick, sendgridInboundParser, sha256Hex, signTrackingToken, signUnsubscribeToken, simulateFlow, stampWatermarkIfNull, sweepStrandedFlowRuns, ungateFlow, verifyTemplate, verifyTrackingToken, verifyUnsubscribeToken };
|