mailery 0.4.0 → 0.7.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 +3 -1
- package/dist/admin/spa/index-CjQTOX9H.js +41 -0
- package/dist/admin/spa/index-CjQTOX9H.js.map +1 -0
- package/dist/admin/spa/index.html +1 -1
- package/dist/admin/spa/template-editor-yH0Oz4Ix.js +502 -0
- package/dist/admin/spa/template-editor-yH0Oz4Ix.js.map +1 -0
- package/dist/cli.cjs +231 -56
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +231 -56
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +3040 -259
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -4
- package/dist/index.d.ts +62 -4
- package/dist/index.js +3034 -260
- package/dist/index.js.map +1 -1
- package/dist/{null-CGaNDDlQ.d.cts → null-B0rPgE5_.d.cts} +449 -4
- package/dist/{null-CGaNDDlQ.d.ts → null-B0rPgE5_.d.ts} +449 -4
- package/dist/testing.cjs +1199 -111
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1196 -111
- package/dist/testing.js.map +1 -1
- package/package.json +8 -1
- package/dist/admin/spa/index-CyOYk9BX.js +0 -41
- package/dist/admin/spa/index-CyOYk9BX.js.map +0 -1
- package/dist/admin/spa/template-editor-DjkGIZF_.js +0 -502
- package/dist/admin/spa/template-editor-DjkGIZF_.js.map +0 -1
|
@@ -47,6 +47,24 @@ interface ContactAdapter {
|
|
|
47
47
|
addTags?(externalId: string, tags: string[]): Promise<void>;
|
|
48
48
|
removeTags?(externalId: string, tags: string[]): Promise<void>;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Constrains WHEN a send step's email may go out. The flow's waits decide the
|
|
52
|
+
* earliest moment (T + N days); the window then pushes that moment forward —
|
|
53
|
+
* never backward — to the next allowed slot:
|
|
54
|
+
*
|
|
55
|
+
* - `timeOfDay` — deliver at this local wall-clock time ('HH:mm'). A send
|
|
56
|
+
* arriving after that time waits for the next day's slot (with a short
|
|
57
|
+
* grace period so tick jitter doesn't add 24h).
|
|
58
|
+
* - `weekdaysOnly` — a slot landing on Saturday/Sunday moves to Monday.
|
|
59
|
+
* - `useContactTimezone` — interpret times in `contact.timezone` when set,
|
|
60
|
+
* else fall back to `timezone` (IANA name, default UTC).
|
|
61
|
+
*/
|
|
62
|
+
interface DeliveryWindow {
|
|
63
|
+
weekdaysOnly?: boolean;
|
|
64
|
+
timeOfDay?: string;
|
|
65
|
+
useContactTimezone?: boolean;
|
|
66
|
+
timezone?: string;
|
|
67
|
+
}
|
|
50
68
|
type FlowStep = {
|
|
51
69
|
type: 'wait';
|
|
52
70
|
value: number;
|
|
@@ -65,6 +83,7 @@ type FlowStep = {
|
|
|
65
83
|
templateSlug: string;
|
|
66
84
|
providerOverride?: string;
|
|
67
85
|
vars?: Record<string, unknown>;
|
|
86
|
+
delivery?: DeliveryWindow;
|
|
68
87
|
} | {
|
|
69
88
|
type: 'tag';
|
|
70
89
|
addTags?: string[];
|
|
@@ -375,7 +394,7 @@ type QueueDriverConfig = {
|
|
|
375
394
|
* server-shaped interfaces.
|
|
376
395
|
*/
|
|
377
396
|
type SubscriptionStatus = 'subscribed' | 'pending_doi' | 'unsubscribed' | 'bounced' | 'complained';
|
|
378
|
-
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed';
|
|
397
|
+
type SendStatus = 'queued' | 'sending' | 'sent' | 'delivered' | 'bounced' | 'complained' | 'failed' | 'suppressed' | 'cancelled';
|
|
379
398
|
type TemplateKind = 'transactional' | 'marketing';
|
|
380
399
|
type SuppressionScope = 'all' | 'marketing' | 'transactional';
|
|
381
400
|
type SuppressionReason = 'unsubscribed' | 'hard_bounce' | 'complaint' | 'manual' | 'list_cleaning' | 'gdpr_forget';
|
|
@@ -417,6 +436,63 @@ type SenderDomainValidation = {
|
|
|
417
436
|
};
|
|
418
437
|
declare function validateSenderDomain(fromEmail: string, templateKind: TemplateKind, registry: SenderDomainRegistry | undefined): SenderDomainValidation;
|
|
419
438
|
|
|
439
|
+
/**
|
|
440
|
+
* VarsAdapter — host-provided template variables, resolved at render time.
|
|
441
|
+
*
|
|
442
|
+
* The host declares a zod schema (the contract templates can rely on) plus a
|
|
443
|
+
* `resolve` function that loads those values from the host's own database for
|
|
444
|
+
* a given contact. Resolved keys are merged into the render context root, so
|
|
445
|
+
* a schema key `user` is referenced in templates as `{{user.name}}`.
|
|
446
|
+
*
|
|
447
|
+
* The schema does double duty: it is exposed to the admin SPA as JSON Schema
|
|
448
|
+
* (GET /vars-schema) to drive editor autocomplete, and the content linter
|
|
449
|
+
* uses it to flag `{{paths}}` that don't exist.
|
|
450
|
+
*/
|
|
451
|
+
|
|
452
|
+
interface VarsResolveInfo {
|
|
453
|
+
/** Why the vars are being resolved. Previews and tests should be side-effect free. */
|
|
454
|
+
reason: 'send' | 'preview' | 'test';
|
|
455
|
+
/** Slug of the template being rendered, when known. */
|
|
456
|
+
templateSlug?: string;
|
|
457
|
+
/** Slug of the flow the send belongs to, when the render is part of a flow run. */
|
|
458
|
+
flowSlug?: string;
|
|
459
|
+
/** Name of the event that triggered the flow run, when applicable. */
|
|
460
|
+
eventName?: string;
|
|
461
|
+
/**
|
|
462
|
+
* Properties of the triggering event (`mailer.fire(name, id, properties)`).
|
|
463
|
+
* This is how a resolver scopes lookups for account/topic flows:
|
|
464
|
+
* `info.eventProperties?.accountId` tells it WHICH account the run is about.
|
|
465
|
+
* Also available raw in templates as `{{event.*}}`.
|
|
466
|
+
*/
|
|
467
|
+
eventProperties?: Record<string, unknown>;
|
|
468
|
+
}
|
|
469
|
+
interface VarsAdapter<S extends z.ZodType = z.ZodType> {
|
|
470
|
+
/** Contract for what `resolve` returns. Root keys become root template variables. */
|
|
471
|
+
schema: S;
|
|
472
|
+
/** Load the variables for one contact from the host's data. */
|
|
473
|
+
resolve(contact: Contact, info: VarsResolveInfo): Promise<z.infer<S>> | z.infer<S>;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Identity helper that pins `resolve`'s return type to `z.infer<schema>` —
|
|
477
|
+
* without it, TypeScript widens the schema generic and the return type is
|
|
478
|
+
* unchecked.
|
|
479
|
+
*
|
|
480
|
+
* const varsAdapter = defineVars({
|
|
481
|
+
* schema: z.object({ user: z.object({ name: z.string() }) }),
|
|
482
|
+
* async resolve(contact) {
|
|
483
|
+
* return { user: { name: await lookupName(contact.externalId) } }
|
|
484
|
+
* },
|
|
485
|
+
* })
|
|
486
|
+
*/
|
|
487
|
+
declare function defineVars<S extends z.ZodType>(adapter: VarsAdapter<S>): VarsAdapter<S>;
|
|
488
|
+
/**
|
|
489
|
+
* Render-context keys mailery owns. Resolved vars never override these — a
|
|
490
|
+
* schema that declares one is rejected at Mailer.init.
|
|
491
|
+
*/
|
|
492
|
+
declare const RESERVED_VAR_KEYS: readonly ["contact", "vars", "event", "unsubscribeUrl", "viewInBrowserUrl", "preferenceCenterUrl", "senderAddress"];
|
|
493
|
+
/** JSON Schema for the adapter's zod schema (wire format for the admin SPA + linter). */
|
|
494
|
+
declare function varsJsonSchema(adapter: VarsAdapter): Record<string, unknown>;
|
|
495
|
+
|
|
420
496
|
/**
|
|
421
497
|
* Mailer configuration shape. Required + optional surfaces with sane defaults.
|
|
422
498
|
*/
|
|
@@ -430,6 +506,94 @@ interface RedisOptions {
|
|
|
430
506
|
username?: string;
|
|
431
507
|
tls?: boolean;
|
|
432
508
|
}
|
|
509
|
+
interface DnsblListSpec {
|
|
510
|
+
/** DNS suffix to query, e.g. `'dbl.spamhaus.org'`. */
|
|
511
|
+
host: string;
|
|
512
|
+
/** Display label for the UI, e.g. `'Spamhaus DBL'`. */
|
|
513
|
+
label: string;
|
|
514
|
+
}
|
|
515
|
+
interface DnsblConfig {
|
|
516
|
+
/** Domain block lists (DBLs) — queried as `<domain>.<host>`. */
|
|
517
|
+
domainLists?: DnsblListSpec[];
|
|
518
|
+
/** IP block lists — queried as `<reversed-octets>.<host>`. */
|
|
519
|
+
ipLists?: DnsblListSpec[];
|
|
520
|
+
/** Dedicated sending IPs to check. Most operators on shared ESPs leave empty. */
|
|
521
|
+
dedicatedIps?: string[];
|
|
522
|
+
/** Hours between automatic runs. Default 24. Set to 0 to disable scheduled runs. */
|
|
523
|
+
intervalHours?: number;
|
|
524
|
+
}
|
|
525
|
+
interface MailTesterConfig {
|
|
526
|
+
/** API key from your Mail-Tester paid plan. */
|
|
527
|
+
apiKey: string;
|
|
528
|
+
/** Minimum score (0-10) below which publish is blocked. Default 8.0. */
|
|
529
|
+
minScore?: number;
|
|
530
|
+
/**
|
|
531
|
+
* How long a successful score remains cached for the same content. Default 24.
|
|
532
|
+
* Re-publishing the same body within the window does not burn a credit.
|
|
533
|
+
*/
|
|
534
|
+
cacheHours?: number;
|
|
535
|
+
/** API base URL. Defaults to `https://mail-tester.com/api`. Override for staging or alternate provider. */
|
|
536
|
+
baseUrl?: string;
|
|
537
|
+
}
|
|
538
|
+
interface DmarcSourceTag {
|
|
539
|
+
/** Source IP, e.g. `'149.72.45.10'`. */
|
|
540
|
+
ip: string;
|
|
541
|
+
/** Operator-provided label, e.g. `'SendGrid (transactional)'` or `'Hubspot'`. */
|
|
542
|
+
label: string;
|
|
543
|
+
/** When true, ignore failures from this source in the alignment-trend headline. */
|
|
544
|
+
ignored?: boolean;
|
|
545
|
+
}
|
|
546
|
+
interface DmarcConfig {
|
|
547
|
+
/**
|
|
548
|
+
* Operator-tagged sources. After RUA reports start arriving, the operator
|
|
549
|
+
* marks each source IP as known/legit (with a label) or rogue. Tagged
|
|
550
|
+
* sources are excluded from the headline failure count, surfacing only
|
|
551
|
+
* unknown senders.
|
|
552
|
+
*/
|
|
553
|
+
knownSources?: DmarcSourceTag[];
|
|
554
|
+
/**
|
|
555
|
+
* How many days of failure rows to retain. Default 90. Older rows are
|
|
556
|
+
* trimmed by the daily tick to keep the collection bounded.
|
|
557
|
+
*/
|
|
558
|
+
retentionDays?: number;
|
|
559
|
+
}
|
|
560
|
+
interface SndsConfig {
|
|
561
|
+
/**
|
|
562
|
+
* SNDS automated-access key. Obtained per-account from the SNDS portal at
|
|
563
|
+
* https://sendersupport.olc.protection.outlook.com/snds/ once each
|
|
564
|
+
* sending IP has been added and verified.
|
|
565
|
+
*/
|
|
566
|
+
accessKey: string;
|
|
567
|
+
/**
|
|
568
|
+
* Optional IP filter — when set, only rows for these IPs are persisted.
|
|
569
|
+
* Useful when an SNDS account covers a wider IP pool than this mailery
|
|
570
|
+
* deployment uses. Defaults to keeping all rows the API returns.
|
|
571
|
+
*/
|
|
572
|
+
ips?: string[];
|
|
573
|
+
/** Hours between automatic pulls. Default 24. Set to 0 to disable scheduled pulls. */
|
|
574
|
+
intervalHours?: number;
|
|
575
|
+
}
|
|
576
|
+
interface PostmasterConfig {
|
|
577
|
+
/** OAuth client ID from a Google Cloud project with Postmaster Tools API enabled. */
|
|
578
|
+
clientId: string;
|
|
579
|
+
clientSecret: string;
|
|
580
|
+
/**
|
|
581
|
+
* A refresh token obtained via the OAuth consent flow with scope
|
|
582
|
+
* `https://www.googleapis.com/auth/postmaster.readonly` for the user that
|
|
583
|
+
* verified the domain(s) in Postmaster Tools. See
|
|
584
|
+
* docs/guide/deliverability.md → "Google Postmaster Tools" for the
|
|
585
|
+
* one-time setup procedure.
|
|
586
|
+
*/
|
|
587
|
+
refreshToken: string;
|
|
588
|
+
/**
|
|
589
|
+
* Domains to pull data for. When omitted, mailery uses every domain from the
|
|
590
|
+
* senderDomains registry plus the From defaults. Each must be verified in
|
|
591
|
+
* Postmaster Tools under the same Google account.
|
|
592
|
+
*/
|
|
593
|
+
domains?: string[];
|
|
594
|
+
/** Hours between automatic pulls. Default 24. Set to 0 to disable scheduled pulls. */
|
|
595
|
+
intervalHours?: number;
|
|
596
|
+
}
|
|
433
597
|
interface CircuitBreakerThresholds {
|
|
434
598
|
/** Trip when last-hour hard-bounce rate >= this percent (e.g. 2 = 2%). */
|
|
435
599
|
hardBounceRatePctTrip: number;
|
|
@@ -443,6 +607,14 @@ interface MailerConfig {
|
|
|
443
607
|
db: Db;
|
|
444
608
|
collectionPrefix?: string;
|
|
445
609
|
adapter: ContactAdapter;
|
|
610
|
+
/**
|
|
611
|
+
* Optional host-provided template variables. `resolve(contact)` runs at
|
|
612
|
+
* send/preview/test render time; its result is merged into the template
|
|
613
|
+
* context root (schema key `user` → `{{user.name}}`). The zod schema
|
|
614
|
+
* drives admin-editor autocomplete and the `unknown_variable` lint rule.
|
|
615
|
+
* Build one with `defineVars({ schema, resolve })`.
|
|
616
|
+
*/
|
|
617
|
+
varsAdapter?: VarsAdapter<any>;
|
|
446
618
|
/**
|
|
447
619
|
* Queue driver selection. One of:
|
|
448
620
|
* - `{ driver: 'bull', redis: ... }` — BullMQ (default for prod; requires Redis)
|
|
@@ -481,6 +653,41 @@ interface MailerConfig {
|
|
|
481
653
|
/** How long the DOI token stays valid. Default 7 days. */
|
|
482
654
|
doiTokenLifetimeDays?: number;
|
|
483
655
|
circuitBreaker?: Partial<CircuitBreakerThresholds>;
|
|
656
|
+
/**
|
|
657
|
+
* Daily DNS-based block-list checks on sender domains and (optionally) any
|
|
658
|
+
* dedicated sending IPs. Results surface in setup-status and on the Health
|
|
659
|
+
* screen. No external API needed — plain DNS lookups.
|
|
660
|
+
*/
|
|
661
|
+
dnsbl?: DnsblConfig;
|
|
662
|
+
/**
|
|
663
|
+
* Pulls daily reputation tiers + spam ratio + auth pass rates from Google
|
|
664
|
+
* Postmaster Tools. Trips the (domain × marketing) circuit breaker when a
|
|
665
|
+
* domain falls to reputation `BAD`. Only meaningful at >100/day to Gmail.
|
|
666
|
+
*/
|
|
667
|
+
postmaster?: PostmasterConfig;
|
|
668
|
+
/**
|
|
669
|
+
* Pulls daily Outlook/Hotmail/Live IP reputation from Microsoft's Smart
|
|
670
|
+
* Network Data Services. IP-level rather than domain-level — only useful
|
|
671
|
+
* if you send from a dedicated IP. Visibility-only: RED filter results
|
|
672
|
+
* surface as a setup-status error, no auto-trip.
|
|
673
|
+
*/
|
|
674
|
+
snds?: SndsConfig;
|
|
675
|
+
/**
|
|
676
|
+
* Optional Mail-Tester integration. When set, the template editor exposes
|
|
677
|
+
* a "Run deliverability check" button that sends a copy of the rendered
|
|
678
|
+
* draft to a Mail-Tester address and polls for a score (0-10) covering
|
|
679
|
+
* SpamAssassin verdict, auth alignment, content red flags, and link
|
|
680
|
+
* reputation. Score < minScore blocks publish until re-run or overridden.
|
|
681
|
+
*/
|
|
682
|
+
mailTester?: MailTesterConfig;
|
|
683
|
+
/**
|
|
684
|
+
* DMARC RUA aggregate reports tell you who is sending mail that claims to
|
|
685
|
+
* be from your domain — both legitimate sources and spoofers. Operator
|
|
686
|
+
* uploads the report attachments (or POSTs them via SendGrid Inbound Parse
|
|
687
|
+
* to /admin/mailer/api/dmarc/upload). Mailery decompresses, parses, and
|
|
688
|
+
* surfaces alignment failures + unknown senders.
|
|
689
|
+
*/
|
|
690
|
+
dmarc?: DmarcConfig;
|
|
484
691
|
broadcastConfirmationThreshold?: number;
|
|
485
692
|
broadcastEnqueueBatchSize?: number;
|
|
486
693
|
broadcastEnqueueMaxWaiting?: number;
|
|
@@ -611,7 +818,7 @@ interface FlowVersionDoc {
|
|
|
611
818
|
}
|
|
612
819
|
interface FlowRunHistoryEntry {
|
|
613
820
|
stepIndex: number;
|
|
614
|
-
action: 'entered' | 'wait_started' | 'wait_completed' | 'condition_evaluated' | 'branch_taken' | 'sent' | 'send_skipped' | 'tagged' | 'event_fired' | 'webhook_called' | 'exited' | 'failed';
|
|
821
|
+
action: 'entered' | 'wait_started' | 'wait_completed' | 'condition_evaluated' | 'branch_taken' | 'sent' | 'send_skipped' | 'send_deferred' | 'tagged' | 'event_fired' | 'webhook_called' | 'exited' | 'failed';
|
|
615
822
|
at: Date;
|
|
616
823
|
details?: Record<string, unknown>;
|
|
617
824
|
}
|
|
@@ -622,6 +829,17 @@ interface FlowRunDoc {
|
|
|
622
829
|
flowSlug: string;
|
|
623
830
|
flowVersion: number;
|
|
624
831
|
emailAtEntry: string;
|
|
832
|
+
/**
|
|
833
|
+
* Snapshot of the event that triggered this run. Properties surface in
|
|
834
|
+
* templates as `{{event.*}}` and in `varsAdapter.resolve` via
|
|
835
|
+
* `info.eventProperties` — this is how account/topic-scoped flows know
|
|
836
|
+
* which account or topic the run is about. Null for non-event entries.
|
|
837
|
+
*/
|
|
838
|
+
triggerEvent?: {
|
|
839
|
+
name: string;
|
|
840
|
+
properties: Record<string, unknown>;
|
|
841
|
+
occurredAt: Date;
|
|
842
|
+
} | null;
|
|
625
843
|
enteredAt: Date;
|
|
626
844
|
status: FlowRunStatus;
|
|
627
845
|
currentStepIndex: number;
|
|
@@ -834,8 +1052,22 @@ interface WebhookEventDoc {
|
|
|
834
1052
|
processed: boolean;
|
|
835
1053
|
raw: unknown;
|
|
836
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Health bucket. One doc per (senderDomain, kind) pair plus a single aggregate
|
|
1057
|
+
* doc with _id='agg' that rolls up everything.
|
|
1058
|
+
*
|
|
1059
|
+
* _id: 'agg' // cross-domain, cross-kind roll-up
|
|
1060
|
+
* _id: 'd:<senderDomain>|k:<kind>' // per-bucket
|
|
1061
|
+
*
|
|
1062
|
+
* The aggregate is informational only — it never trips. Trips live on
|
|
1063
|
+
* buckets so one subdomain tanking doesn't hold mail for the others.
|
|
1064
|
+
*/
|
|
837
1065
|
interface HealthDoc {
|
|
838
|
-
_id:
|
|
1066
|
+
_id: string;
|
|
1067
|
+
/** null on the aggregate doc, set on bucket docs. */
|
|
1068
|
+
senderDomain: string | null;
|
|
1069
|
+
/** null on the aggregate doc, set on bucket docs. */
|
|
1070
|
+
kind: TemplateKind | null;
|
|
839
1071
|
windowStartedAt: Date;
|
|
840
1072
|
windowDurationMs: number;
|
|
841
1073
|
counters: {
|
|
@@ -859,6 +1091,167 @@ interface HealthDoc {
|
|
|
859
1091
|
manuallyResumedAt: Date | null;
|
|
860
1092
|
updatedAt: Date;
|
|
861
1093
|
}
|
|
1094
|
+
type PostmasterReputation = 'HIGH' | 'MEDIUM' | 'LOW' | 'BAD' | 'REPUTATION_CATEGORY_UNSPECIFIED';
|
|
1095
|
+
/**
|
|
1096
|
+
* One day's traffic stats for a domain from Google Postmaster Tools.
|
|
1097
|
+
* Stored per (domain, date) so we can chart trends.
|
|
1098
|
+
*/
|
|
1099
|
+
interface PostmasterSnapshotDoc {
|
|
1100
|
+
_id?: ObjectId;
|
|
1101
|
+
domain: string;
|
|
1102
|
+
/** Reporting date in YYYY-MM-DD per Postmaster's stat day. */
|
|
1103
|
+
date: string;
|
|
1104
|
+
domainReputation: PostmasterReputation | null;
|
|
1105
|
+
ipReputations: Array<{
|
|
1106
|
+
reputation: PostmasterReputation;
|
|
1107
|
+
ipCount: number;
|
|
1108
|
+
}> | null;
|
|
1109
|
+
userReportedSpamRatio: number | null;
|
|
1110
|
+
spfSuccessRatio: number | null;
|
|
1111
|
+
dkimSuccessRatio: number | null;
|
|
1112
|
+
dmarcSuccessRatio: number | null;
|
|
1113
|
+
outboundEncryptionRatio: number | null;
|
|
1114
|
+
inboundEncryptionRatio: number | null;
|
|
1115
|
+
deliveryErrors: Array<{
|
|
1116
|
+
errorType: string;
|
|
1117
|
+
errorClass: string;
|
|
1118
|
+
errorRatio: number;
|
|
1119
|
+
}> | null;
|
|
1120
|
+
spammyFeedbackLoops: Array<{
|
|
1121
|
+
id: string;
|
|
1122
|
+
spamRatio: number;
|
|
1123
|
+
}> | null;
|
|
1124
|
+
fetchedAt: Date;
|
|
1125
|
+
}
|
|
1126
|
+
interface MailTesterFeedback {
|
|
1127
|
+
category: string;
|
|
1128
|
+
severity: 'info' | 'warning' | 'error';
|
|
1129
|
+
message: string;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Cached Mail-Tester deliverability score for a given (template content)
|
|
1133
|
+
* fingerprint. Operators don't burn a credit re-running on identical
|
|
1134
|
+
* content within cacheHours.
|
|
1135
|
+
*/
|
|
1136
|
+
interface MailTesterScoreDoc {
|
|
1137
|
+
_id?: ObjectId;
|
|
1138
|
+
templateSlug: string;
|
|
1139
|
+
/** Hash of bodyHash + subject + fromEmail — the cache key. */
|
|
1140
|
+
contentKey: string;
|
|
1141
|
+
checkId: string;
|
|
1142
|
+
score: number;
|
|
1143
|
+
feedback: MailTesterFeedback[];
|
|
1144
|
+
rawSummary: string | null;
|
|
1145
|
+
fetchedAt: Date;
|
|
1146
|
+
expiresAt: Date;
|
|
1147
|
+
}
|
|
1148
|
+
type DmarcPolicy = 'none' | 'quarantine' | 'reject';
|
|
1149
|
+
type DmarcAuthResult = 'pass' | 'fail' | 'softfail' | 'neutral' | 'temperror' | 'permerror' | 'none' | 'unknown';
|
|
1150
|
+
/**
|
|
1151
|
+
* One DMARC RUA aggregate report. Receivers (Google, Yahoo, Microsoft, etc.)
|
|
1152
|
+
* email one per day per sending domain. We store one DmarcReportDoc per
|
|
1153
|
+
* incoming report, plus one DmarcFailureDoc per non-aligned record row.
|
|
1154
|
+
*/
|
|
1155
|
+
interface DmarcReportDoc {
|
|
1156
|
+
_id?: ObjectId;
|
|
1157
|
+
reportId: string;
|
|
1158
|
+
/** Reporting org, e.g. 'google.com', 'enterprise.protection.outlook.com'. */
|
|
1159
|
+
orgName: string;
|
|
1160
|
+
/** Reporter contact email from <report_metadata>. */
|
|
1161
|
+
email: string;
|
|
1162
|
+
/** The domain we publish DMARC for, from <policy_published><domain>. */
|
|
1163
|
+
domain: string;
|
|
1164
|
+
policyP: DmarcPolicy;
|
|
1165
|
+
/** % of mail subject to the policy (1-100). */
|
|
1166
|
+
policyPct: number;
|
|
1167
|
+
/** Activity window the report covers. */
|
|
1168
|
+
rangeStart: Date;
|
|
1169
|
+
rangeEnd: Date;
|
|
1170
|
+
/** Sum of <count> across all records. */
|
|
1171
|
+
totalMessages: number;
|
|
1172
|
+
/** Messages that passed DMARC (DKIM aligned OR SPF aligned). */
|
|
1173
|
+
passCount: number;
|
|
1174
|
+
failCount: number;
|
|
1175
|
+
receivedAt: Date;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* One alignment-failing source × day. Composite key `(sourceIp, domain, day)`
|
|
1179
|
+
* lets us roll up "this IP sent N misaligned messages over the past week."
|
|
1180
|
+
* Failures from multiple reports for the same key get counted once per
|
|
1181
|
+
* report (we de-dupe on reportId × sourceIp).
|
|
1182
|
+
*/
|
|
1183
|
+
interface DmarcFailureDoc {
|
|
1184
|
+
_id?: ObjectId;
|
|
1185
|
+
reportId: string;
|
|
1186
|
+
domain: string;
|
|
1187
|
+
sourceIp: string;
|
|
1188
|
+
count: number;
|
|
1189
|
+
headerFrom: string;
|
|
1190
|
+
dkimResult: DmarcAuthResult;
|
|
1191
|
+
spfResult: DmarcAuthResult;
|
|
1192
|
+
dispositionApplied: 'none' | 'quarantine' | 'reject' | string;
|
|
1193
|
+
/** YYYY-MM-DD bucket derived from the reporting window's mid-point. */
|
|
1194
|
+
day: string;
|
|
1195
|
+
receivedAt: Date;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Operator-set DMARC source tag. Created/updated/deleted from the admin UI
|
|
1199
|
+
* so operators can mark "this IP is our SendGrid", "this is Hubspot", etc.
|
|
1200
|
+
* Merged with `MailerConfig.dmarc.knownSources` at read time — the config
|
|
1201
|
+
* version is read-only baseline, the collection is mutable runtime state.
|
|
1202
|
+
*/
|
|
1203
|
+
interface DmarcSourceTagDoc {
|
|
1204
|
+
_id?: ObjectId;
|
|
1205
|
+
ip: string;
|
|
1206
|
+
label: string;
|
|
1207
|
+
ignored: boolean;
|
|
1208
|
+
setBy: string;
|
|
1209
|
+
setAt: Date;
|
|
1210
|
+
}
|
|
1211
|
+
type SndsFilterResult = 'GREEN' | 'YELLOW' | 'RED' | 'UNKNOWN';
|
|
1212
|
+
/**
|
|
1213
|
+
* One row from a Microsoft SNDS data export covering a single activity
|
|
1214
|
+
* window for one sending IP. Stored unique per (ip, activityStart) so
|
|
1215
|
+
* historical windows accumulate without re-writing.
|
|
1216
|
+
*/
|
|
1217
|
+
interface SndsSnapshotDoc {
|
|
1218
|
+
_id?: ObjectId;
|
|
1219
|
+
ip: string;
|
|
1220
|
+
activityStart: Date;
|
|
1221
|
+
activityEnd: Date;
|
|
1222
|
+
rcptCommands: number;
|
|
1223
|
+
dataCommands: number;
|
|
1224
|
+
messageRecipients: number;
|
|
1225
|
+
filterResult: SndsFilterResult;
|
|
1226
|
+
/** 0-1 fraction. SNDS reports as "<0.1%", "0.1-0.3%", etc; we store the upper bound. null when unknown. */
|
|
1227
|
+
complaintRate: number | null;
|
|
1228
|
+
trapMessageCount: number;
|
|
1229
|
+
sampleHelo: string | null;
|
|
1230
|
+
sampleMailFrom: string | null;
|
|
1231
|
+
fetchedAt: Date;
|
|
1232
|
+
}
|
|
1233
|
+
type DnsblResult = 'clean' | 'listed' | 'error';
|
|
1234
|
+
type DnsblTargetKind = 'domain' | 'ip';
|
|
1235
|
+
/**
|
|
1236
|
+
* Latest DNSBL check result per (target, list). Replaced in-place on each run
|
|
1237
|
+
* so the collection stays small. History (if needed later) lives in audit_log.
|
|
1238
|
+
*/
|
|
1239
|
+
interface DnsblCheckDoc {
|
|
1240
|
+
_id?: ObjectId;
|
|
1241
|
+
/** Domain (e.g. `mkt.example.com`) or dotted-quad IP (e.g. `203.0.113.5`). */
|
|
1242
|
+
target: string;
|
|
1243
|
+
targetKind: DnsblTargetKind;
|
|
1244
|
+
/** DNS suffix queried (e.g. `dbl.spamhaus.org`). */
|
|
1245
|
+
list: string;
|
|
1246
|
+
/** Display label for the UI (e.g. `Spamhaus DBL`). */
|
|
1247
|
+
listLabel: string;
|
|
1248
|
+
result: DnsblResult;
|
|
1249
|
+
/** A records returned by the lookup, when listed. */
|
|
1250
|
+
returnCodes: string[];
|
|
1251
|
+
/** When result is 'error', the message; otherwise null. */
|
|
1252
|
+
errorMessage: string | null;
|
|
1253
|
+
runAt: Date;
|
|
1254
|
+
}
|
|
862
1255
|
interface ContactTagDoc {
|
|
863
1256
|
_id?: ObjectId;
|
|
864
1257
|
externalId: string;
|
|
@@ -883,6 +1276,13 @@ interface Collections {
|
|
|
883
1276
|
webhookEvents: Collection<WebhookEventDoc>;
|
|
884
1277
|
health: Collection<HealthDoc>;
|
|
885
1278
|
contactTags: Collection<ContactTagDoc>;
|
|
1279
|
+
dnsblChecks: Collection<DnsblCheckDoc>;
|
|
1280
|
+
postmasterSnapshots: Collection<PostmasterSnapshotDoc>;
|
|
1281
|
+
sndsSnapshots: Collection<SndsSnapshotDoc>;
|
|
1282
|
+
dmarcReports: Collection<DmarcReportDoc>;
|
|
1283
|
+
dmarcFailures: Collection<DmarcFailureDoc>;
|
|
1284
|
+
dmarcSourceTags: Collection<DmarcSourceTagDoc>;
|
|
1285
|
+
mailTesterScores: Collection<MailTesterScoreDoc>;
|
|
886
1286
|
}
|
|
887
1287
|
declare function getCollections(db: Db, prefix?: string): Collections;
|
|
888
1288
|
declare function ensureIndexes(db: Db, prefix?: string): Promise<void>;
|
|
@@ -902,6 +1302,7 @@ declare class EventRegistry {
|
|
|
902
1302
|
register(reg: EventRegistration): void;
|
|
903
1303
|
has(name: string): boolean;
|
|
904
1304
|
policy(name: string): DedupePolicy | undefined;
|
|
1305
|
+
list(): EventRegistration[];
|
|
905
1306
|
/**
|
|
906
1307
|
* Derive a dedupeKey for an event call. Returns null when no policy is
|
|
907
1308
|
* registered AND no key was passed — caller should throw.
|
|
@@ -918,10 +1319,27 @@ interface RunnerContext {
|
|
|
918
1319
|
db: Db;
|
|
919
1320
|
collections: Collections;
|
|
920
1321
|
adapter: ContactAdapter;
|
|
1322
|
+
varsAdapter?: VarsAdapter;
|
|
921
1323
|
providers: Record<string, MailProvider>;
|
|
922
1324
|
queues: Queues;
|
|
923
1325
|
config: ResolvedConfig;
|
|
924
1326
|
handlebarsHelpers?: Record<string, Handlebars.HelperDelegate>;
|
|
1327
|
+
/**
|
|
1328
|
+
* Optional audit-log writer. Available when the runner context is derived
|
|
1329
|
+
* from a Mailer instance (Mailer.getRunnerContext()); absent in some
|
|
1330
|
+
* lightweight test harnesses. Runner code that uses this must tolerate
|
|
1331
|
+
* `undefined`.
|
|
1332
|
+
*/
|
|
1333
|
+
audit?: (entry: {
|
|
1334
|
+
actor: string;
|
|
1335
|
+
action: string;
|
|
1336
|
+
resource: {
|
|
1337
|
+
collection: string;
|
|
1338
|
+
id?: string;
|
|
1339
|
+
slug?: string;
|
|
1340
|
+
};
|
|
1341
|
+
diffSummary?: string;
|
|
1342
|
+
}) => Promise<void>;
|
|
925
1343
|
}
|
|
926
1344
|
|
|
927
1345
|
/**
|
|
@@ -975,6 +1393,33 @@ declare class Mailer {
|
|
|
975
1393
|
suppress(email: string, opts: Omit<SuppressInput, 'email'>): Promise<void>;
|
|
976
1394
|
tag(externalId: string, tag: string): Promise<void>;
|
|
977
1395
|
untag(externalId: string, tag: string): Promise<void>;
|
|
1396
|
+
/**
|
|
1397
|
+
* Abort every active run of one flow for a contact, immediately. Runs parked
|
|
1398
|
+
* in a `wait` exit too — their delayed wake-up jobs find the run exited and
|
|
1399
|
+
* no-op. Also cancels any of the flow's emails still sitting in the send
|
|
1400
|
+
* queue for this contact (queued or awaiting retry), so an abort means no
|
|
1401
|
+
* further mail, not just no further steps.
|
|
1402
|
+
*
|
|
1403
|
+
* No-op (returns zero counts) when nothing is active. Safe to call from the
|
|
1404
|
+
* same handler that processes the business event ("user upgraded").
|
|
1405
|
+
*/
|
|
1406
|
+
abortFlow(flowSlug: string, externalId: string, opts?: {
|
|
1407
|
+
reason?: string;
|
|
1408
|
+
}): Promise<{
|
|
1409
|
+
abortedRuns: number;
|
|
1410
|
+
cancelledSends: number;
|
|
1411
|
+
}>;
|
|
1412
|
+
/**
|
|
1413
|
+
* Abort every active flow run for a contact across all flows. Same semantics
|
|
1414
|
+
* as `abortFlow` — for "stop everything" events (account deleted, churned).
|
|
1415
|
+
*/
|
|
1416
|
+
abortAllFlows(externalId: string, opts?: {
|
|
1417
|
+
reason?: string;
|
|
1418
|
+
}): Promise<{
|
|
1419
|
+
abortedRuns: number;
|
|
1420
|
+
cancelledSends: number;
|
|
1421
|
+
}>;
|
|
1422
|
+
private abortActiveRuns;
|
|
978
1423
|
/**
|
|
979
1424
|
* GDPR right-to-erasure. Hard-deletes the contact's PII and leaves a hashed
|
|
980
1425
|
* suppression row to block re-import. INVARIANT 9.
|
|
@@ -1024,4 +1469,4 @@ declare class NullProvider implements MailProvider {
|
|
|
1024
1469
|
reset(): void;
|
|
1025
1470
|
}
|
|
1026
1471
|
|
|
1027
|
-
export { type AdapterFilter as A, type BroadcastDoc as B, type ContactAdapter as C, type
|
|
1472
|
+
export { getCollections as $, type AdapterFilter as A, type BroadcastDoc as B, type ContactAdapter as C, type DeliveryWindow as D, type EventDoc as E, type FlowStep as F, type SenderDomainValidation as G, type HealthDoc as H, type SubscriptionDoc as I, type SubscriptionStatus as J, type SuppressionDoc as K, type LeadDoc as L, type MailProvider as M, type NormalizedEvent as N, type OutboxDoc as O, type Predicate as P, type SuppressionReason as Q, type RunnerContext as R, type SendArgs as S, type TemplateDoc as T, type TemplateKind as U, type TemplateVersionDoc as V, type VarsAdapter as W, type VarsResolveInfo as X, type WebhookEventDoc as Y, defineVars as Z, ensureIndexes as _, type Contact as a, validateSenderDomain as a0, varsJsonSchema as a1, type SendResult as b, type MailTesterFeedback as c, Mailer as d, type SuppressionScope as e, type SegmentFilter as f, type AuditLogDoc as g, type BroadcastStatus as h, type CircuitBreakerThresholds as i, type Collections as j, type ContactTagDoc as k, type FlowDoc as l, type FlowGoal as m, type FlowRunDoc as n, type FlowRunStatus as o, type FlowVersionDoc as p, type HealthStatus as q, type MailerConfig as r, NullProvider as s, RESERVED_VAR_KEYS as t, type RedisOptions as u, type SegmentDefinition as v, type SendDoc as w, type SendStatus as x, type SenderDomainConfig as y, type SenderDomainRegistry as z };
|