@marlinjai/mail-contract 0.5.0 → 0.6.1
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.mts +8528 -1390
- package/dist/index.d.ts +8528 -1390
- package/dist/index.js +1572 -778
- package/dist/index.mjs +1503 -778
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -175,6 +175,8 @@ __export(index_exports, {
|
|
|
175
175
|
IMPORT_SKIP_REASONS: () => IMPORT_SKIP_REASONS,
|
|
176
176
|
IMPORT_STATUSES: () => IMPORT_STATUSES,
|
|
177
177
|
IMPORT_TERMINAL_STATUSES: () => IMPORT_TERMINAL_STATUSES,
|
|
178
|
+
INSERTION_PROBLEM_REASONS: () => INSERTION_PROBLEM_REASONS,
|
|
179
|
+
INSERTION_TEXT_SLOTS: () => INSERTION_TEXT_SLOTS,
|
|
178
180
|
INVITE_REFUSAL_REASONS: () => INVITE_REFUSAL_REASONS,
|
|
179
181
|
INVITE_STATUSES: () => INVITE_STATUSES,
|
|
180
182
|
Id: () => Id,
|
|
@@ -192,6 +194,13 @@ __export(index_exports, {
|
|
|
192
194
|
ImportStatus: () => ImportStatus,
|
|
193
195
|
ImportWarning: () => ImportWarning,
|
|
194
196
|
ImportedAsset: () => ImportedAsset,
|
|
197
|
+
Insertion: () => Insertion,
|
|
198
|
+
InsertionChoice: () => InsertionChoice,
|
|
199
|
+
InsertionChoiceKey: () => InsertionChoiceKey,
|
|
200
|
+
InsertionChoices: () => InsertionChoices,
|
|
201
|
+
InsertionName: () => InsertionName,
|
|
202
|
+
InsertionText: () => InsertionText,
|
|
203
|
+
Insertions: () => Insertions,
|
|
195
204
|
Invite: () => Invite,
|
|
196
205
|
InviteAccept: () => InviteAccept,
|
|
197
206
|
InviteAccepted: () => InviteAccepted,
|
|
@@ -219,6 +228,9 @@ __export(index_exports, {
|
|
|
219
228
|
MAX_IMPORT_BYTES: () => MAX_IMPORT_BYTES,
|
|
220
229
|
MAX_IMPORT_ROWS: () => MAX_IMPORT_ROWS,
|
|
221
230
|
MAX_IMPORT_URL_LENGTH: () => MAX_IMPORT_URL_LENGTH,
|
|
231
|
+
MAX_INSERTIONS: () => MAX_INSERTIONS,
|
|
232
|
+
MAX_INSERTION_CHOICES: () => MAX_INSERTION_CHOICES,
|
|
233
|
+
MAX_INSERTION_TEXT_LENGTH: () => MAX_INSERTION_TEXT_LENGTH,
|
|
222
234
|
MAX_INVITE_TTL_DAYS: () => MAX_INVITE_TTL_DAYS,
|
|
223
235
|
MAX_IN_LIST: () => MAX_IN_LIST,
|
|
224
236
|
MAX_LETTER_RECIPIENTS: () => MAX_LETTER_RECIPIENTS,
|
|
@@ -291,6 +303,8 @@ __export(index_exports, {
|
|
|
291
303
|
Ok: () => Ok,
|
|
292
304
|
PLAN_FEATURES: () => PLAN_FEATURES,
|
|
293
305
|
PLAN_IDS: () => PLAN_IDS,
|
|
306
|
+
PREVIEW_EMPTY: () => PREVIEW_EMPTY,
|
|
307
|
+
PREVIEW_FILLED: () => PREVIEW_FILLED,
|
|
294
308
|
PROVIDER_KINDS: () => PROVIDER_KINDS,
|
|
295
309
|
PageQuery: () => PageQuery,
|
|
296
310
|
Plan: () => Plan,
|
|
@@ -391,6 +405,8 @@ __export(index_exports, {
|
|
|
391
405
|
TopicCreate: () => TopicCreate,
|
|
392
406
|
TopicUpdate: () => TopicUpdate,
|
|
393
407
|
TrackingSettings: () => TrackingSettings,
|
|
408
|
+
TranslationInsertion: () => TranslationInsertion,
|
|
409
|
+
TranslationInsertions: () => TranslationInsertions,
|
|
394
410
|
UNSUBSCRIBE_PATH_PREFIX: () => UNSUBSCRIBE_PATH_PREFIX,
|
|
395
411
|
UNSUBSCRIBE_SOURCES: () => UNSUBSCRIBE_SOURCES,
|
|
396
412
|
USAGE_METRICS: () => USAGE_METRICS,
|
|
@@ -432,7 +448,10 @@ __export(index_exports, {
|
|
|
432
448
|
WorkspaceSettings: () => WorkspaceSettings,
|
|
433
449
|
WorkspaceUpdate: () => WorkspaceUpdate,
|
|
434
450
|
acceptsIdempotencyKey: () => acceptsIdempotencyKey,
|
|
451
|
+
addInsertion: () => addInsertion,
|
|
452
|
+
addInsertionChoice: () => addInsertionChoice,
|
|
435
453
|
addLanguageDraft: () => addLanguageDraft,
|
|
454
|
+
alignTranslationInsertions: () => alignTranslationInsertions,
|
|
436
455
|
alwaysWaits: () => alwaysWaits,
|
|
437
456
|
automationFilterFamily: () => automationFilterFamily,
|
|
438
457
|
automationRoutes: () => automationRoutes,
|
|
@@ -441,15 +460,27 @@ __export(index_exports, {
|
|
|
441
460
|
buildTemplatePatch: () => buildTemplatePatch,
|
|
442
461
|
canTransition: () => canTransition,
|
|
443
462
|
canonicalLocale: () => canonicalLocale,
|
|
463
|
+
choiceKeyProblem: () => choiceKeyProblem,
|
|
464
|
+
chooseInsertion: () => chooseInsertion,
|
|
444
465
|
cycles: () => cycles,
|
|
466
|
+
defaultPreviewPick: () => defaultPreviewPick,
|
|
445
467
|
defaultSettings: () => defaultSettings,
|
|
468
|
+
demoteForSave: () => demoteForSave,
|
|
469
|
+
demotedBySave: () => demotedBySave,
|
|
470
|
+
describeMissingTexts: () => describeMissingTexts,
|
|
446
471
|
draftDocument: () => draftDocument,
|
|
472
|
+
draftInsertionProblems: () => draftInsertionProblems,
|
|
447
473
|
draftsDirty: () => draftsDirty,
|
|
448
474
|
draftsFromTemplate: () => draftsFromTemplate,
|
|
449
475
|
draftsPatch: () => draftsPatch,
|
|
476
|
+
editInsertionText: () => editInsertionText,
|
|
450
477
|
editLanguageDraft: () => editLanguageDraft,
|
|
478
|
+
editMainInsertionText: () => editMainInsertionText,
|
|
479
|
+
editMainInsertions: () => editMainInsertions,
|
|
480
|
+
effectivePreviewPicks: () => effectivePreviewPicks,
|
|
451
481
|
emptyFlow: () => emptyFlow,
|
|
452
482
|
errorBody: () => errorBody,
|
|
483
|
+
escapeMergeHtml: () => escapeMergeHtml,
|
|
453
484
|
exportFilename: () => exportFilename,
|
|
454
485
|
filterConditions: () => filterConditions,
|
|
455
486
|
filterDepth: () => filterDepth,
|
|
@@ -459,30 +490,65 @@ __export(index_exports, {
|
|
|
459
490
|
formatExportWarningsHeader: () => formatExportWarningsHeader,
|
|
460
491
|
formatUsageWarningHeader: () => formatUsageWarningHeader,
|
|
461
492
|
foundationRoutes: () => foundationRoutes,
|
|
493
|
+
insertionByProblem: () => insertionByProblem,
|
|
494
|
+
insertionChoices: () => insertionChoices,
|
|
495
|
+
insertionCoverage: () => insertionCoverage,
|
|
496
|
+
insertionInputs: () => insertionInputs,
|
|
497
|
+
insertionNameProblem: () => insertionNameProblem,
|
|
498
|
+
insertionProblems: () => insertionProblems,
|
|
499
|
+
insertionRefusalHint: () => insertionRefusalHint,
|
|
500
|
+
insertionSlotLabel: () => insertionSlotLabel,
|
|
501
|
+
insertionTextSlots: () => insertionTextSlots,
|
|
502
|
+
insertionTokens: () => insertionTokens,
|
|
503
|
+
insertionValue: () => insertionValue,
|
|
462
504
|
inviteRoutes: () => inviteRoutes,
|
|
463
505
|
isDayEntirelyPast: () => isDayEntirelyPast,
|
|
464
506
|
isPublishable: () => isPublishable,
|
|
465
507
|
isRetryableError: () => isRetryableError,
|
|
466
508
|
keepLanguageDocument: () => keepLanguageDocument,
|
|
509
|
+
languageInsertions: () => languageInsertions,
|
|
467
510
|
languageName: () => languageName,
|
|
468
511
|
languageTabs: () => languageTabs,
|
|
512
|
+
mainInsertionText: () => mainInsertionText,
|
|
469
513
|
matchLocale: () => matchLocale,
|
|
470
514
|
matchRoute: () => matchRoute,
|
|
515
|
+
missingInsertionTexts: () => missingInsertionTexts,
|
|
471
516
|
missingRequiredMergeFields: () => missingRequiredMergeFields,
|
|
517
|
+
normalizeChoiceKey: () => normalizeChoiceKey,
|
|
472
518
|
nowSeconds: () => nowSeconds,
|
|
473
519
|
operationsWithAccess: () => operationsWithAccess,
|
|
520
|
+
ownEntry: () => ownEntry,
|
|
474
521
|
page: () => page,
|
|
475
522
|
parseExportWarningsHeader: () => parseExportWarningsHeader,
|
|
476
523
|
parseUsageWarningHeader: () => parseUsageWarningHeader,
|
|
524
|
+
personalizeHtml: () => personalizeHtml,
|
|
525
|
+
personalizeText: () => personalizeText,
|
|
526
|
+
placementProblems: () => placementProblems,
|
|
477
527
|
platformRoutes: () => platformRoutes,
|
|
528
|
+
previewControls: () => previewControls,
|
|
529
|
+
previewInsertions: () => previewInsertions,
|
|
530
|
+
previewPersonalization: () => previewPersonalization,
|
|
478
531
|
primaryLanguage: () => primaryLanguage,
|
|
479
532
|
reachable: () => reachable,
|
|
533
|
+
readyWithoutInsertions: () => readyWithoutInsertions,
|
|
480
534
|
readyWithoutSubject: () => readyWithoutSubject,
|
|
481
535
|
rebaseDrafts: () => rebaseDrafts,
|
|
536
|
+
removeInsertion: () => removeInsertion,
|
|
537
|
+
removeInsertionChoice: () => removeInsertionChoice,
|
|
482
538
|
removeLanguageDraft: () => removeLanguageDraft,
|
|
539
|
+
renameInsertion: () => renameInsertion,
|
|
540
|
+
renameInsertionChecked: () => renameInsertionChecked,
|
|
541
|
+
renameInsertionChoice: () => renameInsertionChoice,
|
|
542
|
+
renameInsertionChoiceChecked: () => renameInsertionChoiceChecked,
|
|
543
|
+
resolveInsertions: () => resolveInsertions,
|
|
483
544
|
restorePatch: () => restorePatch,
|
|
484
545
|
routes: () => routes,
|
|
546
|
+
sameJson: () => sameJson,
|
|
547
|
+
sampleMergeValue: () => sampleMergeValue,
|
|
548
|
+
saveDemotions: () => saveDemotions,
|
|
485
549
|
sendingRoutes: () => sendingRoutes,
|
|
550
|
+
setInsertionBy: () => setInsertionBy,
|
|
551
|
+
setInsertionFilled: () => setInsertionFilled,
|
|
486
552
|
setLanguageStatus: () => setLanguageStatus,
|
|
487
553
|
signWebhook: () => signWebhook,
|
|
488
554
|
statusForError: () => statusForError,
|
|
@@ -493,7 +559,10 @@ __export(index_exports, {
|
|
|
493
559
|
testGroupSize: () => testGroupSize,
|
|
494
560
|
tightLoops: () => tightLoops,
|
|
495
561
|
timingSafeEqualString: () => timingSafeEqualString,
|
|
562
|
+
translationInsertionProblems: () => translationInsertionProblems,
|
|
563
|
+
translationTextFromInput: () => translationTextFromInput,
|
|
496
564
|
triggerKey: () => triggerKey,
|
|
565
|
+
unplacedInsertions: () => unplacedInsertions,
|
|
497
566
|
validateAutomation: () => validateAutomation,
|
|
498
567
|
verifyWebhook: () => verifyWebhook
|
|
499
568
|
});
|
|
@@ -646,39 +715,341 @@ var API_VERSION_PREFIX = "/v1";
|
|
|
646
715
|
var HEALTH_PATH = "/healthz";
|
|
647
716
|
|
|
648
717
|
// src/workspace.ts
|
|
649
|
-
var
|
|
718
|
+
var import_zod9 = require("zod");
|
|
650
719
|
|
|
651
720
|
// src/automations.ts
|
|
652
|
-
var
|
|
721
|
+
var import_zod8 = require("zod");
|
|
653
722
|
|
|
654
723
|
// src/templates.ts
|
|
724
|
+
var import_zod4 = require("zod");
|
|
725
|
+
|
|
726
|
+
// src/insertions.ts
|
|
655
727
|
var import_zod3 = require("zod");
|
|
728
|
+
|
|
729
|
+
// src/unsubscribe.ts
|
|
730
|
+
var UNSUBSCRIBE_PATH_PREFIX = "/u/";
|
|
731
|
+
var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
|
|
732
|
+
var RESERVED_MERGE_FIELDS = {
|
|
733
|
+
/** From the recipient's merge values, else the contact. Supports a fallback. */
|
|
734
|
+
first_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
735
|
+
last_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
736
|
+
email: { source: "contact", fallback: false, required_for_broadcast: false },
|
|
737
|
+
/** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
|
|
738
|
+
unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
|
|
739
|
+
};
|
|
740
|
+
var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
|
|
741
|
+
function findMergeFields(html) {
|
|
742
|
+
const uses = [];
|
|
743
|
+
for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
|
|
744
|
+
uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
|
|
745
|
+
}
|
|
746
|
+
return uses;
|
|
747
|
+
}
|
|
748
|
+
function missingRequiredMergeFields(html) {
|
|
749
|
+
const used = new Set(findMergeFields(html).map((u) => u.name));
|
|
750
|
+
return Object.keys(RESERVED_MERGE_FIELDS).filter(
|
|
751
|
+
(name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/insertions.ts
|
|
756
|
+
function ownEntry(record, key) {
|
|
757
|
+
return record != null && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : void 0;
|
|
758
|
+
}
|
|
759
|
+
var MAX_INSERTIONS = 20;
|
|
760
|
+
var MAX_INSERTION_CHOICES = 30;
|
|
761
|
+
var MAX_INSERTION_TEXT_LENGTH = 2e3;
|
|
762
|
+
var NAME = /^[a-z][a-z0-9_]*$/;
|
|
763
|
+
var InsertionName = import_zod3.z.string().max(64).regex(NAME, "a merge-field name: lowercase letters, digits and underscores, starting with a letter");
|
|
764
|
+
var INSERTION_TEXT_SLOTS = ["filled", "otherwise"];
|
|
765
|
+
var InsertionChoiceKey = import_zod3.z.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/, "lowercase letters, digits, hyphens and underscores, up to 64").refine((key) => !INSERTION_TEXT_SLOTS.includes(key), "filled and otherwise name the other two texts, not a choice");
|
|
766
|
+
var InsertionText = import_zod3.z.string().max(MAX_INSERTION_TEXT_LENGTH);
|
|
767
|
+
var Insertion = import_zod3.z.object({
|
|
768
|
+
by: InsertionName,
|
|
769
|
+
choices: import_zod3.z.record(InsertionChoiceKey, InsertionText).refine((c) => Object.keys(c).length <= MAX_INSERTION_CHOICES, `at most ${MAX_INSERTION_CHOICES} choices`),
|
|
770
|
+
filled: InsertionText.nullable(),
|
|
771
|
+
otherwise: InsertionText
|
|
772
|
+
});
|
|
773
|
+
var Insertions = import_zod3.z.record(InsertionName, Insertion).refine((m) => Object.keys(m).length <= MAX_INSERTIONS, `at most ${MAX_INSERTIONS} insertions`);
|
|
774
|
+
var TranslationInsertion = import_zod3.z.object({
|
|
775
|
+
choices: import_zod3.z.record(InsertionChoiceKey, InsertionText.nullable()),
|
|
776
|
+
filled: InsertionText.nullable(),
|
|
777
|
+
otherwise: InsertionText.nullable()
|
|
778
|
+
});
|
|
779
|
+
var TranslationInsertions = import_zod3.z.record(InsertionName, TranslationInsertion);
|
|
780
|
+
var INSERTION_PROBLEM_REASONS = [
|
|
781
|
+
/** A reserved merge field (`first_name`, `email`, ...) cannot be an insertion's name. */
|
|
782
|
+
"insertion_name",
|
|
783
|
+
/** `by` is `unsubscribe_url` or another insertion's name. */
|
|
784
|
+
"insertion_by",
|
|
785
|
+
/** A text places an insertion: texts hold merge fields only, one level. */
|
|
786
|
+
"insertion_text",
|
|
787
|
+
/** `{{opening|...}}`: an insertion's `otherwise` is its fallback, in every language. */
|
|
788
|
+
"insertion_with_fallback",
|
|
789
|
+
/** A language version names an insertion or a choice the main language does not have. */
|
|
790
|
+
"unknown_translation_insertion"
|
|
791
|
+
];
|
|
792
|
+
function insertionTokens(text, insertions) {
|
|
793
|
+
const out = [];
|
|
794
|
+
for (const match of text.matchAll(MERGE_FIELD_PATTERN)) {
|
|
795
|
+
const name = match[1];
|
|
796
|
+
if (Object.prototype.hasOwnProperty.call(insertions, name)) {
|
|
797
|
+
out.push({ name, fallback: match[2] === void 0 ? null : match[2] });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return out;
|
|
801
|
+
}
|
|
802
|
+
function eachText(ins) {
|
|
803
|
+
const texts = [];
|
|
804
|
+
for (const [key, text] of Object.entries(ins.choices)) if (text !== null) texts.push([`choices.${key}`, text]);
|
|
805
|
+
if (ins.filled !== null) texts.push(["filled", ins.filled]);
|
|
806
|
+
if (ins.otherwise !== null) texts.push(["otherwise", ins.otherwise]);
|
|
807
|
+
return texts;
|
|
808
|
+
}
|
|
809
|
+
function insertionProblems(insertions) {
|
|
810
|
+
const problems = [];
|
|
811
|
+
for (const [name, ins] of Object.entries(insertions)) {
|
|
812
|
+
if (Object.prototype.hasOwnProperty.call(RESERVED_MERGE_FIELDS, name)) {
|
|
813
|
+
problems.push({ reason: "insertion_name", path: name, message: `"${name}" is a merge field the service fills; name the insertion something else` });
|
|
814
|
+
}
|
|
815
|
+
if (ins.by === "unsubscribe_url" || Object.prototype.hasOwnProperty.call(insertions, ins.by)) {
|
|
816
|
+
problems.push({ reason: "insertion_by", path: `${name}.by`, message: `"${ins.by}" cannot choose an insertion: use a value the client or the contact provides` });
|
|
817
|
+
}
|
|
818
|
+
for (const [where, text] of eachText(ins)) {
|
|
819
|
+
if (insertionTokens(text, insertions).length > 0) {
|
|
820
|
+
problems.push({ reason: "insertion_text", path: `${name}.${where}`, message: "an insertion text can hold merge fields, not other insertions" });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return problems;
|
|
825
|
+
}
|
|
826
|
+
function translationInsertionProblems(main, translation) {
|
|
827
|
+
const problems = [];
|
|
828
|
+
for (const [name, ins] of Object.entries(translation)) {
|
|
829
|
+
const def = ownEntry(main, name);
|
|
830
|
+
if (!def) {
|
|
831
|
+
problems.push({ reason: "unknown_translation_insertion", path: name, message: `the main language has no insertion "${name}"` });
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
for (const key of Object.keys(ins.choices)) {
|
|
835
|
+
if (!Object.prototype.hasOwnProperty.call(def.choices, key)) {
|
|
836
|
+
problems.push({ reason: "unknown_translation_insertion", path: `${name}.choices.${key}`, message: `"${name}" has no choice "${key}" in the main language` });
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
for (const [where, text] of eachText(ins)) {
|
|
840
|
+
if (insertionTokens(text, main).length > 0) {
|
|
841
|
+
problems.push({ reason: "insertion_text", path: `${name}.${where}`, message: "an insertion text can hold merge fields, not other insertions" });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return problems;
|
|
846
|
+
}
|
|
847
|
+
function placementProblems(texts, insertions) {
|
|
848
|
+
const problems = [];
|
|
849
|
+
for (const [where, text] of Object.entries(texts)) {
|
|
850
|
+
if (!text) continue;
|
|
851
|
+
for (const use of insertionTokens(text, insertions)) {
|
|
852
|
+
if (use.fallback !== null) {
|
|
853
|
+
problems.push({
|
|
854
|
+
reason: "insertion_with_fallback",
|
|
855
|
+
path: where,
|
|
856
|
+
message: `{{${use.name}|...}}: an insertion takes no fallback, its "otherwise" text is the fallback`
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return problems;
|
|
862
|
+
}
|
|
863
|
+
function unplacedInsertions(texts, insertions) {
|
|
864
|
+
const placed = /* @__PURE__ */ new Set();
|
|
865
|
+
for (const text of texts) if (text) for (const use of insertionTokens(text, insertions)) placed.add(use.name);
|
|
866
|
+
return Object.keys(insertions).filter((name) => !placed.has(name));
|
|
867
|
+
}
|
|
868
|
+
function insertionCoverage(main, translation) {
|
|
869
|
+
const missing = [];
|
|
870
|
+
for (const [name, def] of Object.entries(main)) {
|
|
871
|
+
const t = ownEntry(translation, name);
|
|
872
|
+
for (const key of Object.keys(def.choices)) {
|
|
873
|
+
const text = ownEntry(t?.choices, key);
|
|
874
|
+
if (typeof text !== "string") missing.push({ insertion: name, choice: key });
|
|
875
|
+
}
|
|
876
|
+
if (def.filled !== null && typeof t?.filled !== "string") missing.push({ insertion: name, choice: "filled" });
|
|
877
|
+
if (typeof t?.otherwise !== "string") missing.push({ insertion: name, choice: "otherwise" });
|
|
878
|
+
}
|
|
879
|
+
return missing;
|
|
880
|
+
}
|
|
881
|
+
function alignTranslationInsertions(main, translation) {
|
|
882
|
+
const out = {};
|
|
883
|
+
for (const [name, def] of Object.entries(main)) {
|
|
884
|
+
const t = ownEntry(translation, name);
|
|
885
|
+
const choices = {};
|
|
886
|
+
for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t?.choices, key) ?? null;
|
|
887
|
+
out[name] = { choices, filled: def.filled === null ? null : t?.filled ?? null, otherwise: t?.otherwise ?? null };
|
|
888
|
+
}
|
|
889
|
+
return out;
|
|
890
|
+
}
|
|
891
|
+
function languageInsertions(main, translation) {
|
|
892
|
+
if (insertionCoverage(main, translation).length > 0) return null;
|
|
893
|
+
const out = {};
|
|
894
|
+
for (const [name, def] of Object.entries(main)) {
|
|
895
|
+
const t = ownEntry(translation, name);
|
|
896
|
+
const choices = {};
|
|
897
|
+
for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t.choices, key);
|
|
898
|
+
out[name] = { by: def.by, choices, filled: def.filled === null ? null : t.filled, otherwise: t.otherwise };
|
|
899
|
+
}
|
|
900
|
+
return out;
|
|
901
|
+
}
|
|
902
|
+
function insertionInputs(insertions) {
|
|
903
|
+
const out = {};
|
|
904
|
+
for (const [name, def] of Object.entries(insertions)) {
|
|
905
|
+
const entry = ownEntry(out, def.by) ?? (out[def.by] = { insertions: [], keys: [], filled: false });
|
|
906
|
+
entry.insertions.push(name);
|
|
907
|
+
for (const key of Object.keys(def.choices)) if (!entry.keys.includes(key)) entry.keys.push(key);
|
|
908
|
+
if (def.filled !== null) entry.filled = true;
|
|
909
|
+
}
|
|
910
|
+
return out;
|
|
911
|
+
}
|
|
912
|
+
function insertionValue(raw) {
|
|
913
|
+
if (typeof raw === "string") return raw.trim() === "" ? null : raw.trim();
|
|
914
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return String(raw);
|
|
915
|
+
if (raw === true) return "true";
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
var InsertionChoice = import_zod3.z.object({
|
|
919
|
+
value: import_zod3.z.string().nullable(),
|
|
920
|
+
chosen: import_zod3.z.string(),
|
|
921
|
+
matched: import_zod3.z.boolean()
|
|
922
|
+
});
|
|
923
|
+
var InsertionChoices = import_zod3.z.record(InsertionName, InsertionChoice);
|
|
924
|
+
function chooseInsertion(insertion, raw) {
|
|
925
|
+
const value = insertionValue(raw);
|
|
926
|
+
if (value !== null) {
|
|
927
|
+
const key = value.toLowerCase();
|
|
928
|
+
if (Object.prototype.hasOwnProperty.call(insertion.choices, key)) {
|
|
929
|
+
return { value, chosen: key, matched: true, text: insertion.choices[key] };
|
|
930
|
+
}
|
|
931
|
+
if (insertion.filled !== null) return { value, chosen: "filled", matched: true, text: insertion.filled };
|
|
932
|
+
return { value, chosen: "otherwise", matched: false, text: insertion.otherwise };
|
|
933
|
+
}
|
|
934
|
+
return { value: null, chosen: "otherwise", matched: true, text: insertion.otherwise };
|
|
935
|
+
}
|
|
936
|
+
function resolveInsertions(insertions, raw) {
|
|
937
|
+
const out = {};
|
|
938
|
+
for (const [name, def] of Object.entries(insertions)) out[name] = chooseInsertion(def, raw(def.by));
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
function insertionChoices(resolved) {
|
|
942
|
+
const out = {};
|
|
943
|
+
for (const [name, r] of Object.entries(resolved)) out[name] = { value: r.value, chosen: r.chosen, matched: r.matched };
|
|
944
|
+
return out;
|
|
945
|
+
}
|
|
946
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
947
|
+
function escapeMergeHtml(value) {
|
|
948
|
+
return value.replace(/[&<>"']/g, (ch) => ESCAPES[ch]);
|
|
949
|
+
}
|
|
950
|
+
function pattern() {
|
|
951
|
+
return new RegExp(MERGE_FIELD_PATTERN.source, "g");
|
|
952
|
+
}
|
|
953
|
+
function insertionHtml(text, p) {
|
|
954
|
+
let out = "";
|
|
955
|
+
let last = 0;
|
|
956
|
+
for (const match of text.matchAll(pattern())) {
|
|
957
|
+
out += escapeMergeHtml(text.slice(last, match.index));
|
|
958
|
+
out += escapeMergeHtml(p.value(match[1], match[2]));
|
|
959
|
+
last = match.index + match[0].length;
|
|
960
|
+
}
|
|
961
|
+
out += escapeMergeHtml(text.slice(last));
|
|
962
|
+
return out.trim() === "" ? "" : out.replace(/\r\n|\r|\n/g, "<br>");
|
|
963
|
+
}
|
|
964
|
+
function insertionPlain(text, p) {
|
|
965
|
+
return text.replace(pattern(), (_m, name, fallback) => p.value(name, fallback));
|
|
966
|
+
}
|
|
967
|
+
function tokenHtml(name, fallback, p) {
|
|
968
|
+
const ins = ownEntry(p.insertions, name);
|
|
969
|
+
if (ins) return insertionHtml(ins.text, p);
|
|
970
|
+
return escapeMergeHtml(p.value(name, fallback));
|
|
971
|
+
}
|
|
972
|
+
var FIELD_ONLY_PARAGRAPH = new RegExp(
|
|
973
|
+
String.raw`<(p|div|li|h[1-6])(\s[^>]*)?>((?:\s| | |<br\s*\/?>|<\/?(?:strong|b|em|i|u|span|font|a)(?:\s[^>]*)?>|\{\{\s*[a-z][a-z0-9_]*\s*(?:\|[^}]*)?\}\})*)<\/\1>`,
|
|
974
|
+
"g"
|
|
975
|
+
);
|
|
976
|
+
var TEXT_BLOCK_ROW = /<tr>\s*<td\b[^>]*\bclass="[^"]*\bel-text\b[^"]*"[^>]*>([\s\S]*?)<\/td>\s*<\/tr>/g;
|
|
977
|
+
function hasVisibleContent(html) {
|
|
978
|
+
if (/<(img|hr|video|svg|input|iframe|table)\b/i.test(html)) return true;
|
|
979
|
+
const text = html.replace(/<!--[\s\S]*?-->/g, "").replace(/<[^>]*>/g, "").replace(/ | | /g, "").trim();
|
|
980
|
+
return text !== "";
|
|
981
|
+
}
|
|
982
|
+
function dropEmptyParagraphs(html, p) {
|
|
983
|
+
return html.replace(FIELD_ONLY_PARAGRAPH, (whole, _tag, _attrs, content) => {
|
|
984
|
+
const tokens = [...content.matchAll(pattern())];
|
|
985
|
+
if (tokens.length === 0) return whole;
|
|
986
|
+
return tokens.every((m) => tokenHtml(m[1], m[2], p).trim() === "") ? "" : whole;
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
function personalizeHtml(html, p) {
|
|
990
|
+
let out = html.replace(TEXT_BLOCK_ROW, (whole, inner) => {
|
|
991
|
+
if (!pattern().test(inner)) return whole;
|
|
992
|
+
const kept = dropEmptyParagraphs(inner, p);
|
|
993
|
+
const filled = kept.replace(pattern(), (_m, name, fallback) => tokenHtml(name, fallback, p));
|
|
994
|
+
return hasVisibleContent(filled) ? whole : "";
|
|
995
|
+
});
|
|
996
|
+
out = dropEmptyParagraphs(out, p);
|
|
997
|
+
return out.replace(pattern(), (_m, name, fallback) => tokenHtml(name, fallback, p));
|
|
998
|
+
}
|
|
999
|
+
var CONTROL = new RegExp("[\\u0000-\\u001f\\u007f]+", "g");
|
|
1000
|
+
function personalizeText(text, p) {
|
|
1001
|
+
return text.replace(pattern(), (_m, name, fallback) => {
|
|
1002
|
+
const ins = ownEntry(p.insertions, name);
|
|
1003
|
+
return ins ? insertionPlain(ins.text, p) : p.value(name, fallback);
|
|
1004
|
+
}).replace(CONTROL, " ").replace(/ {2,}/g, " ").trim();
|
|
1005
|
+
}
|
|
1006
|
+
function sampleMergeValue(values) {
|
|
1007
|
+
return (name, fallback) => {
|
|
1008
|
+
const v = ownEntry(values, name);
|
|
1009
|
+
const text = typeof v === "string" ? v : typeof v === "number" && Number.isFinite(v) ? String(v) : typeof v === "boolean" ? String(v) : null;
|
|
1010
|
+
if (text === null || text.trim() === "") return fallback?.trim() ?? "";
|
|
1011
|
+
return text;
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// src/templates.ts
|
|
656
1016
|
var DOCUMENT_SCHEMA_VERSIONS = ["1.0", "1.1"];
|
|
657
|
-
var DocumentSchemaVersion =
|
|
658
|
-
var TemplateDocument =
|
|
1017
|
+
var DocumentSchemaVersion = import_zod4.z.enum(DOCUMENT_SCHEMA_VERSIONS);
|
|
1018
|
+
var TemplateDocument = import_zod4.z.object({
|
|
659
1019
|
version: DocumentSchemaVersion,
|
|
660
|
-
metadata:
|
|
661
|
-
sections:
|
|
1020
|
+
metadata: import_zod4.z.record(import_zod4.z.unknown()),
|
|
1021
|
+
sections: import_zod4.z.array(import_zod4.z.record(import_zod4.z.unknown()))
|
|
662
1022
|
}).passthrough();
|
|
663
1023
|
var MAX_DOCUMENT_BYTES = 1e6;
|
|
664
1024
|
var MAX_SUBJECT_LENGTH = 998;
|
|
665
1025
|
var MAX_PREHEADER_LENGTH = 500;
|
|
666
|
-
var TemplateSubject =
|
|
667
|
-
var TemplatePreheader =
|
|
1026
|
+
var TemplateSubject = import_zod4.z.string().max(MAX_SUBJECT_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
1027
|
+
var TemplatePreheader = import_zod4.z.string().max(MAX_PREHEADER_LENGTH).regex(/^[^\x00-\x1f\x7f]*$/, "one line, without control characters");
|
|
668
1028
|
var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
|
|
669
|
-
var TemplateLanguageStatus =
|
|
670
|
-
var TemplateTranslation =
|
|
1029
|
+
var TemplateLanguageStatus = import_zod4.z.enum(TEMPLATE_LANGUAGE_STATUSES);
|
|
1030
|
+
var TemplateTranslation = import_zod4.z.object({
|
|
671
1031
|
subject: TemplateSubject.nullable(),
|
|
672
1032
|
preheader: TemplatePreheader.nullable(),
|
|
673
1033
|
document: TemplateDocument,
|
|
674
|
-
status: TemplateLanguageStatus
|
|
1034
|
+
status: TemplateLanguageStatus,
|
|
1035
|
+
/**
|
|
1036
|
+
* This language's texts for the main language's insertions
|
|
1037
|
+
* (docs/plans/2026-09-25-translated-insertions.md). The service always
|
|
1038
|
+
* returns it, aligned to the main language's structure (a text not written
|
|
1039
|
+
* yet is null). In a save, leaving it out keeps the stored texts, so a
|
|
1040
|
+
* client that does not know insertions never erases them. A `ready` version
|
|
1041
|
+
* needs every text: marking one ready without is `ready_needs_insertions`,
|
|
1042
|
+
* and a save that leaves a ready version without one (the main language
|
|
1043
|
+
* added a choice) sets it back to `draft`.
|
|
1044
|
+
*/
|
|
1045
|
+
insertions: TranslationInsertions.optional()
|
|
675
1046
|
});
|
|
676
1047
|
var MAX_TEMPLATE_TRANSLATIONS = 19;
|
|
677
|
-
var TemplateTranslations =
|
|
678
|
-
var Template =
|
|
1048
|
+
var TemplateTranslations = import_zod4.z.record(LocaleTag, TemplateTranslation).refine((t) => Object.keys(t).length <= MAX_TEMPLATE_TRANSLATIONS, `at most ${MAX_TEMPLATE_TRANSLATIONS} language versions`);
|
|
1049
|
+
var Template = import_zod4.z.object({
|
|
679
1050
|
id: Id,
|
|
680
|
-
name:
|
|
681
|
-
description:
|
|
1051
|
+
name: import_zod4.z.string().min(1).max(200),
|
|
1052
|
+
description: import_zod4.z.string().max(2e3).nullable(),
|
|
682
1053
|
/**
|
|
683
1054
|
* The main language: what `subject`, `preheader` and `document` are written
|
|
684
1055
|
* in, and what every recipient without a ready version of their own gets.
|
|
@@ -695,114 +1066,132 @@ var Template = import_zod3.z.object({
|
|
|
695
1066
|
* lists is kept, but no send uses it until the language is listed again.
|
|
696
1067
|
*/
|
|
697
1068
|
translations: TemplateTranslations,
|
|
1069
|
+
/**
|
|
1070
|
+
* The main language's insertions: named sentences chosen per recipient by
|
|
1071
|
+
* one merge value, placed as `{{name}}` (see `insertions.ts`). Their
|
|
1072
|
+
* structure is the main language's; every other language holds its own
|
|
1073
|
+
* texts in `translations[tag].insertions`.
|
|
1074
|
+
*/
|
|
1075
|
+
insertions: Insertions,
|
|
698
1076
|
/** Increments on every saved change, of any language. */
|
|
699
|
-
version:
|
|
700
|
-
thumbnail_url:
|
|
1077
|
+
version: import_zod4.z.number().int().min(1),
|
|
1078
|
+
thumbnail_url: import_zod4.z.string().url().nullable(),
|
|
701
1079
|
archived_at: Timestamp.nullable(),
|
|
702
1080
|
created_at: Timestamp,
|
|
703
1081
|
updated_at: Timestamp
|
|
704
1082
|
});
|
|
705
|
-
var TemplateSummary = Template.omit({ document: true, translations: true }).extend({
|
|
706
|
-
languages:
|
|
1083
|
+
var TemplateSummary = Template.omit({ document: true, translations: true, insertions: true }).extend({
|
|
1084
|
+
languages: import_zod4.z.record(LocaleTag, TemplateLanguageStatus),
|
|
1085
|
+
/** The names of the template's insertions, sorted alphabetically. */
|
|
1086
|
+
insertion_names: import_zod4.z.array(import_zod4.z.string())
|
|
707
1087
|
});
|
|
708
|
-
var TemplateCreate =
|
|
709
|
-
name:
|
|
710
|
-
description:
|
|
1088
|
+
var TemplateCreate = import_zod4.z.object({
|
|
1089
|
+
name: import_zod4.z.string().min(1).max(200),
|
|
1090
|
+
description: import_zod4.z.string().max(2e3).optional(),
|
|
711
1091
|
locale: LocaleTag.optional(),
|
|
712
1092
|
subject: TemplateSubject.nullable().optional(),
|
|
713
1093
|
preheader: TemplatePreheader.nullable().optional(),
|
|
714
1094
|
document: TemplateDocument,
|
|
715
|
-
translations: TemplateTranslations.optional()
|
|
1095
|
+
translations: TemplateTranslations.optional(),
|
|
1096
|
+
insertions: Insertions.optional()
|
|
716
1097
|
});
|
|
717
|
-
var TemplateUpdate =
|
|
718
|
-
base_version:
|
|
719
|
-
name:
|
|
720
|
-
description:
|
|
1098
|
+
var TemplateUpdate = import_zod4.z.object({
|
|
1099
|
+
base_version: import_zod4.z.number().int().min(1),
|
|
1100
|
+
name: import_zod4.z.string().min(1).max(200).optional(),
|
|
1101
|
+
description: import_zod4.z.string().max(2e3).nullable().optional(),
|
|
721
1102
|
locale: LocaleTag.optional(),
|
|
722
1103
|
subject: TemplateSubject.nullable().optional(),
|
|
723
1104
|
preheader: TemplatePreheader.nullable().optional(),
|
|
724
1105
|
document: TemplateDocument.optional(),
|
|
725
|
-
translations:
|
|
726
|
-
|
|
1106
|
+
translations: import_zod4.z.record(LocaleTag, TemplateTranslation.nullable()).optional(),
|
|
1107
|
+
/**
|
|
1108
|
+
* The main language's insertions, whole: what is left out is removed, from
|
|
1109
|
+
* every language in the same save. A rename is a removal and an addition,
|
|
1110
|
+
* so an editor carries each language's texts to the new name in the same
|
|
1111
|
+
* save (it sends those languages in `translations`).
|
|
1112
|
+
*/
|
|
1113
|
+
insertions: Insertions.optional(),
|
|
1114
|
+
archived: import_zod4.z.boolean().optional()
|
|
727
1115
|
}).refine(
|
|
728
|
-
(v) => v.name !== void 0 || v.description !== void 0 || v.locale !== void 0 || v.subject !== void 0 || v.preheader !== void 0 || v.document !== void 0 || v.translations !== void 0 || v.archived !== void 0,
|
|
1116
|
+
(v) => v.name !== void 0 || v.description !== void 0 || v.locale !== void 0 || v.subject !== void 0 || v.preheader !== void 0 || v.document !== void 0 || v.translations !== void 0 || v.insertions !== void 0 || v.archived !== void 0,
|
|
729
1117
|
"at least one field besides base_version"
|
|
730
1118
|
);
|
|
731
|
-
var SavedSectionBody =
|
|
1119
|
+
var SavedSectionBody = import_zod4.z.record(import_zod4.z.unknown());
|
|
732
1120
|
var MAX_SAVED_SECTIONS = 50;
|
|
733
|
-
var SavedSection =
|
|
1121
|
+
var SavedSection = import_zod4.z.object({
|
|
734
1122
|
id: Id,
|
|
735
|
-
name:
|
|
736
|
-
description:
|
|
1123
|
+
name: import_zod4.z.string().min(1).max(200),
|
|
1124
|
+
description: import_zod4.z.string().max(2e3).nullable(),
|
|
737
1125
|
section: SavedSectionBody,
|
|
738
|
-
created_by:
|
|
1126
|
+
created_by: import_zod4.z.string().nullable(),
|
|
739
1127
|
created_at: Timestamp,
|
|
740
1128
|
updated_at: Timestamp
|
|
741
1129
|
});
|
|
742
|
-
var SavedSectionCreate =
|
|
743
|
-
name:
|
|
744
|
-
description:
|
|
1130
|
+
var SavedSectionCreate = import_zod4.z.object({
|
|
1131
|
+
name: import_zod4.z.string().min(1).max(200),
|
|
1132
|
+
description: import_zod4.z.string().max(2e3).optional(),
|
|
745
1133
|
section: SavedSectionBody
|
|
746
1134
|
});
|
|
747
|
-
var SavedSectionUpdate =
|
|
748
|
-
name:
|
|
749
|
-
description:
|
|
1135
|
+
var SavedSectionUpdate = import_zod4.z.object({
|
|
1136
|
+
name: import_zod4.z.string().min(1).max(200).optional(),
|
|
1137
|
+
description: import_zod4.z.string().max(2e3).nullable().optional(),
|
|
750
1138
|
section: SavedSectionBody.optional()
|
|
751
1139
|
}).refine((v) => v.name !== void 0 || v.description !== void 0 || v.section !== void 0, "at least one field");
|
|
752
1140
|
var TemplateListQuery = PageQuery.extend({
|
|
753
|
-
archived:
|
|
1141
|
+
archived: import_zod4.z.enum(["true", "false"]).optional()
|
|
754
1142
|
});
|
|
755
|
-
var TemplateVersion =
|
|
1143
|
+
var TemplateVersion = import_zod4.z.object({
|
|
756
1144
|
template_id: Id,
|
|
757
|
-
version:
|
|
1145
|
+
version: import_zod4.z.number().int().min(1),
|
|
758
1146
|
locale: LocaleTag,
|
|
759
1147
|
subject: TemplateSubject.nullable(),
|
|
760
1148
|
preheader: TemplatePreheader.nullable(),
|
|
761
1149
|
document: TemplateDocument,
|
|
762
1150
|
translations: TemplateTranslations,
|
|
763
|
-
|
|
1151
|
+
insertions: Insertions,
|
|
1152
|
+
created_by: import_zod4.z.string().nullable(),
|
|
764
1153
|
created_at: Timestamp
|
|
765
1154
|
});
|
|
766
|
-
var TemplateVersionParams =
|
|
1155
|
+
var TemplateVersionParams = import_zod4.z.object({
|
|
767
1156
|
id: Id,
|
|
768
|
-
version:
|
|
1157
|
+
version: import_zod4.z.coerce.number().int().min(1)
|
|
769
1158
|
});
|
|
770
|
-
var CompileMessage =
|
|
771
|
-
message:
|
|
1159
|
+
var CompileMessage = import_zod4.z.object({
|
|
1160
|
+
message: import_zod4.z.string().min(1),
|
|
772
1161
|
/** Where in the document or the MJML the message points, when known. */
|
|
773
|
-
path:
|
|
774
|
-
line:
|
|
1162
|
+
path: import_zod4.z.string().optional(),
|
|
1163
|
+
line: import_zod4.z.number().int().min(1).optional()
|
|
775
1164
|
});
|
|
776
|
-
var CompileResult =
|
|
777
|
-
mjml:
|
|
778
|
-
html:
|
|
779
|
-
warnings:
|
|
780
|
-
errors:
|
|
1165
|
+
var CompileResult = import_zod4.z.object({
|
|
1166
|
+
mjml: import_zod4.z.string(),
|
|
1167
|
+
html: import_zod4.z.string(),
|
|
1168
|
+
warnings: import_zod4.z.array(CompileMessage),
|
|
1169
|
+
errors: import_zod4.z.array(CompileMessage)
|
|
781
1170
|
});
|
|
782
|
-
var TemplateCompileRequest =
|
|
783
|
-
version:
|
|
1171
|
+
var TemplateCompileRequest = import_zod4.z.object({
|
|
1172
|
+
version: import_zod4.z.number().int().min(1).optional(),
|
|
784
1173
|
locale: LocaleTag.optional()
|
|
785
1174
|
});
|
|
786
|
-
var CompileRequest =
|
|
1175
|
+
var CompileRequest = import_zod4.z.object({
|
|
787
1176
|
document: TemplateDocument
|
|
788
1177
|
});
|
|
789
1178
|
var ASSET_CONTENT_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
790
|
-
var AssetContentType =
|
|
1179
|
+
var AssetContentType = import_zod4.z.enum(ASSET_CONTENT_TYPES);
|
|
791
1180
|
var MAX_ASSET_BYTES = 10 * 1024 * 1024;
|
|
792
|
-
var Asset =
|
|
1181
|
+
var Asset = import_zod4.z.object({
|
|
793
1182
|
id: Id,
|
|
794
|
-
url:
|
|
1183
|
+
url: import_zod4.z.string().url(),
|
|
795
1184
|
content_type: AssetContentType,
|
|
796
|
-
size_bytes:
|
|
797
|
-
width:
|
|
798
|
-
height:
|
|
799
|
-
filename:
|
|
1185
|
+
size_bytes: import_zod4.z.number().int().min(1).max(MAX_ASSET_BYTES),
|
|
1186
|
+
width: import_zod4.z.number().int().min(1).nullable(),
|
|
1187
|
+
height: import_zod4.z.number().int().min(1).nullable(),
|
|
1188
|
+
filename: import_zod4.z.string().min(1).max(255),
|
|
800
1189
|
created_at: Timestamp
|
|
801
1190
|
});
|
|
802
1191
|
var ASSET_UPLOAD_FIELD = "file";
|
|
803
1192
|
var MAX_IMPORT_URL_LENGTH = 2048;
|
|
804
|
-
var AssetImport =
|
|
805
|
-
url:
|
|
1193
|
+
var AssetImport = import_zod4.z.object({
|
|
1194
|
+
url: import_zod4.z.string().max(MAX_IMPORT_URL_LENGTH).url().refine((u) => {
|
|
806
1195
|
try {
|
|
807
1196
|
const url = new URL(u);
|
|
808
1197
|
if (url.protocol === "https:") return true;
|
|
@@ -812,7 +1201,7 @@ var AssetImport = import_zod3.z.object({
|
|
|
812
1201
|
}
|
|
813
1202
|
}, "must be https (http only for localhost)"),
|
|
814
1203
|
/** The stored file name; derived from the address when omitted. */
|
|
815
|
-
filename:
|
|
1204
|
+
filename: import_zod4.z.string().min(1).max(255).optional()
|
|
816
1205
|
});
|
|
817
1206
|
var MAX_MJML_IMPORT_BYTES = 512 * 1024;
|
|
818
1207
|
var MAX_MJML_DEPTH = 32;
|
|
@@ -827,56 +1216,56 @@ var MJML_IMPORT_REFUSALS = [
|
|
|
827
1216
|
"too_complex",
|
|
828
1217
|
"invalid_document"
|
|
829
1218
|
];
|
|
830
|
-
var ImportWarning =
|
|
831
|
-
severity:
|
|
1219
|
+
var ImportWarning = import_zod4.z.object({
|
|
1220
|
+
severity: import_zod4.z.enum(["info", "warning"]),
|
|
832
1221
|
/** Stable kind, e.g. `kept_as_html`, `unknown_component`, `remote_image_not_imported`. */
|
|
833
|
-
code:
|
|
1222
|
+
code: import_zod4.z.string().min(1).max(64),
|
|
834
1223
|
/** Where in the source, e.g. `mj-body > mj-section[2] > mj-column[1] > mj-social[1]`. */
|
|
835
|
-
path:
|
|
836
|
-
line:
|
|
837
|
-
message:
|
|
838
|
-
fragment:
|
|
1224
|
+
path: import_zod4.z.string(),
|
|
1225
|
+
line: import_zod4.z.number().int().min(1).optional(),
|
|
1226
|
+
message: import_zod4.z.string().min(1),
|
|
1227
|
+
fragment: import_zod4.z.string().optional()
|
|
839
1228
|
});
|
|
840
|
-
var RemoteImage =
|
|
841
|
-
url:
|
|
1229
|
+
var RemoteImage = import_zod4.z.object({
|
|
1230
|
+
url: import_zod4.z.string().min(1),
|
|
842
1231
|
/** The element and attribute it is loaded from, e.g. `<img src>`. */
|
|
843
|
-
where:
|
|
1232
|
+
where: import_zod4.z.string()
|
|
844
1233
|
});
|
|
845
|
-
var ImportedAsset =
|
|
846
|
-
source_url:
|
|
1234
|
+
var ImportedAsset = import_zod4.z.object({
|
|
1235
|
+
source_url: import_zod4.z.string().min(1),
|
|
847
1236
|
asset_id: Id,
|
|
848
|
-
url:
|
|
1237
|
+
url: import_zod4.z.string().url()
|
|
849
1238
|
});
|
|
850
|
-
var mjmlSource =
|
|
851
|
-
var TemplateImportPreviewRequest =
|
|
852
|
-
var TemplateImportPreview =
|
|
1239
|
+
var mjmlSource = import_zod4.z.string().min(1, "paste or upload the MJML");
|
|
1240
|
+
var TemplateImportPreviewRequest = import_zod4.z.object({ mjml: mjmlSource });
|
|
1241
|
+
var TemplateImportPreview = import_zod4.z.object({
|
|
853
1242
|
document: TemplateDocument,
|
|
854
|
-
warnings:
|
|
1243
|
+
warnings: import_zod4.z.array(ImportWarning),
|
|
855
1244
|
/** The document compiled under the workspace's asset policy, exactly as a send would. */
|
|
856
1245
|
compiled: CompileResult,
|
|
857
1246
|
/** Images loaded from outside the service; under `service_only` each is also a compile error. */
|
|
858
|
-
remote_images:
|
|
859
|
-
asset_policy:
|
|
1247
|
+
remote_images: import_zod4.z.array(RemoteImage),
|
|
1248
|
+
asset_policy: import_zod4.z.enum(["any", "service_only"])
|
|
860
1249
|
});
|
|
861
|
-
var TemplateImport =
|
|
862
|
-
name:
|
|
863
|
-
description:
|
|
1250
|
+
var TemplateImport = import_zod4.z.object({
|
|
1251
|
+
name: import_zod4.z.string().min(1).max(200),
|
|
1252
|
+
description: import_zod4.z.string().max(2e3).optional(),
|
|
864
1253
|
mjml: mjmlSource,
|
|
865
|
-
import_remote_assets:
|
|
1254
|
+
import_remote_assets: import_zod4.z.boolean().optional()
|
|
866
1255
|
});
|
|
867
|
-
var TemplateImportResult =
|
|
1256
|
+
var TemplateImportResult = import_zod4.z.object({
|
|
868
1257
|
template: Template,
|
|
869
|
-
warnings:
|
|
870
|
-
imported_assets:
|
|
1258
|
+
warnings: import_zod4.z.array(ImportWarning),
|
|
1259
|
+
imported_assets: import_zod4.z.array(ImportedAsset)
|
|
871
1260
|
});
|
|
872
1261
|
var EXPORT_FORMATS = ["mjml", "html"];
|
|
873
|
-
var ExportFormat =
|
|
874
|
-
var TemplateExportQuery =
|
|
1262
|
+
var ExportFormat = import_zod4.z.enum(EXPORT_FORMATS);
|
|
1263
|
+
var TemplateExportQuery = import_zod4.z.object({
|
|
875
1264
|
format: ExportFormat,
|
|
876
|
-
version:
|
|
1265
|
+
version: import_zod4.z.coerce.number().int().min(1).optional(),
|
|
877
1266
|
locale: LocaleTag.optional()
|
|
878
1267
|
});
|
|
879
|
-
var MailingExportQuery =
|
|
1268
|
+
var MailingExportQuery = import_zod4.z.object({ format: ExportFormat });
|
|
880
1269
|
var EXPORT_CONTENT_TYPES = {
|
|
881
1270
|
mjml: "text/plain; charset=utf-8",
|
|
882
1271
|
html: "text/html; charset=utf-8"
|
|
@@ -895,7 +1284,7 @@ function formatExportWarningsHeader(messages) {
|
|
|
895
1284
|
function parseExportWarningsHeader(value) {
|
|
896
1285
|
if (!value) return [];
|
|
897
1286
|
try {
|
|
898
|
-
const parsed =
|
|
1287
|
+
const parsed = import_zod4.z.array(CompileMessage).safeParse(JSON.parse(decodeURIComponent(value)));
|
|
899
1288
|
return parsed.success ? parsed.data : [];
|
|
900
1289
|
} catch {
|
|
901
1290
|
return [];
|
|
@@ -907,32 +1296,32 @@ function exportFilename(name, format) {
|
|
|
907
1296
|
}
|
|
908
1297
|
|
|
909
1298
|
// src/platform.ts
|
|
910
|
-
var
|
|
1299
|
+
var import_zod7 = require("zod");
|
|
911
1300
|
|
|
912
1301
|
// src/mailings.ts
|
|
913
|
-
var
|
|
1302
|
+
var import_zod6 = require("zod");
|
|
914
1303
|
|
|
915
1304
|
// src/ab-test.ts
|
|
916
|
-
var
|
|
917
|
-
var AbVariant =
|
|
918
|
-
key:
|
|
919
|
-
subject:
|
|
1305
|
+
var import_zod5 = require("zod");
|
|
1306
|
+
var AbVariant = import_zod5.z.object({
|
|
1307
|
+
key: import_zod5.z.string().regex(/^[a-z]$/, "a single lowercase letter"),
|
|
1308
|
+
subject: import_zod5.z.string().min(1).max(998).optional(),
|
|
920
1309
|
/** Replaces the mailing's document for this variant. */
|
|
921
|
-
document:
|
|
1310
|
+
document: import_zod5.z.record(import_zod5.z.unknown()).optional(),
|
|
922
1311
|
/**
|
|
923
1312
|
* Keeps the document this variant key already has in the mailing's current
|
|
924
1313
|
* test, so a test can be changed without sending every document again. The
|
|
925
1314
|
* service refuses it (`validation_failed`) when that variant has none.
|
|
926
1315
|
*/
|
|
927
|
-
keep_document:
|
|
1316
|
+
keep_document: import_zod5.z.literal(true).optional()
|
|
928
1317
|
});
|
|
929
1318
|
var AB_WINNER_METRICS = ["opens", "clicks", "manual"];
|
|
930
|
-
var AbTestConfig =
|
|
931
|
-
variants:
|
|
1319
|
+
var AbTestConfig = import_zod5.z.object({
|
|
1320
|
+
variants: import_zod5.z.array(AbVariant).min(2).max(5),
|
|
932
1321
|
/** Share of recipients in the test group, split evenly across variants; the rest get the winner. */
|
|
933
|
-
test_fraction:
|
|
934
|
-
winner_metric:
|
|
935
|
-
decide_after_minutes:
|
|
1322
|
+
test_fraction: import_zod5.z.number().gt(0).lte(1),
|
|
1323
|
+
winner_metric: import_zod5.z.enum(AB_WINNER_METRICS),
|
|
1324
|
+
decide_after_minutes: import_zod5.z.number().int().min(15).max(10080).optional()
|
|
936
1325
|
}).refine((c) => new Set(c.variants.map((v) => v.key)).size === c.variants.length, "variant keys must be unique").refine((c) => c.variants.every((v) => v.subject !== void 0 || v.document !== void 0 || v.keep_document === true), {
|
|
937
1326
|
message: "every variant changes the subject, the document or both",
|
|
938
1327
|
path: ["variants"]
|
|
@@ -944,19 +1333,19 @@ var AbTestConfig = import_zod4.z.object({
|
|
|
944
1333
|
path: ["decide_after_minutes"]
|
|
945
1334
|
});
|
|
946
1335
|
var AB_TEST_STATUSES = ["pending", "testing", "awaiting_pick", "decided"];
|
|
947
|
-
var AbTestState =
|
|
948
|
-
variants:
|
|
949
|
-
test_fraction:
|
|
950
|
-
winner_metric:
|
|
951
|
-
decide_after_minutes:
|
|
952
|
-
status:
|
|
1336
|
+
var AbTestState = import_zod5.z.object({
|
|
1337
|
+
variants: import_zod5.z.array(import_zod5.z.object({ key: import_zod5.z.string(), subject: import_zod5.z.string().nullable(), has_document: import_zod5.z.boolean() })),
|
|
1338
|
+
test_fraction: import_zod5.z.number(),
|
|
1339
|
+
winner_metric: import_zod5.z.enum(AB_WINNER_METRICS),
|
|
1340
|
+
decide_after_minutes: import_zod5.z.number().int().nullable(),
|
|
1341
|
+
status: import_zod5.z.enum(AB_TEST_STATUSES),
|
|
953
1342
|
/** When the metric decides; null for manual or before the start. */
|
|
954
1343
|
decide_at: Timestamp.nullable(),
|
|
955
|
-
winner:
|
|
956
|
-
decided_by:
|
|
1344
|
+
winner: import_zod5.z.string().nullable(),
|
|
1345
|
+
decided_by: import_zod5.z.enum(["metric", "manual"]).nullable(),
|
|
957
1346
|
decided_at: Timestamp.nullable()
|
|
958
1347
|
});
|
|
959
|
-
var AbWinnerRequest =
|
|
1348
|
+
var AbWinnerRequest = import_zod5.z.object({ variant: import_zod5.z.string().regex(/^[a-z]$/) });
|
|
960
1349
|
function testGroupSize(total, fraction, variants) {
|
|
961
1350
|
return Math.min(total, Math.max(variants, Math.ceil(total * fraction)));
|
|
962
1351
|
}
|
|
@@ -971,9 +1360,9 @@ var MAILING_STATUSES = [
|
|
|
971
1360
|
"partially_failed",
|
|
972
1361
|
"cancelled"
|
|
973
1362
|
];
|
|
974
|
-
var MailingStatus =
|
|
1363
|
+
var MailingStatus = import_zod6.z.enum(MAILING_STATUSES);
|
|
975
1364
|
var MAILING_ACTIONS = ["send", "schedule", "unschedule", "pause", "resume", "cancel", "retry-failed"];
|
|
976
|
-
var MailingAction =
|
|
1365
|
+
var MailingAction = import_zod6.z.enum(MAILING_ACTIONS);
|
|
977
1366
|
var MAILING_TRANSITIONS = {
|
|
978
1367
|
draft: ["send", "schedule", "cancel"],
|
|
979
1368
|
scheduled: ["send", "schedule", "unschedule", "cancel"],
|
|
@@ -988,23 +1377,23 @@ function canTransition(status, action) {
|
|
|
988
1377
|
}
|
|
989
1378
|
var EDITABLE_MAILING_STATUSES = ["draft", "scheduled"];
|
|
990
1379
|
var TERMINAL_MAILING_STATUSES = ["sent", "cancelled"];
|
|
991
|
-
var MailingMetadata =
|
|
992
|
-
var MailingCounts =
|
|
993
|
-
total:
|
|
994
|
-
queued:
|
|
995
|
-
sending:
|
|
996
|
-
sent:
|
|
997
|
-
failed:
|
|
998
|
-
skipped:
|
|
1380
|
+
var MailingMetadata = import_zod6.z.record(import_zod6.z.string().max(500)).refine((v) => Object.keys(v).length <= 20, "at most 20 keys").refine((v) => Object.keys(v).every((k) => k.length >= 1 && k.length <= 64), "keys are 1 to 64 characters");
|
|
1381
|
+
var MailingCounts = import_zod6.z.object({
|
|
1382
|
+
total: import_zod6.z.number().int().min(0),
|
|
1383
|
+
queued: import_zod6.z.number().int().min(0),
|
|
1384
|
+
sending: import_zod6.z.number().int().min(0),
|
|
1385
|
+
sent: import_zod6.z.number().int().min(0),
|
|
1386
|
+
failed: import_zod6.z.number().int().min(0),
|
|
1387
|
+
skipped: import_zod6.z.number().int().min(0)
|
|
999
1388
|
});
|
|
1000
1389
|
var MAILING_KINDS = ["broadcast", "automation", "notification"];
|
|
1001
|
-
var MailingKind =
|
|
1002
|
-
var Mailing =
|
|
1390
|
+
var MailingKind = import_zod6.z.enum(MAILING_KINDS);
|
|
1391
|
+
var Mailing = import_zod6.z.object({
|
|
1003
1392
|
id: Id,
|
|
1004
1393
|
kind: MailingKind,
|
|
1005
|
-
name:
|
|
1006
|
-
subject:
|
|
1007
|
-
preheader:
|
|
1394
|
+
name: import_zod6.z.string().max(200).nullable(),
|
|
1395
|
+
subject: import_zod6.z.string().min(1).max(998),
|
|
1396
|
+
preheader: import_zod6.z.string().max(500).nullable(),
|
|
1008
1397
|
/**
|
|
1009
1398
|
* The template the document was taken from, if any. The snapshot is what is
|
|
1010
1399
|
* sent in the main language; the template's other language versions are
|
|
@@ -1049,15 +1438,15 @@ var Mailing = import_zod5.z.object({
|
|
|
1049
1438
|
* recipients refused as dead addresses in one run (see the provider's
|
|
1050
1439
|
* `rejections.anomaly`). Resuming clears it and starts a new run.
|
|
1051
1440
|
*/
|
|
1052
|
-
pause_reason:
|
|
1441
|
+
pause_reason: import_zod6.z.string().nullable().default(null),
|
|
1053
1442
|
created_at: Timestamp,
|
|
1054
1443
|
updated_at: Timestamp
|
|
1055
1444
|
});
|
|
1056
1445
|
var MailingSummary = Mailing.omit({ document: true });
|
|
1057
1446
|
var MailingContent = {
|
|
1058
|
-
name:
|
|
1059
|
-
subject:
|
|
1060
|
-
preheader:
|
|
1447
|
+
name: import_zod6.z.string().max(200).optional(),
|
|
1448
|
+
subject: import_zod6.z.string().min(1).max(998),
|
|
1449
|
+
preheader: import_zod6.z.string().max(500).optional(),
|
|
1061
1450
|
/** See `Mailing.send_locale`. */
|
|
1062
1451
|
send_locale: LocaleTag.nullable().optional(),
|
|
1063
1452
|
/**
|
|
@@ -1070,7 +1459,7 @@ var MailingContent = {
|
|
|
1070
1459
|
provider_id: Id,
|
|
1071
1460
|
metadata: MailingMetadata.optional()
|
|
1072
1461
|
};
|
|
1073
|
-
var MailingCreate =
|
|
1462
|
+
var MailingCreate = import_zod6.z.object({
|
|
1074
1463
|
...MailingContent,
|
|
1075
1464
|
subject: MailingContent.subject.optional(),
|
|
1076
1465
|
document: TemplateDocument.optional(),
|
|
@@ -1082,10 +1471,10 @@ var MailingCreate = import_zod5.z.object({
|
|
|
1082
1471
|
message: "a mailing made from a document needs a subject",
|
|
1083
1472
|
path: ["subject"]
|
|
1084
1473
|
});
|
|
1085
|
-
var MailingUpdate =
|
|
1086
|
-
name:
|
|
1474
|
+
var MailingUpdate = import_zod6.z.object({
|
|
1475
|
+
name: import_zod6.z.string().max(200).nullable().optional(),
|
|
1087
1476
|
subject: MailingContent.subject.optional(),
|
|
1088
|
-
preheader:
|
|
1477
|
+
preheader: import_zod6.z.string().max(500).nullable().optional(),
|
|
1089
1478
|
/** Null turns a draft into a letter, which it can only be with at most one recipient. */
|
|
1090
1479
|
topic: Slug.nullable().optional(),
|
|
1091
1480
|
send_locale: LocaleTag.nullable().optional(),
|
|
@@ -1099,30 +1488,30 @@ var MailingListQuery = PageQuery.extend({
|
|
|
1099
1488
|
kind: MailingKind.optional(),
|
|
1100
1489
|
automation_id: Id.optional()
|
|
1101
1490
|
});
|
|
1102
|
-
var MailingSendRequest =
|
|
1103
|
-
var MailingRetryFailedRequest =
|
|
1491
|
+
var MailingSendRequest = import_zod6.z.object({}).strict();
|
|
1492
|
+
var MailingRetryFailedRequest = import_zod6.z.object({
|
|
1104
1493
|
/**
|
|
1105
1494
|
* Also requeue recipients whose outcome is unknown after a crash. They may
|
|
1106
1495
|
* already have received the message, so this is a human's decision, never a
|
|
1107
1496
|
* default.
|
|
1108
1497
|
*/
|
|
1109
|
-
include_outcome_unknown:
|
|
1498
|
+
include_outcome_unknown: import_zod6.z.boolean().optional()
|
|
1110
1499
|
}).strict();
|
|
1111
|
-
var MailingActionRequest =
|
|
1112
|
-
var MailingTestRequest =
|
|
1500
|
+
var MailingActionRequest = import_zod6.z.object({}).strict();
|
|
1501
|
+
var MailingTestRequest = import_zod6.z.object({
|
|
1113
1502
|
to: Email,
|
|
1114
1503
|
merge: Properties.optional(),
|
|
1115
1504
|
locale: LocaleTag.optional()
|
|
1116
1505
|
});
|
|
1117
|
-
var MailingTestResult =
|
|
1506
|
+
var MailingTestResult = import_zod6.z.object({
|
|
1118
1507
|
message_id: Id,
|
|
1119
|
-
provider_message_id:
|
|
1508
|
+
provider_message_id: import_zod6.z.string().nullable()
|
|
1120
1509
|
});
|
|
1121
1510
|
var RECIPIENT_STATUSES = ["queued", "sending", "sent", "failed", "skipped"];
|
|
1122
|
-
var RecipientStatus =
|
|
1511
|
+
var RecipientStatus = import_zod6.z.enum(RECIPIENT_STATUSES);
|
|
1123
1512
|
var SKIP_REASONS = ["suppressed", "not_subscribed", "contact_erased", "cancelled", "outcome_unknown"];
|
|
1124
|
-
var SkipReason =
|
|
1125
|
-
var Recipient =
|
|
1513
|
+
var SkipReason = import_zod6.z.enum(SKIP_REASONS);
|
|
1514
|
+
var Recipient = import_zod6.z.object({
|
|
1126
1515
|
id: Id,
|
|
1127
1516
|
mailing_id: Id,
|
|
1128
1517
|
contact_id: Id.nullable(),
|
|
@@ -1130,75 +1519,98 @@ var Recipient = import_zod5.z.object({
|
|
|
1130
1519
|
merge: Properties,
|
|
1131
1520
|
status: RecipientStatus,
|
|
1132
1521
|
skip_reason: SkipReason.nullable(),
|
|
1133
|
-
attempts:
|
|
1522
|
+
attempts: import_zod6.z.number().int().min(0),
|
|
1134
1523
|
message_id: Id.nullable(),
|
|
1135
|
-
last_error:
|
|
1524
|
+
last_error: import_zod6.z.string().nullable(),
|
|
1136
1525
|
created_at: Timestamp,
|
|
1137
1526
|
updated_at: Timestamp
|
|
1138
1527
|
});
|
|
1139
1528
|
var MAX_RECIPIENTS_PER_BATCH = 1e3;
|
|
1140
1529
|
var MAX_LETTER_RECIPIENTS = 1;
|
|
1141
1530
|
var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
|
|
1142
|
-
var RecipientInput =
|
|
1531
|
+
var RecipientInput = import_zod6.z.object({
|
|
1143
1532
|
contact_id: Id.optional(),
|
|
1144
|
-
external_id:
|
|
1533
|
+
external_id: import_zod6.z.string().min(1).max(255).optional(),
|
|
1145
1534
|
email: Email.optional(),
|
|
1146
1535
|
merge: Properties.optional()
|
|
1147
1536
|
}).refine((v) => v.contact_id !== void 0 || v.external_id !== void 0 || v.email !== void 0, {
|
|
1148
1537
|
message: "contact_id, external_id or email is required",
|
|
1149
1538
|
path: ["email"]
|
|
1150
1539
|
});
|
|
1151
|
-
var RecipientBatch =
|
|
1152
|
-
recipients:
|
|
1540
|
+
var RecipientBatch = import_zod6.z.object({
|
|
1541
|
+
recipients: import_zod6.z.array(RecipientInput).min(1).max(MAX_RECIPIENTS_PER_BATCH)
|
|
1153
1542
|
});
|
|
1154
1543
|
var RECIPIENT_REJECTIONS = ["unknown_contact", "duplicate_in_batch"];
|
|
1155
|
-
var RecipientBatchResult =
|
|
1544
|
+
var RecipientBatchResult = import_zod6.z.object({
|
|
1156
1545
|
/** Newly queued. */
|
|
1157
|
-
added:
|
|
1546
|
+
added: import_zod6.z.number().int().min(0),
|
|
1158
1547
|
/** Already on the mailing from an earlier batch: left unchanged. */
|
|
1159
|
-
already_present:
|
|
1548
|
+
already_present: import_zod6.z.number().int().min(0),
|
|
1160
1549
|
/** Items not added, by their index in the request. */
|
|
1161
|
-
rejected:
|
|
1550
|
+
rejected: import_zod6.z.array(import_zod6.z.object({ index: import_zod6.z.number().int().min(0), reason: import_zod6.z.enum(RECIPIENT_REJECTIONS) }))
|
|
1162
1551
|
});
|
|
1163
1552
|
var RecipientListQuery = PageQuery.extend({
|
|
1164
1553
|
status: RecipientStatus.optional(),
|
|
1165
1554
|
skip_reason: SkipReason.optional()
|
|
1166
1555
|
});
|
|
1167
1556
|
var MESSAGE_OUTCOMES = ["sent", "failed"];
|
|
1168
|
-
var MessageOutcome =
|
|
1169
|
-
var MessageSummary =
|
|
1557
|
+
var MessageOutcome = import_zod6.z.enum(MESSAGE_OUTCOMES);
|
|
1558
|
+
var MessageSummary = import_zod6.z.object({
|
|
1170
1559
|
id: Id,
|
|
1171
1560
|
mailing_id: Id.nullable(),
|
|
1172
1561
|
recipient_id: Id.nullable(),
|
|
1173
1562
|
contact_id: Id.nullable(),
|
|
1174
1563
|
to: Email,
|
|
1175
|
-
subject:
|
|
1564
|
+
subject: import_zod6.z.string(),
|
|
1176
1565
|
provider_id: Id,
|
|
1177
|
-
provider_message_id:
|
|
1566
|
+
provider_message_id: import_zod6.z.string().nullable(),
|
|
1178
1567
|
outcome: MessageOutcome,
|
|
1179
|
-
error:
|
|
1180
|
-
is_test:
|
|
1181
|
-
recipient_count:
|
|
1568
|
+
error: import_zod6.z.string().nullable(),
|
|
1569
|
+
is_test: import_zod6.z.boolean(),
|
|
1570
|
+
recipient_count: import_zod6.z.number().int().min(1),
|
|
1182
1571
|
/** The language version the message was sent in; null for messages archived before languages existed. */
|
|
1183
1572
|
locale: LocaleTag.nullable(),
|
|
1573
|
+
/**
|
|
1574
|
+
* The choice each of the template's insertions made for this recipient
|
|
1575
|
+
* (`{ opening: { value: "venue", chosen: "venue", matched: true } }`);
|
|
1576
|
+
* `matched: false` is a filled value no choice took, sent with the
|
|
1577
|
+
* "otherwise" text. Null when the message had no insertions.
|
|
1578
|
+
*/
|
|
1579
|
+
insertions: InsertionChoices.nullable(),
|
|
1184
1580
|
created_at: Timestamp
|
|
1185
1581
|
});
|
|
1186
|
-
var Message = MessageSummary.extend({ html:
|
|
1187
|
-
var MailingLanguageStatus =
|
|
1188
|
-
var MailingLanguage =
|
|
1582
|
+
var Message = MessageSummary.extend({ html: import_zod6.z.string() });
|
|
1583
|
+
var MailingLanguageStatus = import_zod6.z.enum(["main", "ready", "draft", "not_a_workspace_language"]);
|
|
1584
|
+
var MailingLanguage = import_zod6.z.object({
|
|
1189
1585
|
locale: LocaleTag,
|
|
1190
1586
|
status: MailingLanguageStatus,
|
|
1191
1587
|
/** Queued recipients who will get this version. */
|
|
1192
|
-
planned:
|
|
1588
|
+
planned: import_zod6.z.number().int().min(0),
|
|
1193
1589
|
/** Messages already sent in this language (tests excluded). */
|
|
1194
|
-
sent:
|
|
1590
|
+
sent: import_zod6.z.number().int().min(0)
|
|
1195
1591
|
});
|
|
1196
|
-
var MailingLanguageReport =
|
|
1592
|
+
var MailingLanguageReport = import_zod6.z.object({
|
|
1197
1593
|
main_locale: LocaleTag,
|
|
1198
1594
|
send_locale: LocaleTag.nullable(),
|
|
1199
|
-
languages:
|
|
1200
|
-
fallback:
|
|
1201
|
-
no_language:
|
|
1595
|
+
languages: import_zod6.z.array(MailingLanguage),
|
|
1596
|
+
fallback: import_zod6.z.array(import_zod6.z.object({ wanted: import_zod6.z.string(), planned: import_zod6.z.number().int().min(1) })),
|
|
1597
|
+
no_language: import_zod6.z.number().int().min(0),
|
|
1598
|
+
/**
|
|
1599
|
+
* Values that no choice of an insertion takes, so the "otherwise" text goes
|
|
1600
|
+
* out (docs/plans/2026-09-25-translated-insertions.md, decision 3): per
|
|
1601
|
+
* insertion and value, the queued recipients who send it (`planned`) and the
|
|
1602
|
+
* messages that went out with it (`sent`). A client whose form offers the
|
|
1603
|
+
* template's keys never produces one; a non-empty list means the client and
|
|
1604
|
+
* the template have drifted apart.
|
|
1605
|
+
*/
|
|
1606
|
+
insertion_misses: import_zod6.z.array(
|
|
1607
|
+
import_zod6.z.object({
|
|
1608
|
+
insertion: import_zod6.z.string(),
|
|
1609
|
+
value: import_zod6.z.string(),
|
|
1610
|
+
planned: import_zod6.z.number().int().min(0),
|
|
1611
|
+
sent: import_zod6.z.number().int().min(0)
|
|
1612
|
+
})
|
|
1613
|
+
)
|
|
1202
1614
|
});
|
|
1203
1615
|
var MessageListQuery = PageQuery.extend({
|
|
1204
1616
|
mailing_id: Id.optional(),
|
|
@@ -1206,26 +1618,26 @@ var MessageListQuery = PageQuery.extend({
|
|
|
1206
1618
|
});
|
|
1207
1619
|
|
|
1208
1620
|
// src/platform.ts
|
|
1209
|
-
var Tag =
|
|
1621
|
+
var Tag = import_zod7.z.object({
|
|
1210
1622
|
id: Id,
|
|
1211
1623
|
slug: Slug,
|
|
1212
|
-
name:
|
|
1213
|
-
contact_count:
|
|
1624
|
+
name: import_zod7.z.string().min(1).max(120),
|
|
1625
|
+
contact_count: import_zod7.z.number().int().min(0),
|
|
1214
1626
|
created_at: Timestamp
|
|
1215
1627
|
});
|
|
1216
|
-
var TagCreate =
|
|
1217
|
-
var TagAssignment =
|
|
1218
|
-
contact_ids:
|
|
1628
|
+
var TagCreate = import_zod7.z.object({ slug: Slug, name: import_zod7.z.string().min(1).max(120) });
|
|
1629
|
+
var TagAssignment = import_zod7.z.object({
|
|
1630
|
+
contact_ids: import_zod7.z.array(Id).min(1).max(1e3)
|
|
1219
1631
|
});
|
|
1220
1632
|
var CONTACT_PROPERTY_TYPES = ["string", "number", "boolean", "date"];
|
|
1221
|
-
var ContactPropertyType =
|
|
1222
|
-
var ContactPropertyKey =
|
|
1223
|
-
var ContactPropertyDefinition =
|
|
1633
|
+
var ContactPropertyType = import_zod7.z.enum(CONTACT_PROPERTY_TYPES);
|
|
1634
|
+
var ContactPropertyKey = import_zod7.z.string().regex(/^[A-Za-z0-9_.-]{1,64}$/, "letters, digits, dot, underscore and hyphen, 1 to 64");
|
|
1635
|
+
var ContactPropertyDefinition = import_zod7.z.object({
|
|
1224
1636
|
key: ContactPropertyKey,
|
|
1225
|
-
label:
|
|
1637
|
+
label: import_zod7.z.string().min(1).max(120),
|
|
1226
1638
|
type: ContactPropertyType
|
|
1227
1639
|
});
|
|
1228
|
-
var ContactPropertyParams =
|
|
1640
|
+
var ContactPropertyParams = import_zod7.z.object({ key: ContactPropertyKey });
|
|
1229
1641
|
var FILTER_OPERATORS = [
|
|
1230
1642
|
"eq",
|
|
1231
1643
|
"neq",
|
|
@@ -1241,8 +1653,8 @@ var FILTER_OPERATORS = [
|
|
|
1241
1653
|
"in",
|
|
1242
1654
|
"not_in"
|
|
1243
1655
|
];
|
|
1244
|
-
var FilterOperator =
|
|
1245
|
-
var FilterField =
|
|
1656
|
+
var FilterOperator = import_zod7.z.enum(FILTER_OPERATORS);
|
|
1657
|
+
var FilterField = import_zod7.z.string().regex(
|
|
1246
1658
|
/^(email|first_name|last_name|locale|created_at|tag|topic|engagement:(opened|clicked)|property:[A-Za-z0-9_.-]{1,64})$/,
|
|
1247
1659
|
"unknown filter field"
|
|
1248
1660
|
);
|
|
@@ -1258,18 +1670,18 @@ var FILTER_FIELD_OPERATORS = {
|
|
|
1258
1670
|
property: FILTER_OPERATORS
|
|
1259
1671
|
};
|
|
1260
1672
|
var MAX_FILTER_DEPTH = 5;
|
|
1261
|
-
var FilterCondition =
|
|
1673
|
+
var FilterCondition = import_zod7.z.object({ field: FilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
|
|
1262
1674
|
message: "exists and not_exists take no value; every other operator needs one",
|
|
1263
1675
|
path: ["value"]
|
|
1264
1676
|
}).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
|
|
1265
1677
|
message: "in and not_in take an array",
|
|
1266
1678
|
path: ["value"]
|
|
1267
1679
|
});
|
|
1268
|
-
var SegmentFilter =
|
|
1269
|
-
() =>
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1680
|
+
var SegmentFilter = import_zod7.z.lazy(
|
|
1681
|
+
() => import_zod7.z.union([
|
|
1682
|
+
import_zod7.z.object({ and: import_zod7.z.array(SegmentFilter).min(1).max(50) }).strict(),
|
|
1683
|
+
import_zod7.z.object({ or: import_zod7.z.array(SegmentFilter).min(1).max(50) }).strict(),
|
|
1684
|
+
import_zod7.z.object({ not: SegmentFilter }).strict(),
|
|
1273
1685
|
FilterCondition
|
|
1274
1686
|
])
|
|
1275
1687
|
);
|
|
@@ -1357,42 +1769,42 @@ function untypedValueProblem(c, every) {
|
|
|
1357
1769
|
if (orderOp) return every(isFilterNumber, "numbers (define the property to compare dates)");
|
|
1358
1770
|
return every((v) => isScalar(v) || v === null, "strings, numbers, booleans or null");
|
|
1359
1771
|
}
|
|
1360
|
-
var Segment =
|
|
1772
|
+
var Segment = import_zod7.z.object({
|
|
1361
1773
|
id: Id,
|
|
1362
|
-
name:
|
|
1774
|
+
name: import_zod7.z.string().min(1).max(120),
|
|
1363
1775
|
filter: SegmentFilter,
|
|
1364
1776
|
/** Computed on read. */
|
|
1365
|
-
contact_count:
|
|
1777
|
+
contact_count: import_zod7.z.number().int().min(0),
|
|
1366
1778
|
created_at: Timestamp,
|
|
1367
1779
|
updated_at: Timestamp
|
|
1368
1780
|
});
|
|
1369
|
-
var SegmentCreate =
|
|
1370
|
-
var SegmentPreviewRequest =
|
|
1371
|
-
var SegmentPreview =
|
|
1372
|
-
contact_count:
|
|
1781
|
+
var SegmentCreate = import_zod7.z.object({ name: import_zod7.z.string().min(1).max(120), filter: BoundedSegmentFilter });
|
|
1782
|
+
var SegmentPreviewRequest = import_zod7.z.object({ filter: BoundedSegmentFilter });
|
|
1783
|
+
var SegmentPreview = import_zod7.z.object({
|
|
1784
|
+
contact_count: import_zod7.z.number().int().min(0),
|
|
1373
1785
|
/** Up to five matching contacts, for a human to sanity-check the filter. */
|
|
1374
|
-
sample:
|
|
1786
|
+
sample: import_zod7.z.array(import_zod7.z.object({ id: Id, email: Email })).max(5)
|
|
1375
1787
|
});
|
|
1376
|
-
var MailingAudienceFromSegment =
|
|
1788
|
+
var MailingAudienceFromSegment = import_zod7.z.object({ segment_id: Id });
|
|
1377
1789
|
var SIGNUP_FORM_FIELDS = ["first_name", "last_name"];
|
|
1378
1790
|
var HOSTED_PAGE_LOCALES = ["en", "de", "it", "fr", "es"];
|
|
1379
|
-
var SignupFormTranslation =
|
|
1380
|
-
title:
|
|
1381
|
-
consent_text:
|
|
1791
|
+
var SignupFormTranslation = import_zod7.z.object({
|
|
1792
|
+
title: import_zod7.z.string().min(1).max(120),
|
|
1793
|
+
consent_text: import_zod7.z.string().min(1).max(2e3)
|
|
1382
1794
|
});
|
|
1383
|
-
var SignupForm =
|
|
1795
|
+
var SignupForm = import_zod7.z.object({
|
|
1384
1796
|
id: Id,
|
|
1385
|
-
name:
|
|
1797
|
+
name: import_zod7.z.string().min(1).max(120),
|
|
1386
1798
|
/** The heading of the hosted page. */
|
|
1387
|
-
title:
|
|
1799
|
+
title: import_zod7.z.string().min(1).max(120),
|
|
1388
1800
|
/** What the person agrees to, shown above the button and recorded with the confirmation. */
|
|
1389
|
-
consent_text:
|
|
1390
|
-
translations:
|
|
1391
|
-
topics:
|
|
1392
|
-
tags:
|
|
1801
|
+
consent_text: import_zod7.z.string().min(1).max(2e3),
|
|
1802
|
+
translations: import_zod7.z.record(SignupFormTranslation),
|
|
1803
|
+
topics: import_zod7.z.array(Slug).min(1),
|
|
1804
|
+
tags: import_zod7.z.array(Slug),
|
|
1393
1805
|
/** Fields shown besides email. */
|
|
1394
|
-
fields:
|
|
1395
|
-
double_opt_in:
|
|
1806
|
+
fields: import_zod7.z.array(import_zod7.z.enum(SIGNUP_FORM_FIELDS)),
|
|
1807
|
+
double_opt_in: import_zod7.z.literal(true),
|
|
1396
1808
|
/** The provider the confirmation mail is sent through. */
|
|
1397
1809
|
provider_id: Id,
|
|
1398
1810
|
/**
|
|
@@ -1401,11 +1813,11 @@ var SignupForm = import_zod6.z.object({
|
|
|
1401
1813
|
*/
|
|
1402
1814
|
confirmation_template_id: Id.nullable(),
|
|
1403
1815
|
/** Where the person lands after confirming. Null shows the hosted confirmation page. */
|
|
1404
|
-
redirect_url:
|
|
1816
|
+
redirect_url: import_zod7.z.string().url().nullable(),
|
|
1405
1817
|
/** Origins allowed to embed the form and call the submission route from a browser. */
|
|
1406
|
-
allowed_origins:
|
|
1818
|
+
allowed_origins: import_zod7.z.array(import_zod7.z.string().url()).max(20),
|
|
1407
1819
|
/** Bumped on every change; recorded with each confirmation. */
|
|
1408
|
-
version:
|
|
1820
|
+
version: import_zod7.z.number().int().min(1),
|
|
1409
1821
|
created_at: Timestamp,
|
|
1410
1822
|
updated_at: Timestamp
|
|
1411
1823
|
});
|
|
@@ -1420,56 +1832,56 @@ var SignupFormCreate = SignupForm.omit({
|
|
|
1420
1832
|
tags: SignupForm.shape.tags.optional(),
|
|
1421
1833
|
fields: SignupForm.shape.fields.optional(),
|
|
1422
1834
|
confirmation_template_id: Id.nullable().optional(),
|
|
1423
|
-
redirect_url:
|
|
1835
|
+
redirect_url: import_zod7.z.string().url().nullable().optional(),
|
|
1424
1836
|
allowed_origins: SignupForm.shape.allowed_origins.optional()
|
|
1425
1837
|
});
|
|
1426
|
-
var SignupFormEmbed =
|
|
1427
|
-
hosted_url:
|
|
1838
|
+
var SignupFormEmbed = import_zod7.z.object({
|
|
1839
|
+
hosted_url: import_zod7.z.string().url(),
|
|
1428
1840
|
/** A plain HTML form posting to the hosted page; works without JavaScript. */
|
|
1429
|
-
html:
|
|
1841
|
+
html: import_zod7.z.string(),
|
|
1430
1842
|
/** A small optional script that adds the time check and submits in place. */
|
|
1431
|
-
script_url:
|
|
1843
|
+
script_url: import_zod7.z.string().url(),
|
|
1432
1844
|
/** Where a browser fetches a fresh `form_token` from (GET, public, CORS for `allowed_origins`). */
|
|
1433
|
-
token_url:
|
|
1845
|
+
token_url: import_zod7.z.string().url()
|
|
1434
1846
|
});
|
|
1435
|
-
var SignupSubmission =
|
|
1847
|
+
var SignupSubmission = import_zod7.z.object({
|
|
1436
1848
|
email: Email,
|
|
1437
|
-
first_name:
|
|
1438
|
-
last_name:
|
|
1439
|
-
locale:
|
|
1849
|
+
first_name: import_zod7.z.string().max(200).optional(),
|
|
1850
|
+
last_name: import_zod7.z.string().max(200).optional(),
|
|
1851
|
+
locale: import_zod7.z.string().min(2).max(35).optional(),
|
|
1440
1852
|
/** Honeypot: must be empty. */
|
|
1441
|
-
website:
|
|
1853
|
+
website: import_zod7.z.string().max(0).optional(),
|
|
1442
1854
|
/**
|
|
1443
1855
|
* The signed render time from the form's token URL: a submission sooner than a
|
|
1444
1856
|
* human could fill the form, or long after, is refused.
|
|
1445
1857
|
*/
|
|
1446
|
-
form_token:
|
|
1858
|
+
form_token: import_zod7.z.string().min(1).max(512)
|
|
1447
1859
|
});
|
|
1448
1860
|
var IMPORT_STATUSES = ["uploaded", "validating", "validated", "committing", "completed", "failed", "cancelled"];
|
|
1449
|
-
var ImportStatus =
|
|
1861
|
+
var ImportStatus = import_zod7.z.enum(IMPORT_STATUSES);
|
|
1450
1862
|
var IMPORT_TERMINAL_STATUSES = ["completed", "failed", "cancelled"];
|
|
1451
|
-
var ImportColumnMapping =
|
|
1452
|
-
|
|
1863
|
+
var ImportColumnMapping = import_zod7.z.record(
|
|
1864
|
+
import_zod7.z.string().regex(/^(email|external_id|first_name|last_name|locale|property:[A-Za-z0-9_.-]{1,64}|ignore)$/)
|
|
1453
1865
|
);
|
|
1454
1866
|
var IMPORT_FILE_FIELD = "file";
|
|
1455
1867
|
var MAX_IMPORT_BYTES = 50 * 1024 * 1024;
|
|
1456
1868
|
var MAX_IMPORT_ROWS = 2e5;
|
|
1457
|
-
var ImportMappingRequest =
|
|
1869
|
+
var ImportMappingRequest = import_zod7.z.object({
|
|
1458
1870
|
/** CSV header -> contact field. Exactly one column maps to `email`. */
|
|
1459
1871
|
mapping: ImportColumnMapping.refine(
|
|
1460
1872
|
(m) => Object.values(m).filter((v) => v === "email").length === 1,
|
|
1461
1873
|
"exactly one column maps to email"
|
|
1462
1874
|
),
|
|
1463
|
-
topics:
|
|
1464
|
-
tags:
|
|
1875
|
+
topics: import_zod7.z.array(Slug).max(100),
|
|
1876
|
+
tags: import_zod7.z.array(Slug).max(100),
|
|
1465
1877
|
/** Existing contacts get the mapped fields overwritten; false only adds topics, tags and missing fields. */
|
|
1466
|
-
update_existing:
|
|
1878
|
+
update_existing: import_zod7.z.boolean().optional(),
|
|
1467
1879
|
/** The importer states the people consented; the service records who said so and when. */
|
|
1468
|
-
consent_confirmed:
|
|
1880
|
+
consent_confirmed: import_zod7.z.literal(true)
|
|
1469
1881
|
});
|
|
1470
|
-
var ImportCommitRequest =
|
|
1882
|
+
var ImportCommitRequest = import_zod7.z.object({ mapping_version: import_zod7.z.number().int().min(1) });
|
|
1471
1883
|
var IMPORT_ROW_OUTCOMES = ["created", "updated", "unchanged", "suppressed", "skipped"];
|
|
1472
|
-
var ImportRowOutcome =
|
|
1884
|
+
var ImportRowOutcome = import_zod7.z.enum(IMPORT_ROW_OUTCOMES);
|
|
1473
1885
|
var IMPORT_SKIP_REASONS = [
|
|
1474
1886
|
"missing_email",
|
|
1475
1887
|
"invalid_email",
|
|
@@ -1478,90 +1890,90 @@ var IMPORT_SKIP_REASONS = [
|
|
|
1478
1890
|
"external_id_conflict",
|
|
1479
1891
|
"wrong_column_count"
|
|
1480
1892
|
];
|
|
1481
|
-
var ImportSkipReason =
|
|
1482
|
-
var ImportReport =
|
|
1483
|
-
created:
|
|
1484
|
-
updated:
|
|
1485
|
-
unchanged:
|
|
1486
|
-
suppressed:
|
|
1487
|
-
skipped:
|
|
1488
|
-
skipped_by_reason:
|
|
1893
|
+
var ImportSkipReason = import_zod7.z.enum(IMPORT_SKIP_REASONS);
|
|
1894
|
+
var ImportReport = import_zod7.z.object({
|
|
1895
|
+
created: import_zod7.z.number().int().min(0),
|
|
1896
|
+
updated: import_zod7.z.number().int().min(0),
|
|
1897
|
+
unchanged: import_zod7.z.number().int().min(0),
|
|
1898
|
+
suppressed: import_zod7.z.number().int().min(0),
|
|
1899
|
+
skipped: import_zod7.z.number().int().min(0),
|
|
1900
|
+
skipped_by_reason: import_zod7.z.record(ImportSkipReason, import_zod7.z.number().int().min(0)),
|
|
1489
1901
|
/** Rows whose contact was subscribed without some topic, because the address is blocked for it. */
|
|
1490
|
-
topics_withheld:
|
|
1902
|
+
topics_withheld: import_zod7.z.number().int().min(0)
|
|
1491
1903
|
});
|
|
1492
|
-
var ImportJob =
|
|
1904
|
+
var ImportJob = import_zod7.z.object({
|
|
1493
1905
|
id: Id,
|
|
1494
1906
|
status: ImportStatus,
|
|
1495
|
-
file_name:
|
|
1496
|
-
file_bytes:
|
|
1497
|
-
total_rows:
|
|
1907
|
+
file_name: import_zod7.z.string().max(255).nullable(),
|
|
1908
|
+
file_bytes: import_zod7.z.number().int().min(0),
|
|
1909
|
+
total_rows: import_zod7.z.number().int().min(0),
|
|
1498
1910
|
/** The header row, as in the file. */
|
|
1499
|
-
columns:
|
|
1911
|
+
columns: import_zod7.z.array(import_zod7.z.string()),
|
|
1500
1912
|
/** The first rows, for the mapping screen. */
|
|
1501
|
-
sample:
|
|
1913
|
+
sample: import_zod7.z.array(import_zod7.z.array(import_zod7.z.string())).max(5),
|
|
1502
1914
|
/** A mapping guessed from the headers, for the mapping screen to start from. */
|
|
1503
1915
|
suggested_mapping: ImportColumnMapping,
|
|
1504
1916
|
mapping: ImportColumnMapping.nullable(),
|
|
1505
|
-
mapping_version:
|
|
1506
|
-
topics:
|
|
1507
|
-
tags:
|
|
1508
|
-
update_existing:
|
|
1917
|
+
mapping_version: import_zod7.z.number().int().min(0),
|
|
1918
|
+
topics: import_zod7.z.array(Slug),
|
|
1919
|
+
tags: import_zod7.z.array(Slug),
|
|
1920
|
+
update_existing: import_zod7.z.boolean(),
|
|
1509
1921
|
/** The dry run of the current mapping; null before one finished. */
|
|
1510
1922
|
dry_run: ImportReport.nullable(),
|
|
1511
1923
|
/** What the commit has written so far; null before it started. */
|
|
1512
1924
|
result: ImportReport.nullable(),
|
|
1513
1925
|
/** Rows the current phase (dry run or commit) has processed. */
|
|
1514
|
-
processed_rows:
|
|
1926
|
+
processed_rows: import_zod7.z.number().int().min(0),
|
|
1515
1927
|
/** Why a `failed` import failed. */
|
|
1516
|
-
error:
|
|
1928
|
+
error: import_zod7.z.string().nullable(),
|
|
1517
1929
|
created_at: Timestamp,
|
|
1518
1930
|
updated_at: Timestamp,
|
|
1519
1931
|
finished_at: Timestamp.nullable()
|
|
1520
1932
|
});
|
|
1521
|
-
var ImportRow =
|
|
1933
|
+
var ImportRow = import_zod7.z.object({
|
|
1522
1934
|
/** The line in the file, 1 being the first line after the header. */
|
|
1523
|
-
row:
|
|
1524
|
-
email:
|
|
1935
|
+
row: import_zod7.z.number().int().min(1),
|
|
1936
|
+
email: import_zod7.z.string().nullable(),
|
|
1525
1937
|
outcome: ImportRowOutcome,
|
|
1526
1938
|
reason: ImportSkipReason.nullable(),
|
|
1527
1939
|
/** A human explanation for a skipped row (which value, which column). */
|
|
1528
|
-
message:
|
|
1940
|
+
message: import_zod7.z.string().nullable(),
|
|
1529
1941
|
contact_id: Id.nullable()
|
|
1530
1942
|
});
|
|
1531
1943
|
var ImportRowListQuery = PageQuery.extend({ outcome: ImportRowOutcome.optional() });
|
|
1532
|
-
var MailingScheduleRequest =
|
|
1533
|
-
var TrackingSettings =
|
|
1534
|
-
var VariantAnalytics =
|
|
1535
|
-
key:
|
|
1536
|
-
sent:
|
|
1537
|
-
unique_opens:
|
|
1538
|
-
unique_clicks:
|
|
1539
|
-
});
|
|
1540
|
-
var MailingAnalytics =
|
|
1944
|
+
var MailingScheduleRequest = import_zod7.z.object({ send_at: Timestamp });
|
|
1945
|
+
var TrackingSettings = import_zod7.z.object({ opens: import_zod7.z.boolean(), clicks: import_zod7.z.boolean() });
|
|
1946
|
+
var VariantAnalytics = import_zod7.z.object({
|
|
1947
|
+
key: import_zod7.z.string(),
|
|
1948
|
+
sent: import_zod7.z.number().int().min(0),
|
|
1949
|
+
unique_opens: import_zod7.z.number().int().min(0).nullable(),
|
|
1950
|
+
unique_clicks: import_zod7.z.number().int().min(0).nullable()
|
|
1951
|
+
});
|
|
1952
|
+
var MailingAnalytics = import_zod7.z.object({
|
|
1541
1953
|
mailing_id: Id,
|
|
1542
1954
|
counts: MailingCounts,
|
|
1543
1955
|
/** What was tracked for this mailing, fixed when it started; null before it started. */
|
|
1544
1956
|
tracking: TrackingSettings.nullable(),
|
|
1545
1957
|
/** Unique recipients with a human open. Null when opens were not tracked. */
|
|
1546
|
-
unique_opens:
|
|
1958
|
+
unique_opens: import_zod7.z.number().int().min(0).nullable(),
|
|
1547
1959
|
/**
|
|
1548
1960
|
* Recipients whose only opens came from Apple Mail Privacy Protection, which
|
|
1549
1961
|
* loads every image on delivery: counted apart, never as human opens.
|
|
1550
1962
|
*/
|
|
1551
|
-
apple_mpp_opens:
|
|
1963
|
+
apple_mpp_opens: import_zod7.z.number().int().min(0).nullable(),
|
|
1552
1964
|
/** Opens and clicks from security scanners and prefetchers, filtered out of the unique counts. */
|
|
1553
|
-
machine_events:
|
|
1554
|
-
unique_clicks:
|
|
1555
|
-
unsubscribes:
|
|
1556
|
-
bounces:
|
|
1557
|
-
complaints:
|
|
1558
|
-
links:
|
|
1559
|
-
variants:
|
|
1965
|
+
machine_events: import_zod7.z.number().int().min(0).nullable(),
|
|
1966
|
+
unique_clicks: import_zod7.z.number().int().min(0).nullable(),
|
|
1967
|
+
unsubscribes: import_zod7.z.number().int().min(0),
|
|
1968
|
+
bounces: import_zod7.z.number().int().min(0),
|
|
1969
|
+
complaints: import_zod7.z.number().int().min(0),
|
|
1970
|
+
links: import_zod7.z.array(import_zod7.z.object({ url: import_zod7.z.string(), unique_clicks: import_zod7.z.number().int().min(0) })).nullable(),
|
|
1971
|
+
variants: import_zod7.z.array(VariantAnalytics).nullable()
|
|
1560
1972
|
});
|
|
1561
1973
|
|
|
1562
1974
|
// src/automations.ts
|
|
1563
1975
|
var AUTOMATION_FILTER_FIELD_PATTERN = /^(email|first_name|last_name|locale|created_at|tag|topic|segment|engagement:(opened|clicked)|step:(opened|clicked)|property:[A-Za-z0-9_.-]{1,64}|event:[A-Za-z0-9_.-]{1,64})$/;
|
|
1564
|
-
var AutomationFilterField =
|
|
1976
|
+
var AutomationFilterField = import_zod8.z.string().regex(AUTOMATION_FILTER_FIELD_PATTERN, "unknown filter field");
|
|
1565
1977
|
var AUTOMATION_FILTER_FIELD_OPERATORS = {
|
|
1566
1978
|
...FILTER_FIELD_OPERATORS,
|
|
1567
1979
|
segment: ["eq", "neq", "in", "not_in"],
|
|
@@ -1571,18 +1983,18 @@ var AUTOMATION_FILTER_FIELD_OPERATORS = {
|
|
|
1571
1983
|
step: ["eq", "neq"]
|
|
1572
1984
|
};
|
|
1573
1985
|
var automationFilterFamily = filterFieldFamily;
|
|
1574
|
-
var AutomationFilterCondition =
|
|
1986
|
+
var AutomationFilterCondition = import_zod8.z.object({ field: AutomationFilterField, op: FilterOperator, value: JsonValue.optional() }).strict().refine((c) => (c.op === "exists" || c.op === "not_exists") === (c.value === void 0), {
|
|
1575
1987
|
message: "exists and not_exists take no value; every other operator needs one",
|
|
1576
1988
|
path: ["value"]
|
|
1577
1989
|
}).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
|
|
1578
1990
|
message: "in and not_in take an array",
|
|
1579
1991
|
path: ["value"]
|
|
1580
1992
|
});
|
|
1581
|
-
var AutomationFilter =
|
|
1582
|
-
() =>
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1993
|
+
var AutomationFilter = import_zod8.z.lazy(
|
|
1994
|
+
() => import_zod8.z.union([
|
|
1995
|
+
import_zod8.z.object({ and: import_zod8.z.array(AutomationFilter).min(1).max(50) }).strict(),
|
|
1996
|
+
import_zod8.z.object({ or: import_zod8.z.array(AutomationFilter).min(1).max(50) }).strict(),
|
|
1997
|
+
import_zod8.z.object({ not: AutomationFilter }).strict(),
|
|
1586
1998
|
AutomationFilterCondition
|
|
1587
1999
|
])
|
|
1588
2000
|
);
|
|
@@ -1595,20 +2007,20 @@ function filterConditions(filter) {
|
|
|
1595
2007
|
if ("not" in filter) return filterConditions(filter.not);
|
|
1596
2008
|
return [filter];
|
|
1597
2009
|
}
|
|
1598
|
-
var StepId =
|
|
1599
|
-
var TimeOfDay =
|
|
1600
|
-
var Weekday =
|
|
1601
|
-
var TimezoneName =
|
|
2010
|
+
var StepId = import_zod8.z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "letters, digits, underscore and hyphen, 1 to 64");
|
|
2011
|
+
var TimeOfDay = import_zod8.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "HH:MM in 24 hours");
|
|
2012
|
+
var Weekday = import_zod8.z.number().int().min(0).max(6);
|
|
2013
|
+
var TimezoneName = import_zod8.z.string().min(1).max(64).regex(/^(UTC|[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)+)$/, "an IANA time zone name such as Europe/Berlin");
|
|
1602
2014
|
var MAX_DELAY_SECONDS = 365 * 24 * 60 * 60;
|
|
1603
|
-
var DateOnly =
|
|
2015
|
+
var DateOnly = import_zod8.z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "a calendar date such as 2026-09-20").refine((v) => {
|
|
1604
2016
|
const [y, m, d] = v.split("-").map(Number);
|
|
1605
2017
|
const t = new Date(Date.UTC(y, m - 1, d));
|
|
1606
2018
|
return t.getUTCFullYear() === y && t.getUTCMonth() + 1 === m && t.getUTCDate() === d;
|
|
1607
2019
|
}, "a real calendar date");
|
|
1608
|
-
var OffsetDays =
|
|
2020
|
+
var OffsetDays = import_zod8.z.number().int().min(-365).max(365);
|
|
1609
2021
|
var DATE_PASSED_ANSWERS = ["skip", "stop", "continue"];
|
|
1610
|
-
var DatePassedAnswer =
|
|
1611
|
-
var EventName =
|
|
2022
|
+
var DatePassedAnswer = import_zod8.z.enum(DATE_PASSED_ANSWERS);
|
|
2023
|
+
var EventName = import_zod8.z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,79}$/, "letters, digits, dot, colon, underscore and hyphen, 1 to 80");
|
|
1612
2024
|
var AUTOMATION_TRIGGER_KINDS = [
|
|
1613
2025
|
"topic_subscribed",
|
|
1614
2026
|
"form_confirmed",
|
|
@@ -1621,17 +2033,17 @@ var AUTOMATION_TRIGGER_KINDS = [
|
|
|
1621
2033
|
"segment_entered",
|
|
1622
2034
|
"link_clicked"
|
|
1623
2035
|
];
|
|
1624
|
-
var AutomationTriggerKind =
|
|
2036
|
+
var AutomationTriggerKind = import_zod8.z.enum(AUTOMATION_TRIGGER_KINDS);
|
|
1625
2037
|
var exclude = { exclude: BoundedAutomationFilter.nullish() };
|
|
1626
|
-
var AutomationTrigger =
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
2038
|
+
var AutomationTrigger = import_zod8.z.discriminatedUnion("kind", [
|
|
2039
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("topic_subscribed"), topic: Slug, ...exclude }).strict(),
|
|
2040
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("form_confirmed"), form_id: Id, ...exclude }).strict(),
|
|
2041
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("tag_added"), tag: Slug, ...exclude }).strict(),
|
|
1630
2042
|
/** `to` given: only a change that lands on this value fires. Absent: any change of the key. */
|
|
1631
|
-
|
|
2043
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("property_changed"), key: ContactPropertyKey, to: JsonValue.optional(), ...exclude }).strict(),
|
|
1632
2044
|
/** `where` filters the event's own data, so only `event:<key>` leaves belong in it. */
|
|
1633
|
-
|
|
1634
|
-
|
|
2045
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("event"), name: EventName, where: BoundedAutomationFilter.nullish(), ...exclude }).strict(),
|
|
2046
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("manual"), ...exclude }).strict(),
|
|
1635
2047
|
/**
|
|
1636
2048
|
* Every year on a date the contact carries: a birthday, a joining date, a
|
|
1637
2049
|
* renewal. `offset_days` moves it ("three days before"), `at` is the wall
|
|
@@ -1641,8 +2053,8 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
|
|
|
1641
2053
|
* A 29 February date fires on 1 March in a year that has no 29 February, so
|
|
1642
2054
|
* nobody is skipped every fourth year.
|
|
1643
2055
|
*/
|
|
1644
|
-
|
|
1645
|
-
kind:
|
|
2056
|
+
import_zod8.z.object({
|
|
2057
|
+
kind: import_zod8.z.literal("date_anniversary"),
|
|
1646
2058
|
key: ContactPropertyKey,
|
|
1647
2059
|
offset_days: OffsetDays,
|
|
1648
2060
|
at: TimeOfDay,
|
|
@@ -1654,8 +2066,8 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
|
|
|
1654
2066
|
* not ask about a segment or about an event's data: there is no event, and a
|
|
1655
2067
|
* segment is its own trigger kind.
|
|
1656
2068
|
*/
|
|
1657
|
-
|
|
1658
|
-
kind:
|
|
2069
|
+
import_zod8.z.object({
|
|
2070
|
+
kind: import_zod8.z.literal("exact_date"),
|
|
1659
2071
|
on: DateOnly,
|
|
1660
2072
|
at: TimeOfDay,
|
|
1661
2073
|
where: BoundedAutomationFilter.nullish(),
|
|
@@ -1674,7 +2086,7 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
|
|
|
1674
2086
|
* already exists. Entering those people as well is the publish dialog's
|
|
1675
2087
|
* backfill question, answered by a person rather than by the sweep.
|
|
1676
2088
|
*/
|
|
1677
|
-
|
|
2089
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("segment_entered"), segment_id: Id, ...exclude }).strict(),
|
|
1678
2090
|
/**
|
|
1679
2091
|
* S4. When somebody clicks a link in a mailing that has already gone out.
|
|
1680
2092
|
*
|
|
@@ -1684,7 +2096,7 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
|
|
|
1684
2096
|
* tracking off there is no click to hear about, so the validator refuses it
|
|
1685
2097
|
* rather than leaving an automation that can never fire.
|
|
1686
2098
|
*/
|
|
1687
|
-
|
|
2099
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("link_clicked"), mailing_id: Id, url: import_zod8.z.string().min(1).max(2048).optional(), ...exclude }).strict()
|
|
1688
2100
|
]);
|
|
1689
2101
|
var AUTOMATION_DATE_TRIGGER_KINDS = ["date_anniversary", "exact_date"];
|
|
1690
2102
|
var MAX_TRIGGERS = 10;
|
|
@@ -1703,41 +2115,45 @@ var AUTOMATION_STEP_KINDS = [
|
|
|
1703
2115
|
"webhook",
|
|
1704
2116
|
"notify"
|
|
1705
2117
|
];
|
|
1706
|
-
var AutomationStepKind =
|
|
2118
|
+
var AutomationStepKind = import_zod8.z.enum(AUTOMATION_STEP_KINDS);
|
|
1707
2119
|
var next = { next: StepId.nullable() };
|
|
1708
|
-
var Unchosen =
|
|
1709
|
-
var DraftSlug =
|
|
1710
|
-
var DraftPropertyKey =
|
|
1711
|
-
var label = { label:
|
|
1712
|
-
var EmailStepLanguage =
|
|
1713
|
-
subject:
|
|
1714
|
-
preheader:
|
|
1715
|
-
document: TemplateDocument
|
|
2120
|
+
var Unchosen = import_zod8.z.literal("");
|
|
2121
|
+
var DraftSlug = import_zod8.z.union([Slug, Unchosen]);
|
|
2122
|
+
var DraftPropertyKey = import_zod8.z.union([ContactPropertyKey, Unchosen]);
|
|
2123
|
+
var label = { label: import_zod8.z.string().max(120).nullish() };
|
|
2124
|
+
var EmailStepLanguage = import_zod8.z.object({
|
|
2125
|
+
subject: import_zod8.z.string().min(1).max(998),
|
|
2126
|
+
preheader: import_zod8.z.string().max(500).nullable(),
|
|
2127
|
+
document: TemplateDocument,
|
|
2128
|
+
/** This language's insertions, its texts over the template's structure (every text present). */
|
|
2129
|
+
insertions: Insertions.nullish()
|
|
1716
2130
|
}).strict();
|
|
1717
|
-
var EmailStep =
|
|
1718
|
-
kind:
|
|
2131
|
+
var EmailStep = import_zod8.z.object({
|
|
2132
|
+
kind: import_zod8.z.literal("email"),
|
|
1719
2133
|
...label,
|
|
1720
2134
|
...next,
|
|
1721
2135
|
template_id: Id.nullish(),
|
|
1722
2136
|
document: TemplateDocument.nullish(),
|
|
1723
2137
|
/** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
|
|
1724
|
-
subject:
|
|
1725
|
-
preheader:
|
|
2138
|
+
subject: import_zod8.z.string().max(998),
|
|
2139
|
+
preheader: import_zod8.z.string().max(500).nullish(),
|
|
1726
2140
|
locale: LocaleTag.nullish(),
|
|
1727
|
-
languages:
|
|
2141
|
+
languages: import_zod8.z.record(LocaleTag, EmailStepLanguage).nullish(),
|
|
2142
|
+
/** The main language's insertions, snapshot from the template when the automation is published. */
|
|
2143
|
+
insertions: Insertions.nullish()
|
|
1728
2144
|
}).strict();
|
|
1729
|
-
var DelayStep =
|
|
1730
|
-
kind:
|
|
2145
|
+
var DelayStep = import_zod8.z.object({
|
|
2146
|
+
kind: import_zod8.z.literal("delay"),
|
|
1731
2147
|
...label,
|
|
1732
2148
|
...next,
|
|
1733
|
-
wait:
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2149
|
+
wait: import_zod8.z.discriminatedUnion("mode", [
|
|
2150
|
+
import_zod8.z.object({ mode: import_zod8.z.literal("duration"), seconds: import_zod8.z.number().int().min(1).max(MAX_DELAY_SECONDS) }).strict(),
|
|
2151
|
+
import_zod8.z.object({ mode: import_zod8.z.literal("time_of_day"), at: TimeOfDay }).strict(),
|
|
2152
|
+
import_zod8.z.object({ mode: import_zod8.z.literal("weekday"), weekday: Weekday, at: TimeOfDay }).strict(),
|
|
1737
2153
|
/** A fixed day has no next occurrence, so `continue` is not one of its answers. */
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
mode:
|
|
2154
|
+
import_zod8.z.object({ mode: import_zod8.z.literal("date"), on: DateOnly, at: TimeOfDay, passed: import_zod8.z.enum(["skip", "stop"]) }).strict(),
|
|
2155
|
+
import_zod8.z.object({
|
|
2156
|
+
mode: import_zod8.z.literal("date_property"),
|
|
1741
2157
|
key: ContactPropertyKey,
|
|
1742
2158
|
offset_days: OffsetDays,
|
|
1743
2159
|
at: TimeOfDay,
|
|
@@ -1745,8 +2161,8 @@ var DelayStep = import_zod7.z.object({
|
|
|
1745
2161
|
}).strict()
|
|
1746
2162
|
])
|
|
1747
2163
|
}).strict();
|
|
1748
|
-
var ConditionStep =
|
|
1749
|
-
kind:
|
|
2164
|
+
var ConditionStep = import_zod8.z.object({
|
|
2165
|
+
kind: import_zod8.z.literal("condition"),
|
|
1750
2166
|
...label,
|
|
1751
2167
|
filter: BoundedAutomationFilter,
|
|
1752
2168
|
yes: StepId.nullable(),
|
|
@@ -1757,49 +2173,49 @@ var ConditionStep = import_zod7.z.object({
|
|
|
1757
2173
|
* neither half means anything without the other: a window with no answer
|
|
1758
2174
|
* for the end of it would be a silent decision.
|
|
1759
2175
|
*/
|
|
1760
|
-
wait_for:
|
|
2176
|
+
wait_for: import_zod8.z.object({ up_to_s: import_zod8.z.number().int().min(1).max(MAX_DELAY_SECONDS), on_timeout: import_zod8.z.enum(["yes", "no"]) }).strict().nullish()
|
|
1761
2177
|
}).strict();
|
|
1762
|
-
var SplitBranchKey =
|
|
2178
|
+
var SplitBranchKey = import_zod8.z.string().regex(/^[A-Za-z0-9_-]{1,32}$/, "letters, digits, underscore and hyphen, 1 to 32");
|
|
1763
2179
|
var MIN_SPLIT_BRANCHES = 2;
|
|
1764
2180
|
var MAX_SPLIT_BRANCHES = 4;
|
|
1765
|
-
var SplitStep =
|
|
1766
|
-
kind:
|
|
2181
|
+
var SplitStep = import_zod8.z.object({
|
|
2182
|
+
kind: import_zod8.z.literal("split"),
|
|
1767
2183
|
...label,
|
|
1768
|
-
branches:
|
|
1769
|
-
|
|
2184
|
+
branches: import_zod8.z.array(
|
|
2185
|
+
import_zod8.z.object({
|
|
1770
2186
|
key: SplitBranchKey,
|
|
1771
2187
|
/** A whole percentage. Zero is allowed: a branch kept in the flow but switched off. */
|
|
1772
|
-
weight:
|
|
2188
|
+
weight: import_zod8.z.number().int().min(0).max(100),
|
|
1773
2189
|
next: StepId.nullable()
|
|
1774
2190
|
}).strict()
|
|
1775
2191
|
).min(MIN_SPLIT_BRANCHES).max(MAX_SPLIT_BRANCHES).refine((bs) => new Set(bs.map((b) => b.key)).size === bs.length, "each branch needs its own key").refine((bs) => bs.reduce((sum, b) => sum + b.weight, 0) === 100, "the weights have to come to 100")
|
|
1776
2192
|
}).strict();
|
|
1777
|
-
var GotoStep =
|
|
2193
|
+
var GotoStep = import_zod8.z.object({ kind: import_zod8.z.literal("goto"), ...label, target: StepId.nullable() }).strict();
|
|
1778
2194
|
var MAX_STEP_VISITS = 20;
|
|
1779
|
-
var WebhookStep =
|
|
1780
|
-
var NotifyRecipients =
|
|
1781
|
-
var NotifyStep =
|
|
1782
|
-
kind:
|
|
2195
|
+
var WebhookStep = import_zod8.z.object({ kind: import_zod8.z.literal("webhook"), ...label, ...next, endpoint_id: Id.nullable(), payload: JsonValue.nullish() }).strict();
|
|
2196
|
+
var NotifyRecipients = import_zod8.z.union([import_zod8.z.literal("owner"), import_zod8.z.literal("admins"), import_zod8.z.object({ member_id: Id }).strict()]);
|
|
2197
|
+
var NotifyStep = import_zod8.z.object({
|
|
2198
|
+
kind: import_zod8.z.literal("notify"),
|
|
1783
2199
|
...label,
|
|
1784
2200
|
...next,
|
|
1785
2201
|
to: NotifyRecipients,
|
|
1786
2202
|
/** Empty while the step is being written; `not_chosen` refuses to publish it. */
|
|
1787
|
-
subject:
|
|
1788
|
-
body:
|
|
2203
|
+
subject: import_zod8.z.string().max(998),
|
|
2204
|
+
body: import_zod8.z.string().max(1e4)
|
|
1789
2205
|
}).strict();
|
|
1790
|
-
var AutomationStep =
|
|
2206
|
+
var AutomationStep = import_zod8.z.discriminatedUnion("kind", [
|
|
1791
2207
|
EmailStep,
|
|
1792
2208
|
DelayStep,
|
|
1793
2209
|
ConditionStep,
|
|
1794
|
-
|
|
1795
|
-
|
|
2210
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("add_tag"), ...label, ...next, tag: DraftSlug }).strict(),
|
|
2211
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("remove_tag"), ...label, ...next, tag: DraftSlug }).strict(),
|
|
1796
2212
|
/** `value` null clears the property, as a contact upsert does. */
|
|
1797
|
-
|
|
2213
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("set_property"), ...label, ...next, key: DraftPropertyKey, value: JsonValue }).strict(),
|
|
1798
2214
|
/** Subscribing never lifts a suppression: the service checks that, not the flow. */
|
|
1799
|
-
|
|
2215
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("subscribe_topic"), ...label, ...next, topic: DraftSlug }).strict(),
|
|
1800
2216
|
/** `topic` unsubscribes from the automation's own topic, `all` from every topic. */
|
|
1801
|
-
|
|
1802
|
-
|
|
2217
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("unsubscribe"), ...label, ...next, scope: import_zod8.z.enum(["topic", "all"]) }).strict(),
|
|
2218
|
+
import_zod8.z.object({ kind: import_zod8.z.literal("exit"), ...label }).strict(),
|
|
1803
2219
|
SplitStep,
|
|
1804
2220
|
GotoStep,
|
|
1805
2221
|
WebhookStep,
|
|
@@ -1823,25 +2239,25 @@ function stepTargets(step) {
|
|
|
1823
2239
|
}
|
|
1824
2240
|
}
|
|
1825
2241
|
var MAX_STEPS = 200;
|
|
1826
|
-
var AutomationFlow =
|
|
1827
|
-
triggers:
|
|
2242
|
+
var AutomationFlow = import_zod8.z.object({
|
|
2243
|
+
triggers: import_zod8.z.array(AutomationTrigger).max(MAX_TRIGGERS),
|
|
1828
2244
|
/** The first step. `null` is an empty flow. */
|
|
1829
2245
|
entry: StepId.nullable(),
|
|
1830
|
-
steps:
|
|
2246
|
+
steps: import_zod8.z.record(StepId, AutomationStep)
|
|
1831
2247
|
}).strict();
|
|
1832
2248
|
var emptyFlow = () => ({ triggers: [], entry: null, steps: {} });
|
|
1833
|
-
var QuietHours =
|
|
2249
|
+
var QuietHours = import_zod8.z.object({
|
|
1834
2250
|
start: TimeOfDay,
|
|
1835
2251
|
end: TimeOfDay,
|
|
1836
|
-
weekdays:
|
|
2252
|
+
weekdays: import_zod8.z.array(Weekday).min(1).max(7).nullish()
|
|
1837
2253
|
}).strict();
|
|
1838
2254
|
var RE_ENTRY_MODES = ["off", "after_exit"];
|
|
1839
|
-
var ReEntryMode =
|
|
2255
|
+
var ReEntryMode = import_zod8.z.enum(RE_ENTRY_MODES);
|
|
1840
2256
|
var MAX_RE_ENTRY_COOLDOWN_SECONDS = 365 * 24 * 60 * 60;
|
|
1841
|
-
var AutomationSettings =
|
|
2257
|
+
var AutomationSettings = import_zod8.z.object({
|
|
1842
2258
|
re_entry: ReEntryMode,
|
|
1843
2259
|
/** Seconds a person must wait after leaving before entering again. Only with `after_exit`. */
|
|
1844
|
-
re_entry_cooldown_s:
|
|
2260
|
+
re_entry_cooldown_s: import_zod8.z.number().int().min(0).max(MAX_RE_ENTRY_COOLDOWN_SECONDS).nullish(),
|
|
1845
2261
|
quiet_hours: QuietHours.nullish(),
|
|
1846
2262
|
/** Checked before every step; when it matches, the run ends. */
|
|
1847
2263
|
exit_filter: BoundedAutomationFilter.nullish()
|
|
@@ -1899,18 +2315,18 @@ var AUTOMATION_ISSUE_CODES = [
|
|
|
1899
2315
|
/** A one-off date that is already over everywhere on earth, so it would fire for nobody. */
|
|
1900
2316
|
"date_in_the_past"
|
|
1901
2317
|
];
|
|
1902
|
-
var AutomationIssueCode =
|
|
1903
|
-
var AutomationIssue =
|
|
2318
|
+
var AutomationIssueCode = import_zod8.z.enum(AUTOMATION_ISSUE_CODES);
|
|
2319
|
+
var AutomationIssue = import_zod8.z.object({
|
|
1904
2320
|
code: AutomationIssueCode,
|
|
1905
2321
|
/** The step the issue is about, when it is about one. */
|
|
1906
2322
|
step_id: StepId.nullable(),
|
|
1907
2323
|
/** Where inside the document, for the canvas to point at a field. */
|
|
1908
|
-
path:
|
|
1909
|
-
message:
|
|
2324
|
+
path: import_zod8.z.array(import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number()])),
|
|
2325
|
+
message: import_zod8.z.string()
|
|
1910
2326
|
});
|
|
1911
|
-
var AutomationValidation =
|
|
1912
|
-
ok:
|
|
1913
|
-
issues:
|
|
2327
|
+
var AutomationValidation = import_zod8.z.object({
|
|
2328
|
+
ok: import_zod8.z.boolean(),
|
|
2329
|
+
issues: import_zod8.z.array(AutomationIssue)
|
|
1914
2330
|
});
|
|
1915
2331
|
function validateAutomation(flow, settings, ctx) {
|
|
1916
2332
|
const issues = [];
|
|
@@ -2301,7 +2717,7 @@ function cycles(flow) {
|
|
|
2301
2717
|
return out.sort();
|
|
2302
2718
|
}
|
|
2303
2719
|
var AUTOMATION_STATUSES = ["draft", "active", "paused", "archived"];
|
|
2304
|
-
var AutomationStatus =
|
|
2720
|
+
var AutomationStatus = import_zod8.z.enum(AUTOMATION_STATUSES);
|
|
2305
2721
|
var AUTOMATION_END_REASONS = [
|
|
2306
2722
|
"completed",
|
|
2307
2723
|
"exit_filter",
|
|
@@ -2320,9 +2736,9 @@ var AUTOMATION_END_REASONS = [
|
|
|
2320
2736
|
"ended_by_hand",
|
|
2321
2737
|
"failed"
|
|
2322
2738
|
];
|
|
2323
|
-
var AutomationEndReason =
|
|
2739
|
+
var AutomationEndReason = import_zod8.z.enum(AUTOMATION_END_REASONS);
|
|
2324
2740
|
var AUTOMATION_WAIT_REASONS = ["delay", "quiet_hours", "plan_limit", "sending", "condition"];
|
|
2325
|
-
var AutomationWaitReason =
|
|
2741
|
+
var AutomationWaitReason = import_zod8.z.enum(AUTOMATION_WAIT_REASONS);
|
|
2326
2742
|
var AUTOMATION_ENTRY_REFUSALS = [
|
|
2327
2743
|
"already_inside",
|
|
2328
2744
|
"re_entry_off",
|
|
@@ -2333,26 +2749,26 @@ var AUTOMATION_ENTRY_REFUSALS = [
|
|
|
2333
2749
|
"not_active",
|
|
2334
2750
|
"unknown_contact"
|
|
2335
2751
|
];
|
|
2336
|
-
var AutomationEntryRefusal =
|
|
2337
|
-
var AutomationCounts =
|
|
2338
|
-
started:
|
|
2339
|
-
in_progress:
|
|
2340
|
-
completed:
|
|
2341
|
-
exited:
|
|
2342
|
-
failed:
|
|
2343
|
-
});
|
|
2344
|
-
var Automation =
|
|
2752
|
+
var AutomationEntryRefusal = import_zod8.z.enum(AUTOMATION_ENTRY_REFUSALS);
|
|
2753
|
+
var AutomationCounts = import_zod8.z.object({
|
|
2754
|
+
started: import_zod8.z.number().int().min(0),
|
|
2755
|
+
in_progress: import_zod8.z.number().int().min(0),
|
|
2756
|
+
completed: import_zod8.z.number().int().min(0),
|
|
2757
|
+
exited: import_zod8.z.number().int().min(0),
|
|
2758
|
+
failed: import_zod8.z.number().int().min(0)
|
|
2759
|
+
});
|
|
2760
|
+
var Automation = import_zod8.z.object({
|
|
2345
2761
|
id: Id,
|
|
2346
|
-
name:
|
|
2762
|
+
name: import_zod8.z.string().min(1).max(200),
|
|
2347
2763
|
status: AutomationStatus,
|
|
2348
2764
|
/** Every automation sends under one topic; there are no transactional automations. */
|
|
2349
2765
|
topic: Slug,
|
|
2350
2766
|
provider_id: Id.nullable(),
|
|
2351
2767
|
settings: AutomationSettings,
|
|
2352
2768
|
/** The flow people are running through, null while the automation has never been published. */
|
|
2353
|
-
published_version:
|
|
2769
|
+
published_version: import_zod8.z.number().int().min(1).nullable(),
|
|
2354
2770
|
/** The flow being edited. Increments on every save, like a template's version. */
|
|
2355
|
-
draft_version:
|
|
2771
|
+
draft_version: import_zod8.z.number().int().min(1),
|
|
2356
2772
|
counts: AutomationCounts,
|
|
2357
2773
|
created_at: Timestamp,
|
|
2358
2774
|
updated_at: Timestamp,
|
|
@@ -2365,16 +2781,16 @@ var AutomationWithFlow = Automation.extend({
|
|
|
2365
2781
|
published_flow: AutomationFlow.nullable()
|
|
2366
2782
|
});
|
|
2367
2783
|
var AutomationSummary = Automation;
|
|
2368
|
-
var AutomationCreate =
|
|
2369
|
-
name:
|
|
2784
|
+
var AutomationCreate = import_zod8.z.object({
|
|
2785
|
+
name: import_zod8.z.string().min(1).max(200),
|
|
2370
2786
|
topic: Slug,
|
|
2371
2787
|
provider_id: Id.optional(),
|
|
2372
2788
|
flow: AutomationFlow.optional(),
|
|
2373
2789
|
settings: AutomationSettings.partial().optional()
|
|
2374
2790
|
});
|
|
2375
|
-
var AutomationUpdate =
|
|
2376
|
-
base_version:
|
|
2377
|
-
name:
|
|
2791
|
+
var AutomationUpdate = import_zod8.z.object({
|
|
2792
|
+
base_version: import_zod8.z.number().int().min(1),
|
|
2793
|
+
name: import_zod8.z.string().min(1).max(200).optional(),
|
|
2378
2794
|
topic: Slug.optional(),
|
|
2379
2795
|
provider_id: Id.optional(),
|
|
2380
2796
|
flow: AutomationFlow.optional(),
|
|
@@ -2387,42 +2803,42 @@ var AutomationListQuery = PageQuery.extend({
|
|
|
2387
2803
|
status: AutomationStatus.optional(),
|
|
2388
2804
|
topic: Slug.optional()
|
|
2389
2805
|
});
|
|
2390
|
-
var AutomationPublishRequest =
|
|
2806
|
+
var AutomationPublishRequest = import_zod8.z.object({
|
|
2391
2807
|
/**
|
|
2392
2808
|
* Answer what this publish would do and change nothing, whatever the flow
|
|
2393
2809
|
* looks like. Without it a publish that has no issues and moves nobody
|
|
2394
2810
|
* simply goes ahead, which is right for an API caller and wrong for a
|
|
2395
2811
|
* dialog that is only showing somebody what they are about to do.
|
|
2396
2812
|
*/
|
|
2397
|
-
dry_run:
|
|
2398
|
-
confirm:
|
|
2813
|
+
dry_run: import_zod8.z.boolean().optional(),
|
|
2814
|
+
confirm: import_zod8.z.boolean().optional(),
|
|
2399
2815
|
/** Per removed step: the step its waiting runs move to, or null to end them. */
|
|
2400
|
-
moves:
|
|
2816
|
+
moves: import_zod8.z.record(StepId, StepId.nullable()).optional(),
|
|
2401
2817
|
/** Also enter everyone who matches a trigger's condition right now. */
|
|
2402
|
-
backfill:
|
|
2818
|
+
backfill: import_zod8.z.boolean().optional()
|
|
2403
2819
|
}).strict();
|
|
2404
|
-
var AutomationPublishMove =
|
|
2820
|
+
var AutomationPublishMove = import_zod8.z.object({
|
|
2405
2821
|
step_id: StepId,
|
|
2406
2822
|
/** Why the step's people have to move: it is gone, or its delay changed. */
|
|
2407
|
-
change:
|
|
2408
|
-
waiting:
|
|
2823
|
+
change: import_zod8.z.enum(["removed", "delay_changed", "kind_changed"]),
|
|
2824
|
+
waiting: import_zod8.z.number().int().min(0),
|
|
2409
2825
|
/** Where they go: a step id, or null to end their runs with `step_removed`. */
|
|
2410
2826
|
destination: StepId.nullable()
|
|
2411
2827
|
});
|
|
2412
|
-
var AutomationPublishResult =
|
|
2413
|
-
published:
|
|
2828
|
+
var AutomationPublishResult = import_zod8.z.object({
|
|
2829
|
+
published: import_zod8.z.boolean(),
|
|
2414
2830
|
automation: Automation,
|
|
2415
2831
|
/** Empty when nothing has to move; non-empty and `published` false asks for a confirmation. */
|
|
2416
|
-
moves:
|
|
2832
|
+
moves: import_zod8.z.array(AutomationPublishMove),
|
|
2417
2833
|
/** How many people the backfill entered, or would enter when it is not confirmed. */
|
|
2418
|
-
backfill:
|
|
2419
|
-
issues:
|
|
2420
|
-
});
|
|
2421
|
-
var AutomationDuplicateRequest =
|
|
2422
|
-
var AutomationEnrollRequest =
|
|
2423
|
-
contact_ids:
|
|
2424
|
-
external_ids:
|
|
2425
|
-
emails:
|
|
2834
|
+
backfill: import_zod8.z.number().int().min(0),
|
|
2835
|
+
issues: import_zod8.z.array(AutomationIssue)
|
|
2836
|
+
});
|
|
2837
|
+
var AutomationDuplicateRequest = import_zod8.z.object({ name: import_zod8.z.string().min(1).max(200).optional() }).strict();
|
|
2838
|
+
var AutomationEnrollRequest = import_zod8.z.object({
|
|
2839
|
+
contact_ids: import_zod8.z.array(Id).min(1).max(1e3).optional(),
|
|
2840
|
+
external_ids: import_zod8.z.array(import_zod8.z.string().min(1).max(255)).min(1).max(1e3).optional(),
|
|
2841
|
+
emails: import_zod8.z.array(Email).min(1).max(1e3).optional(),
|
|
2426
2842
|
segment_id: Id.optional(),
|
|
2427
2843
|
/** Where to start them; the flow's entry step by default. */
|
|
2428
2844
|
step_id: StepId.optional()
|
|
@@ -2430,34 +2846,34 @@ var AutomationEnrollRequest = import_zod7.z.object({
|
|
|
2430
2846
|
(v) => [v.contact_ids, v.external_ids, v.emails, v.segment_id].filter((x) => x !== void 0).length === 1,
|
|
2431
2847
|
"name the people exactly one way: contact_ids, external_ids, emails or segment_id"
|
|
2432
2848
|
);
|
|
2433
|
-
var AutomationEnrollment =
|
|
2849
|
+
var AutomationEnrollment = import_zod8.z.object({
|
|
2434
2850
|
contact_id: Id.nullable(),
|
|
2435
2851
|
email: Email.nullable(),
|
|
2436
|
-
external_id:
|
|
2437
|
-
entered:
|
|
2852
|
+
external_id: import_zod8.z.string().nullable(),
|
|
2853
|
+
entered: import_zod8.z.boolean(),
|
|
2438
2854
|
run_id: Id.nullable(),
|
|
2439
2855
|
reason: AutomationEntryRefusal.nullable()
|
|
2440
2856
|
});
|
|
2441
|
-
var AutomationEnrollResult =
|
|
2442
|
-
entered:
|
|
2443
|
-
refused:
|
|
2444
|
-
results:
|
|
2857
|
+
var AutomationEnrollResult = import_zod8.z.object({
|
|
2858
|
+
entered: import_zod8.z.number().int().min(0),
|
|
2859
|
+
refused: import_zod8.z.number().int().min(0),
|
|
2860
|
+
results: import_zod8.z.array(AutomationEnrollment)
|
|
2445
2861
|
});
|
|
2446
|
-
var AutomationRunStatus =
|
|
2447
|
-
var AutomationRun =
|
|
2862
|
+
var AutomationRunStatus = import_zod8.z.enum(["active", "waiting", "completed", "exited", "failed"]);
|
|
2863
|
+
var AutomationRun = import_zod8.z.object({
|
|
2448
2864
|
id: Id,
|
|
2449
2865
|
automation_id: Id,
|
|
2450
2866
|
contact_id: Id,
|
|
2451
2867
|
email: Email,
|
|
2452
|
-
external_id:
|
|
2453
|
-
version:
|
|
2868
|
+
external_id: import_zod8.z.string().nullable(),
|
|
2869
|
+
version: import_zod8.z.number().int().min(1),
|
|
2454
2870
|
status: AutomationRunStatus,
|
|
2455
2871
|
current_step_id: StepId.nullable(),
|
|
2456
2872
|
next_action_at: Timestamp.nullable(),
|
|
2457
2873
|
wait_reason: AutomationWaitReason.nullable(),
|
|
2458
|
-
end_reason:
|
|
2874
|
+
end_reason: import_zod8.z.string().nullable(),
|
|
2459
2875
|
/** What went wrong the last time this run tried to take its step, null otherwise. */
|
|
2460
|
-
last_error:
|
|
2876
|
+
last_error: import_zod8.z.string().nullable(),
|
|
2461
2877
|
entered_at: Timestamp,
|
|
2462
2878
|
ended_at: Timestamp.nullable()
|
|
2463
2879
|
});
|
|
@@ -2467,13 +2883,13 @@ var AutomationRunListQuery = PageQuery.extend({
|
|
|
2467
2883
|
contact_id: Id.optional()
|
|
2468
2884
|
});
|
|
2469
2885
|
var ContactAutomationRun = AutomationRun.extend({
|
|
2470
|
-
automation:
|
|
2886
|
+
automation: import_zod8.z.object({ id: Id, name: import_zod8.z.string(), status: AutomationStatus })
|
|
2471
2887
|
});
|
|
2472
2888
|
var ContactAutomationListQuery = PageQuery.extend({ status: AutomationRunStatus.optional() });
|
|
2473
|
-
var AutomationStepExecution =
|
|
2889
|
+
var AutomationStepExecution = import_zod8.z.object({
|
|
2474
2890
|
step_id: StepId,
|
|
2475
2891
|
step_kind: AutomationStepKind,
|
|
2476
|
-
visit:
|
|
2892
|
+
visit: import_zod8.z.number().int().min(1),
|
|
2477
2893
|
started_at: Timestamp,
|
|
2478
2894
|
finished_at: Timestamp.nullable(),
|
|
2479
2895
|
/**
|
|
@@ -2485,48 +2901,48 @@ var AutomationStepExecution = import_zod7.z.object({
|
|
|
2485
2901
|
*/
|
|
2486
2902
|
outcome: Properties
|
|
2487
2903
|
});
|
|
2488
|
-
var AutomationJourney =
|
|
2904
|
+
var AutomationJourney = import_zod8.z.object({
|
|
2489
2905
|
run: AutomationRun,
|
|
2490
|
-
steps:
|
|
2906
|
+
steps: import_zod8.z.array(AutomationStepExecution)
|
|
2491
2907
|
});
|
|
2492
|
-
var AutomationStepReport =
|
|
2908
|
+
var AutomationStepReport = import_zod8.z.object({
|
|
2493
2909
|
step_id: StepId,
|
|
2494
2910
|
step_kind: AutomationStepKind,
|
|
2495
|
-
reached:
|
|
2496
|
-
waiting:
|
|
2497
|
-
passed:
|
|
2911
|
+
reached: import_zod8.z.number().int().min(0),
|
|
2912
|
+
waiting: import_zod8.z.number().int().min(0),
|
|
2913
|
+
passed: import_zod8.z.number().int().min(0),
|
|
2498
2914
|
/** Per branch, for a condition: how many took `yes` and how many `no`. */
|
|
2499
|
-
branches:
|
|
2915
|
+
branches: import_zod8.z.record(import_zod8.z.number().int().min(0)),
|
|
2500
2916
|
/** The step mailing's id, for an email step of the published version. */
|
|
2501
2917
|
mailing_id: Id.nullable()
|
|
2502
2918
|
});
|
|
2503
|
-
var AutomationReport =
|
|
2919
|
+
var AutomationReport = import_zod8.z.object({
|
|
2504
2920
|
counts: AutomationCounts,
|
|
2505
2921
|
/** Why the people who left did: `AutomationEndReason` to a count. */
|
|
2506
|
-
end_reasons:
|
|
2922
|
+
end_reasons: import_zod8.z.record(import_zod8.z.number().int().min(0)),
|
|
2507
2923
|
/**
|
|
2508
2924
|
* Why people a trigger named did not enter: `AutomationEntryRefusal` to a
|
|
2509
2925
|
* count. Without it a pause, an exclusion or a missing subscription would
|
|
2510
2926
|
* turn people away invisibly.
|
|
2511
2927
|
*/
|
|
2512
|
-
refusals:
|
|
2513
|
-
steps:
|
|
2514
|
-
});
|
|
2515
|
-
var AutomationExitRunRequest =
|
|
2516
|
-
var AutomationRunParams =
|
|
2517
|
-
var AutomationStepParams =
|
|
2518
|
-
var AutomationTestEmailRequest =
|
|
2519
|
-
var AutomationValidateRequest =
|
|
2520
|
-
var EventCreate =
|
|
2928
|
+
refusals: import_zod8.z.record(import_zod8.z.number().int().min(0)),
|
|
2929
|
+
steps: import_zod8.z.array(AutomationStepReport)
|
|
2930
|
+
});
|
|
2931
|
+
var AutomationExitRunRequest = import_zod8.z.object({ reason: import_zod8.z.string().max(200).optional() }).strict();
|
|
2932
|
+
var AutomationRunParams = import_zod8.z.object({ id: Id, run_id: Id });
|
|
2933
|
+
var AutomationStepParams = import_zod8.z.object({ id: Id, step_id: StepId });
|
|
2934
|
+
var AutomationTestEmailRequest = import_zod8.z.object({ to: Email, merge: Properties.optional(), locale: LocaleTag.optional() }).strict();
|
|
2935
|
+
var AutomationValidateRequest = import_zod8.z.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
|
|
2936
|
+
var EventCreate = import_zod8.z.object({
|
|
2521
2937
|
name: EventName,
|
|
2522
|
-
contact:
|
|
2938
|
+
contact: import_zod8.z.object({ external_id: import_zod8.z.string().min(1).max(255).optional(), email: Email.optional(), id: Id.optional() }).refine((c) => [c.external_id, c.email, c.id].filter((x) => x !== void 0).length === 1, "name the contact exactly one way"),
|
|
2523
2939
|
data: Properties.optional(),
|
|
2524
2940
|
occurred_at: Timestamp.optional(),
|
|
2525
|
-
id:
|
|
2941
|
+
id: import_zod8.z.string().min(1).max(128).optional()
|
|
2526
2942
|
}).strict();
|
|
2527
|
-
var Event =
|
|
2943
|
+
var Event = import_zod8.z.object({
|
|
2528
2944
|
id: Id,
|
|
2529
|
-
client_event_id:
|
|
2945
|
+
client_event_id: import_zod8.z.string().nullable(),
|
|
2530
2946
|
name: EventName,
|
|
2531
2947
|
contact_id: Id,
|
|
2532
2948
|
data: Properties,
|
|
@@ -2537,53 +2953,27 @@ var Event = import_zod7.z.object({
|
|
|
2537
2953
|
* hand. Each is then admitted or refused by the runtime on its own rules
|
|
2538
2954
|
* (consent, exclusions, re-entry), which `automations.report` counts.
|
|
2539
2955
|
*/
|
|
2540
|
-
entered:
|
|
2956
|
+
entered: import_zod8.z.number().int().min(0)
|
|
2541
2957
|
});
|
|
2542
2958
|
var EventListQuery = PageQuery.extend({
|
|
2543
2959
|
name: EventName.optional(),
|
|
2544
2960
|
contact_id: Id.optional()
|
|
2545
2961
|
});
|
|
2546
2962
|
|
|
2547
|
-
// src/unsubscribe.ts
|
|
2548
|
-
var UNSUBSCRIBE_PATH_PREFIX = "/u/";
|
|
2549
|
-
var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
|
|
2550
|
-
var RESERVED_MERGE_FIELDS = {
|
|
2551
|
-
/** From the recipient's merge values, else the contact. Supports a fallback. */
|
|
2552
|
-
first_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
2553
|
-
last_name: { source: "contact", fallback: true, required_for_broadcast: false },
|
|
2554
|
-
email: { source: "contact", fallback: false, required_for_broadcast: false },
|
|
2555
|
-
/** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
|
|
2556
|
-
unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
|
|
2557
|
-
};
|
|
2558
|
-
var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
|
|
2559
|
-
function findMergeFields(html) {
|
|
2560
|
-
const uses = [];
|
|
2561
|
-
for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
|
|
2562
|
-
uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
|
|
2563
|
-
}
|
|
2564
|
-
return uses;
|
|
2565
|
-
}
|
|
2566
|
-
function missingRequiredMergeFields(html) {
|
|
2567
|
-
const used = new Set(findMergeFields(html).map((u) => u.name));
|
|
2568
|
-
return Object.keys(RESERVED_MERGE_FIELDS).filter(
|
|
2569
|
-
(name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
|
|
2570
|
-
);
|
|
2571
|
-
}
|
|
2572
|
-
|
|
2573
2963
|
// src/workspace.ts
|
|
2574
|
-
var CompanyId =
|
|
2964
|
+
var CompanyId = import_zod9.z.string().min(1).max(64);
|
|
2575
2965
|
var ASSET_POLICIES = ["any", "service_only"];
|
|
2576
|
-
var AssetPolicy =
|
|
2966
|
+
var AssetPolicy = import_zod9.z.enum(ASSET_POLICIES);
|
|
2577
2967
|
var MAX_ALLOWED_ASSET_HOSTS = 20;
|
|
2578
|
-
var AssetHost =
|
|
2968
|
+
var AssetHost = import_zod9.z.string().trim().toLowerCase().min(1).max(253).regex(/^(?=.{1,253}(?::\d{1,5})?$)[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+(:\d{1,5})?$/, "a host name such as example.com");
|
|
2579
2969
|
var MAX_MERGE_DEFAULTS = 50;
|
|
2580
2970
|
var MAX_MERGE_DEFAULT_LENGTH = 2e3;
|
|
2581
|
-
var MergeFieldName =
|
|
2582
|
-
var MergeDefaultValue =
|
|
2583
|
-
var MergeDefaults =
|
|
2971
|
+
var MergeFieldName = import_zod9.z.string().regex(/^[a-z][a-z0-9_]*$/, "a merge-field name: lowercase letters, digits and underscores, starting with a letter").max(64);
|
|
2972
|
+
var MergeDefaultValue = import_zod9.z.union([import_zod9.z.string().max(MAX_MERGE_DEFAULT_LENGTH), import_zod9.z.number().finite(), import_zod9.z.boolean()]);
|
|
2973
|
+
var MergeDefaults = import_zod9.z.record(MergeFieldName, MergeDefaultValue).refine((m) => Object.keys(m).length <= MAX_MERGE_DEFAULTS, `at most ${MAX_MERGE_DEFAULTS} merge defaults`).refine((m) => !Object.keys(m).some((k) => k in RESERVED_MERGE_FIELDS), {
|
|
2584
2974
|
message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
|
|
2585
2975
|
});
|
|
2586
|
-
var WorkspaceSettings =
|
|
2976
|
+
var WorkspaceSettings = import_zod9.z.object({
|
|
2587
2977
|
/**
|
|
2588
2978
|
* The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
|
|
2589
2979
|
* language of a new template, and of the hosted pages when nothing else
|
|
@@ -2596,9 +2986,9 @@ var WorkspaceSettings = import_zod8.z.object({
|
|
|
2596
2986
|
* each; the hosted pages are offered in those of them they support (English,
|
|
2597
2987
|
* German, French, Italian, Spanish). The service stores canonical casing.
|
|
2598
2988
|
*/
|
|
2599
|
-
locales:
|
|
2989
|
+
locales: import_zod9.z.array(LocaleTag).min(1).max(20).refine((l) => new Set(l.map((t) => t.toLowerCase())).size === l.length, "a language appears twice"),
|
|
2600
2990
|
/** Open and click tracking. Off by default, opt-in per workspace. */
|
|
2601
|
-
tracking_enabled:
|
|
2991
|
+
tracking_enabled: import_zod9.z.boolean(),
|
|
2602
2992
|
/** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
|
|
2603
2993
|
asset_policy: AssetPolicy,
|
|
2604
2994
|
/**
|
|
@@ -2606,7 +2996,7 @@ var WorkspaceSettings = import_zod8.z.object({
|
|
|
2606
2996
|
* the workspace's website, typically. Empty by default. Ignored under `any`,
|
|
2607
2997
|
* which allows everything already.
|
|
2608
2998
|
*/
|
|
2609
|
-
allowed_asset_hosts:
|
|
2999
|
+
allowed_asset_hosts: import_zod9.z.array(AssetHost).max(MAX_ALLOWED_ASSET_HOSTS),
|
|
2610
3000
|
/** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
|
|
2611
3001
|
merge_defaults: MergeDefaults,
|
|
2612
3002
|
/**
|
|
@@ -2615,10 +3005,10 @@ var WorkspaceSettings = import_zod8.z.object({
|
|
|
2615
3005
|
*/
|
|
2616
3006
|
default_timezone: TimezoneName.nullable()
|
|
2617
3007
|
});
|
|
2618
|
-
var Workspace =
|
|
3008
|
+
var Workspace = import_zod9.z.object({
|
|
2619
3009
|
id: Id,
|
|
2620
3010
|
slug: Slug,
|
|
2621
|
-
name:
|
|
3011
|
+
name: import_zod9.z.string().min(1).max(120),
|
|
2622
3012
|
settings: WorkspaceSettings,
|
|
2623
3013
|
/**
|
|
2624
3014
|
* The auth-brain company (tenant) the workspace was created for, or null for
|
|
@@ -2629,13 +3019,13 @@ var Workspace = import_zod8.z.object({
|
|
|
2629
3019
|
created_at: Timestamp,
|
|
2630
3020
|
updated_at: Timestamp
|
|
2631
3021
|
});
|
|
2632
|
-
var WorkspaceCreate =
|
|
3022
|
+
var WorkspaceCreate = import_zod9.z.object({
|
|
2633
3023
|
slug: Slug,
|
|
2634
|
-
name:
|
|
3024
|
+
name: import_zod9.z.string().min(1).max(120),
|
|
2635
3025
|
settings: WorkspaceSettings.partial().optional(),
|
|
2636
|
-
owner:
|
|
3026
|
+
owner: import_zod9.z.object({
|
|
2637
3027
|
email: Email,
|
|
2638
|
-
name:
|
|
3028
|
+
name: import_zod9.z.string().max(200).nullable().optional()
|
|
2639
3029
|
}),
|
|
2640
3030
|
/**
|
|
2641
3031
|
* The signed-in person's active auth-brain company. The dashboard always
|
|
@@ -2645,49 +3035,49 @@ var WorkspaceCreate = import_zod8.z.object({
|
|
|
2645
3035
|
*/
|
|
2646
3036
|
company_id: CompanyId.optional()
|
|
2647
3037
|
});
|
|
2648
|
-
var WorkspaceUpdate =
|
|
2649
|
-
name:
|
|
3038
|
+
var WorkspaceUpdate = import_zod9.z.object({
|
|
3039
|
+
name: import_zod9.z.string().min(1).max(120),
|
|
2650
3040
|
settings: WorkspaceSettings.partial()
|
|
2651
3041
|
}).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
2652
|
-
var WorkspaceMove =
|
|
3042
|
+
var WorkspaceMove = import_zod9.z.object({ company_id: CompanyId });
|
|
2653
3043
|
var MEMBER_ROLES = ["owner", "admin", "editor", "viewer"];
|
|
2654
|
-
var MemberRole =
|
|
3044
|
+
var MemberRole = import_zod9.z.enum(MEMBER_ROLES);
|
|
2655
3045
|
var WorkspaceMembership = Workspace.extend({ role: MemberRole });
|
|
2656
|
-
var Member =
|
|
3046
|
+
var Member = import_zod9.z.object({
|
|
2657
3047
|
id: Id,
|
|
2658
3048
|
/** The person's auth-brain subject. */
|
|
2659
|
-
subject:
|
|
3049
|
+
subject: import_zod9.z.string().min(1).max(255),
|
|
2660
3050
|
email: Email,
|
|
2661
|
-
name:
|
|
3051
|
+
name: import_zod9.z.string().max(200).nullable(),
|
|
2662
3052
|
role: MemberRole,
|
|
2663
3053
|
created_at: Timestamp
|
|
2664
3054
|
});
|
|
2665
|
-
var MemberCreate =
|
|
2666
|
-
subject:
|
|
3055
|
+
var MemberCreate = import_zod9.z.object({
|
|
3056
|
+
subject: import_zod9.z.string().min(1).max(255),
|
|
2667
3057
|
email: Email,
|
|
2668
|
-
name:
|
|
3058
|
+
name: import_zod9.z.string().max(200).nullable().optional(),
|
|
2669
3059
|
role: MemberRole
|
|
2670
3060
|
});
|
|
2671
|
-
var MemberUpdate =
|
|
3061
|
+
var MemberUpdate = import_zod9.z.object({ role: MemberRole });
|
|
2672
3062
|
var API_KEY_SCOPES = ["full", "read", "send"];
|
|
2673
|
-
var ApiKeyScope =
|
|
2674
|
-
var ApiKey =
|
|
3063
|
+
var ApiKeyScope = import_zod9.z.enum(API_KEY_SCOPES);
|
|
3064
|
+
var ApiKey = import_zod9.z.object({
|
|
2675
3065
|
id: Id,
|
|
2676
|
-
name:
|
|
3066
|
+
name: import_zod9.z.string().min(1).max(120),
|
|
2677
3067
|
/** The first characters of the key, for recognising it in a list. */
|
|
2678
|
-
prefix:
|
|
3068
|
+
prefix: import_zod9.z.string().min(4).max(32),
|
|
2679
3069
|
scope: ApiKeyScope,
|
|
2680
3070
|
last_used_at: Timestamp.nullable(),
|
|
2681
3071
|
revoked_at: Timestamp.nullable(),
|
|
2682
3072
|
created_at: Timestamp
|
|
2683
3073
|
});
|
|
2684
|
-
var ApiKeyCreate =
|
|
2685
|
-
name:
|
|
3074
|
+
var ApiKeyCreate = import_zod9.z.object({
|
|
3075
|
+
name: import_zod9.z.string().min(1).max(120),
|
|
2686
3076
|
/** Omitted means `full`. */
|
|
2687
3077
|
scope: ApiKeyScope.optional()
|
|
2688
3078
|
});
|
|
2689
|
-
var ApiKeyCreated =
|
|
2690
|
-
key:
|
|
3079
|
+
var ApiKeyCreated = import_zod9.z.object({
|
|
3080
|
+
key: import_zod9.z.string().min(20),
|
|
2691
3081
|
api_key: ApiKey
|
|
2692
3082
|
});
|
|
2693
3083
|
var AUDIT_ACTIONS = [
|
|
@@ -2775,29 +3165,29 @@ var AUDIT_ACTIONS = [
|
|
|
2775
3165
|
"automation.run_ended",
|
|
2776
3166
|
"event.recorded"
|
|
2777
3167
|
];
|
|
2778
|
-
var AuditAction =
|
|
2779
|
-
var AuditActor =
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
type:
|
|
3168
|
+
var AuditAction = import_zod9.z.enum(AUDIT_ACTIONS);
|
|
3169
|
+
var AuditActor = import_zod9.z.discriminatedUnion("type", [
|
|
3170
|
+
import_zod9.z.object({ type: import_zod9.z.literal("member"), member_id: Id, subject: import_zod9.z.string().min(1) }),
|
|
3171
|
+
import_zod9.z.object({
|
|
3172
|
+
type: import_zod9.z.literal("api_key"),
|
|
2783
3173
|
api_key_id: Id,
|
|
2784
3174
|
/**
|
|
2785
3175
|
* Who the client's application said it was acting for, from
|
|
2786
3176
|
* `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
|
|
2787
3177
|
* Absent when the call carried no label.
|
|
2788
3178
|
*/
|
|
2789
|
-
on_behalf_of:
|
|
3179
|
+
on_behalf_of: import_zod9.z.string().min(1).max(ON_BEHALF_OF_MAX_LENGTH).optional()
|
|
2790
3180
|
}),
|
|
2791
3181
|
/** The service itself (the worker, a bounce, the hosted unsubscribe page). */
|
|
2792
|
-
|
|
3182
|
+
import_zod9.z.object({ type: import_zod9.z.literal("system"), reason: import_zod9.z.string().min(1).max(200) })
|
|
2793
3183
|
]);
|
|
2794
|
-
var AuditEntry =
|
|
3184
|
+
var AuditEntry = import_zod9.z.object({
|
|
2795
3185
|
id: Id,
|
|
2796
3186
|
action: AuditAction,
|
|
2797
3187
|
actor: AuditActor,
|
|
2798
|
-
target_type:
|
|
3188
|
+
target_type: import_zod9.z.string().min(1).max(64),
|
|
2799
3189
|
target_id: Id.nullable(),
|
|
2800
|
-
details:
|
|
3190
|
+
details: import_zod9.z.record(import_zod9.z.unknown()),
|
|
2801
3191
|
created_at: Timestamp
|
|
2802
3192
|
});
|
|
2803
3193
|
var AuditQuery = PageQuery.extend({
|
|
@@ -2806,12 +3196,12 @@ var AuditQuery = PageQuery.extend({
|
|
|
2806
3196
|
});
|
|
2807
3197
|
|
|
2808
3198
|
// src/invites.ts
|
|
2809
|
-
var
|
|
3199
|
+
var import_zod10 = require("zod");
|
|
2810
3200
|
var INVITE_STATUSES = ["pending", "accepted", "revoked", "expired"];
|
|
2811
|
-
var InviteStatus =
|
|
3201
|
+
var InviteStatus = import_zod10.z.enum(INVITE_STATUSES);
|
|
2812
3202
|
var DEFAULT_INVITE_TTL_DAYS = 7;
|
|
2813
3203
|
var MAX_INVITE_TTL_DAYS = 30;
|
|
2814
|
-
var Invite =
|
|
3204
|
+
var Invite = import_zod10.z.object({
|
|
2815
3205
|
id: Id,
|
|
2816
3206
|
/** Stored lowercased; only a person signed in with this address can accept. */
|
|
2817
3207
|
email: Email,
|
|
@@ -2819,32 +3209,32 @@ var Invite = import_zod9.z.object({
|
|
|
2819
3209
|
/** Derived from the timestamps below, never stored. */
|
|
2820
3210
|
status: InviteStatus,
|
|
2821
3211
|
/** The member who invited, while they are still one (null after they left). */
|
|
2822
|
-
invited_by:
|
|
3212
|
+
invited_by: import_zod10.z.object({ member_id: Id.nullable(), email: Email.nullable() }),
|
|
2823
3213
|
expires_at: Timestamp,
|
|
2824
3214
|
accepted_at: Timestamp.nullable(),
|
|
2825
3215
|
revoked_at: Timestamp.nullable(),
|
|
2826
3216
|
created_at: Timestamp
|
|
2827
3217
|
});
|
|
2828
|
-
var InviteCreate =
|
|
3218
|
+
var InviteCreate = import_zod10.z.object({
|
|
2829
3219
|
email: Email,
|
|
2830
3220
|
role: MemberRole,
|
|
2831
|
-
expires_in_days:
|
|
3221
|
+
expires_in_days: import_zod10.z.number().int().min(1).max(MAX_INVITE_TTL_DAYS).optional()
|
|
2832
3222
|
});
|
|
2833
|
-
var InviteCreated =
|
|
3223
|
+
var InviteCreated = import_zod10.z.object({
|
|
2834
3224
|
invite: Invite,
|
|
2835
|
-
token:
|
|
3225
|
+
token: import_zod10.z.string().min(32).max(128)
|
|
2836
3226
|
});
|
|
2837
3227
|
var InviteListQuery = PageQuery.extend({
|
|
2838
3228
|
status: InviteStatus.optional()
|
|
2839
3229
|
});
|
|
2840
|
-
var InviteAccept =
|
|
2841
|
-
token:
|
|
3230
|
+
var InviteAccept = import_zod10.z.object({
|
|
3231
|
+
token: import_zod10.z.string().min(32).max(128),
|
|
2842
3232
|
email: Email,
|
|
2843
|
-
name:
|
|
3233
|
+
name: import_zod10.z.string().max(200).nullable().optional()
|
|
2844
3234
|
});
|
|
2845
|
-
var InviteAccepted =
|
|
3235
|
+
var InviteAccepted = import_zod10.z.object({
|
|
2846
3236
|
workspace: WorkspaceMembership,
|
|
2847
|
-
already_member:
|
|
3237
|
+
already_member: import_zod10.z.boolean()
|
|
2848
3238
|
});
|
|
2849
3239
|
var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
|
|
2850
3240
|
|
|
@@ -2886,6 +3276,7 @@ function storedTemplateLine(value) {
|
|
|
2886
3276
|
function buildTemplatePatch(args) {
|
|
2887
3277
|
const { loaded, main, documentChanged, translations, changed, removed } = args;
|
|
2888
3278
|
const out = {};
|
|
3279
|
+
if (args.insertions !== void 0 && !sameJson(args.insertions, loaded.insertions ?? {})) out.insertions = args.insertions;
|
|
2889
3280
|
const subject = storedTemplateLine(main.subject);
|
|
2890
3281
|
if (subject !== storedTemplateLine(loaded.subject)) out.subject = subject;
|
|
2891
3282
|
const preheader = storedTemplateLine(main.preheader);
|
|
@@ -2895,7 +3286,13 @@ function buildTemplatePatch(args) {
|
|
|
2895
3286
|
for (const tag of changed) {
|
|
2896
3287
|
const version = translations[tag];
|
|
2897
3288
|
if (version && !removed.has(tag)) {
|
|
2898
|
-
patch[tag] = {
|
|
3289
|
+
patch[tag] = {
|
|
3290
|
+
subject: storedTemplateLine(version.subject),
|
|
3291
|
+
preheader: storedTemplateLine(version.preheader),
|
|
3292
|
+
document: version.document,
|
|
3293
|
+
status: version.status,
|
|
3294
|
+
...version.insertions !== void 0 ? { insertions: version.insertions } : {}
|
|
3295
|
+
};
|
|
2899
3296
|
}
|
|
2900
3297
|
}
|
|
2901
3298
|
for (const tag of removed) {
|
|
@@ -2904,6 +3301,10 @@ function buildTemplatePatch(args) {
|
|
|
2904
3301
|
if (Object.keys(patch).length > 0) out.translations = patch;
|
|
2905
3302
|
return out;
|
|
2906
3303
|
}
|
|
3304
|
+
function sameJson(a, b) {
|
|
3305
|
+
const norm = (v) => Array.isArray(v) ? v.map(norm) : v !== null && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, norm(v[k])])) : v;
|
|
3306
|
+
return JSON.stringify(norm(a)) === JSON.stringify(norm(b));
|
|
3307
|
+
}
|
|
2907
3308
|
function restorePatch(old, current, workspaceLocales) {
|
|
2908
3309
|
const oldMain = canonicalLocale(old.locale) ?? old.locale;
|
|
2909
3310
|
const listed = workspaceLocales ? new Set(workspaceLocales.map((t) => canonicalLocale(t) ?? t)) : null;
|
|
@@ -2918,7 +3319,7 @@ function restorePatch(old, current, workspaceLocales) {
|
|
|
2918
3319
|
untouched.add(canonical);
|
|
2919
3320
|
continue;
|
|
2920
3321
|
}
|
|
2921
|
-
patch[canonical] = version;
|
|
3322
|
+
patch[canonical] = { ...version, insertions: version.insertions ?? {} };
|
|
2922
3323
|
}
|
|
2923
3324
|
for (const tag of Object.keys(current.translations)) {
|
|
2924
3325
|
const canonical = canonicalLocale(tag) ?? tag;
|
|
@@ -2930,15 +3331,20 @@ function restorePatch(old, current, workspaceLocales) {
|
|
|
2930
3331
|
subject: old.subject,
|
|
2931
3332
|
preheader: old.preheader,
|
|
2932
3333
|
document: old.document,
|
|
3334
|
+
insertions: old.insertions ?? {},
|
|
2933
3335
|
...Object.keys(patch).length > 0 ? { translations: patch } : {}
|
|
2934
3336
|
};
|
|
2935
3337
|
return { update, skipped: skipped.sort() };
|
|
2936
3338
|
}
|
|
2937
3339
|
function draftsFromTemplate(template) {
|
|
3340
|
+
const insertions = template.insertions ?? {};
|
|
2938
3341
|
const translations = {};
|
|
2939
|
-
for (const [tag, version] of Object.entries(template.translations))
|
|
3342
|
+
for (const [tag, version] of Object.entries(template.translations)) {
|
|
3343
|
+
translations[canonicalLocale(tag) ?? tag] = { ...version, insertions: alignTranslationInsertions(insertions, version.insertions) };
|
|
3344
|
+
}
|
|
2940
3345
|
return {
|
|
2941
3346
|
main: { subject: template.subject ?? "", preheader: template.preheader ?? "", document: template.document },
|
|
3347
|
+
insertions,
|
|
2942
3348
|
translations,
|
|
2943
3349
|
mainDocumentChanged: false,
|
|
2944
3350
|
changed: [],
|
|
@@ -2962,13 +3368,19 @@ function addLanguageDraft(drafts, locale) {
|
|
|
2962
3368
|
subject: drafts.main.subject,
|
|
2963
3369
|
preheader: drafts.main.preheader,
|
|
2964
3370
|
document: structuredClone(drafts.main.document),
|
|
2965
|
-
status: "draft"
|
|
3371
|
+
status: "draft",
|
|
3372
|
+
insertions: copyInsertionTexts(drafts.insertions)
|
|
2966
3373
|
}
|
|
2967
3374
|
},
|
|
2968
3375
|
changed: withChanged(drafts.changed, tag),
|
|
2969
3376
|
removed: without(drafts.removed, tag)
|
|
2970
3377
|
};
|
|
2971
3378
|
}
|
|
3379
|
+
function copyInsertionTexts(insertions) {
|
|
3380
|
+
const out = {};
|
|
3381
|
+
for (const [name, def] of Object.entries(insertions)) out[name] = { choices: { ...def.choices }, filled: def.filled, otherwise: def.otherwise };
|
|
3382
|
+
return out;
|
|
3383
|
+
}
|
|
2972
3384
|
function removeLanguageDraft(drafts, locale) {
|
|
2973
3385
|
const tag = canonicalLocale(locale) ?? locale;
|
|
2974
3386
|
const { [tag]: _gone, ...rest } = drafts.translations;
|
|
@@ -3001,6 +3413,7 @@ function setLanguageStatus(drafts, locale, status) {
|
|
|
3001
3413
|
function draftsPatch(drafts, loaded) {
|
|
3002
3414
|
return buildTemplatePatch({
|
|
3003
3415
|
loaded,
|
|
3416
|
+
insertions: drafts.insertions,
|
|
3004
3417
|
main: drafts.main,
|
|
3005
3418
|
documentChanged: drafts.mainDocumentChanged,
|
|
3006
3419
|
translations: drafts.translations,
|
|
@@ -3017,6 +3430,7 @@ function readyWithoutSubject(drafts) {
|
|
|
3017
3430
|
function rebaseDrafts(sent, now, saved) {
|
|
3018
3431
|
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
3019
3432
|
let out = draftsFromTemplate(saved);
|
|
3433
|
+
if (!sameJson(now.insertions, sent.insertions)) out = { ...out, insertions: now.insertions };
|
|
3020
3434
|
if (now.main.subject !== sent.main.subject) out = editLanguageDraft(out, null, { subject: now.main.subject });
|
|
3021
3435
|
if (now.main.preheader !== sent.main.preheader) out = editLanguageDraft(out, null, { preheader: now.main.preheader });
|
|
3022
3436
|
out = same(now.main.document, sent.main.document) ? keepLanguageDocument(out, null, now.main.document) : editLanguageDraft(out, null, { document: now.main.document });
|
|
@@ -3039,17 +3453,321 @@ function rebaseDrafts(sent, now, saved) {
|
|
|
3039
3453
|
removed: without(out.removed, tag)
|
|
3040
3454
|
};
|
|
3041
3455
|
}
|
|
3456
|
+
return sameJson(out.insertions, saved.insertions ?? {}) ? out : alignDrafts(out);
|
|
3457
|
+
}
|
|
3458
|
+
function alignDrafts(drafts) {
|
|
3459
|
+
const translations = {};
|
|
3460
|
+
for (const [tag, version] of Object.entries(drafts.translations)) {
|
|
3461
|
+
translations[tag] = { ...version, insertions: alignTranslationInsertions(drafts.insertions, version.insertions) };
|
|
3462
|
+
}
|
|
3463
|
+
return { ...drafts, translations };
|
|
3464
|
+
}
|
|
3465
|
+
function editMainInsertions(drafts, insertions) {
|
|
3466
|
+
return alignDrafts({ ...drafts, insertions });
|
|
3467
|
+
}
|
|
3468
|
+
function renameKey(record, from, to) {
|
|
3469
|
+
const out = {};
|
|
3470
|
+
for (const [k, v] of Object.entries(record)) out[k === from ? to : k] = v;
|
|
3042
3471
|
return out;
|
|
3043
3472
|
}
|
|
3473
|
+
function renameInsertion(drafts, from, to) {
|
|
3474
|
+
if (from === to || !ownEntry(drafts.insertions, from) || ownEntry(drafts.insertions, to)) return drafts;
|
|
3475
|
+
const translations = {};
|
|
3476
|
+
let changed = drafts.changed;
|
|
3477
|
+
for (const [tag, version] of Object.entries(drafts.translations)) {
|
|
3478
|
+
if (version.insertions && ownEntry(version.insertions, from)) {
|
|
3479
|
+
translations[tag] = { ...version, insertions: renameKey(version.insertions, from, to) };
|
|
3480
|
+
changed = withChanged(changed, tag);
|
|
3481
|
+
} else translations[tag] = version;
|
|
3482
|
+
}
|
|
3483
|
+
return { ...drafts, insertions: renameKey(drafts.insertions, from, to), translations, changed };
|
|
3484
|
+
}
|
|
3485
|
+
function renameInsertionChoice(drafts, name, from, to) {
|
|
3486
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3487
|
+
if (!def || from === to || INSERTION_TEXT_SLOTS.includes(to) || ownEntry(def.choices, from) === void 0 || ownEntry(def.choices, to) !== void 0) {
|
|
3488
|
+
return drafts;
|
|
3489
|
+
}
|
|
3490
|
+
const translations = {};
|
|
3491
|
+
let changed = drafts.changed;
|
|
3492
|
+
for (const [tag, version] of Object.entries(drafts.translations)) {
|
|
3493
|
+
const t = ownEntry(version.insertions, name);
|
|
3494
|
+
if (t && Object.prototype.hasOwnProperty.call(t.choices, from)) {
|
|
3495
|
+
translations[tag] = { ...version, insertions: { ...version.insertions, [name]: { ...t, choices: renameKey(t.choices, from, to) } } };
|
|
3496
|
+
changed = withChanged(changed, tag);
|
|
3497
|
+
} else translations[tag] = version;
|
|
3498
|
+
}
|
|
3499
|
+
return {
|
|
3500
|
+
...drafts,
|
|
3501
|
+
insertions: { ...drafts.insertions, [name]: { ...def, choices: renameKey(def.choices, from, to) } },
|
|
3502
|
+
translations,
|
|
3503
|
+
changed
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
function editInsertionText(drafts, locale, name, choice, text) {
|
|
3507
|
+
const version = drafts.translations[locale];
|
|
3508
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3509
|
+
if (!version || !def) return drafts;
|
|
3510
|
+
const aligned = alignTranslationInsertions(drafts.insertions, version.insertions);
|
|
3511
|
+
const current = aligned[name];
|
|
3512
|
+
let next2;
|
|
3513
|
+
if (choice === "filled") {
|
|
3514
|
+
if (def.filled === null) return drafts;
|
|
3515
|
+
next2 = { ...current, filled: text };
|
|
3516
|
+
} else if (choice === "otherwise") next2 = { ...current, otherwise: text };
|
|
3517
|
+
else if (Object.prototype.hasOwnProperty.call(def.choices, choice)) next2 = { ...current, choices: { ...current.choices, [choice]: text } };
|
|
3518
|
+
else return drafts;
|
|
3519
|
+
return {
|
|
3520
|
+
...drafts,
|
|
3521
|
+
translations: { ...drafts.translations, [locale]: { ...version, insertions: { ...aligned, [name]: next2 } } },
|
|
3522
|
+
changed: withChanged(drafts.changed, locale)
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
function missingInsertionTexts(drafts, locale) {
|
|
3526
|
+
const version = drafts.translations[locale];
|
|
3527
|
+
return version ? insertionCoverage(drafts.insertions, version.insertions) : [];
|
|
3528
|
+
}
|
|
3529
|
+
function readyWithoutInsertions(drafts) {
|
|
3530
|
+
return Object.entries(drafts.translations).filter(([tag, v]) => drafts.changed.includes(tag) && v.status === "ready" && insertionCoverage(drafts.insertions, v.insertions).length > 0).map(([tag]) => tag);
|
|
3531
|
+
}
|
|
3532
|
+
function demotedBySave(drafts, loaded) {
|
|
3533
|
+
if (sameJson(drafts.insertions, loaded.insertions ?? {})) return [];
|
|
3534
|
+
return Object.entries(drafts.translations).filter(([tag, v]) => !drafts.changed.includes(tag) && v.status === "ready" && insertionCoverage(drafts.insertions, v.insertions).length > 0).map(([tag]) => tag).sort();
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
// src/insertion-editing.ts
|
|
3538
|
+
var has = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
3539
|
+
function insertionNameProblem(insertions, name, current) {
|
|
3540
|
+
if (!InsertionName.safeParse(name).success) return "Use lowercase letters, digits and underscores, starting with a letter (like opening).";
|
|
3541
|
+
if (has(RESERVED_MERGE_FIELDS, name)) return `"${name}" is a merge field the service fills itself. Choose another name.`;
|
|
3542
|
+
if (name !== current && has(insertions, name)) return `There is already an insertion called "${name}".`;
|
|
3543
|
+
const chooser = Object.entries(insertions).find(([other, def]) => def.by === name && other !== current);
|
|
3544
|
+
if (chooser) return `"${name}" is the value that chooses "${chooser[0]}". Choose another name.`;
|
|
3545
|
+
if (current !== void 0 && ownEntry(insertions, current)?.by === name) return `"${name}" is the value that chooses this insertion. Choose another name.`;
|
|
3546
|
+
return null;
|
|
3547
|
+
}
|
|
3548
|
+
function insertionByProblem(insertions, by, self) {
|
|
3549
|
+
if (!InsertionName.safeParse(by).success) return "The value that chooses is a merge-field name: lowercase letters, digits and underscores (like reason).";
|
|
3550
|
+
if (by === "unsubscribe_url") return "The unsubscribe link cannot choose a text. Use a value the client or the contact provides.";
|
|
3551
|
+
if (by === self || has(insertions, by)) return `"${by}" is an insertion, not a value. Use a value the client or the contact provides.`;
|
|
3552
|
+
return null;
|
|
3553
|
+
}
|
|
3554
|
+
function normalizeChoiceKey(raw) {
|
|
3555
|
+
return raw.trim().toLowerCase();
|
|
3556
|
+
}
|
|
3557
|
+
function choiceKeyProblem(insertion, key, current) {
|
|
3558
|
+
if (INSERTION_TEXT_SLOTS.includes(key)) {
|
|
3559
|
+
return `"${key}" names the insertion's ${key === "filled" ? '"any other filled value"' : '"otherwise"'} text, so it cannot be a choice. Choose another value.`;
|
|
3560
|
+
}
|
|
3561
|
+
if (!InsertionChoiceKey.safeParse(key).success) return "A choice is the value a client sends: lowercase letters, digits, hyphens and underscores (like venue).";
|
|
3562
|
+
if (key !== current && has(insertion.choices, key)) return `There is already a choice "${key}".`;
|
|
3563
|
+
return null;
|
|
3564
|
+
}
|
|
3565
|
+
function addInsertion(drafts, rawName, rawBy) {
|
|
3566
|
+
const name = rawName.trim();
|
|
3567
|
+
const by = rawBy.trim();
|
|
3568
|
+
if (Object.keys(drafts.insertions).length >= MAX_INSERTIONS) return { ok: false, message: `A template holds at most ${MAX_INSERTIONS} insertions.` };
|
|
3569
|
+
const nameProblem = insertionNameProblem(drafts.insertions, name);
|
|
3570
|
+
if (nameProblem) return { ok: false, message: nameProblem };
|
|
3571
|
+
if (name === by) return { ok: false, message: "An insertion cannot be chosen by its own name. Use the value the client sends (like reason)." };
|
|
3572
|
+
const byProblem = insertionByProblem(drafts.insertions, by);
|
|
3573
|
+
if (byProblem) return { ok: false, message: byProblem };
|
|
3574
|
+
return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { by, choices: {}, filled: null, otherwise: "" } }) };
|
|
3575
|
+
}
|
|
3576
|
+
function removeInsertion(drafts, name) {
|
|
3577
|
+
if (!has(drafts.insertions, name)) return drafts;
|
|
3578
|
+
const { [name]: _gone, ...rest } = drafts.insertions;
|
|
3579
|
+
return editMainInsertions(drafts, rest);
|
|
3580
|
+
}
|
|
3581
|
+
function renameInsertionChecked(drafts, from, rawTo) {
|
|
3582
|
+
const to = rawTo.trim();
|
|
3583
|
+
if (to === from) return { ok: true, drafts };
|
|
3584
|
+
const problem = insertionNameProblem(drafts.insertions, to, from);
|
|
3585
|
+
if (problem) return { ok: false, message: problem };
|
|
3586
|
+
return { ok: true, drafts: renameInsertion(drafts, from, to) };
|
|
3587
|
+
}
|
|
3588
|
+
function setInsertionBy(drafts, name, rawBy) {
|
|
3589
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3590
|
+
if (!def) return { ok: false, message: `There is no insertion "${name}".` };
|
|
3591
|
+
const by = rawBy.trim();
|
|
3592
|
+
const problem = insertionByProblem(drafts.insertions, by, name);
|
|
3593
|
+
if (problem) return { ok: false, message: problem };
|
|
3594
|
+
if (by === def.by) return { ok: true, drafts };
|
|
3595
|
+
return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, by } }) };
|
|
3596
|
+
}
|
|
3597
|
+
function addInsertionChoice(drafts, name, rawKey) {
|
|
3598
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3599
|
+
if (!def) return { ok: false, message: `There is no insertion "${name}".` };
|
|
3600
|
+
if (Object.keys(def.choices).length >= MAX_INSERTION_CHOICES) return { ok: false, message: `An insertion holds at most ${MAX_INSERTION_CHOICES} choices.` };
|
|
3601
|
+
const key = normalizeChoiceKey(rawKey);
|
|
3602
|
+
const problem = choiceKeyProblem(def, key);
|
|
3603
|
+
if (problem) return { ok: false, message: problem };
|
|
3604
|
+
return { ok: true, drafts: editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, choices: { ...def.choices, [key]: "" } } }) };
|
|
3605
|
+
}
|
|
3606
|
+
function removeInsertionChoice(drafts, name, key) {
|
|
3607
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3608
|
+
if (!def || !has(def.choices, key)) return drafts;
|
|
3609
|
+
const { [key]: _gone, ...choices } = def.choices;
|
|
3610
|
+
return editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, choices } });
|
|
3611
|
+
}
|
|
3612
|
+
function renameInsertionChoiceChecked(drafts, name, from, rawTo) {
|
|
3613
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3614
|
+
if (!def) return { ok: false, message: `There is no insertion "${name}".` };
|
|
3615
|
+
const to = normalizeChoiceKey(rawTo);
|
|
3616
|
+
if (to === from) return { ok: true, drafts };
|
|
3617
|
+
const problem = choiceKeyProblem(def, to, from);
|
|
3618
|
+
if (problem) return { ok: false, message: problem };
|
|
3619
|
+
return { ok: true, drafts: renameInsertionChoice(drafts, name, from, to) };
|
|
3620
|
+
}
|
|
3621
|
+
function setInsertionFilled(drafts, name, on) {
|
|
3622
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3623
|
+
if (!def || def.filled !== null === on) return drafts;
|
|
3624
|
+
return editMainInsertions(drafts, { ...drafts.insertions, [name]: { ...def, filled: on ? "" : null } });
|
|
3625
|
+
}
|
|
3626
|
+
function editMainInsertionText(drafts, name, choice, text) {
|
|
3627
|
+
const def = ownEntry(drafts.insertions, name);
|
|
3628
|
+
if (!def) return drafts;
|
|
3629
|
+
let next2;
|
|
3630
|
+
if (choice === "otherwise") next2 = { ...def, otherwise: text };
|
|
3631
|
+
else if (choice === "filled") {
|
|
3632
|
+
if (def.filled === null) return drafts;
|
|
3633
|
+
next2 = { ...def, filled: text };
|
|
3634
|
+
} else if (has(def.choices, choice)) next2 = { ...def, choices: { ...def.choices, [choice]: text } };
|
|
3635
|
+
else return drafts;
|
|
3636
|
+
return editMainInsertions(drafts, { ...drafts.insertions, [name]: next2 });
|
|
3637
|
+
}
|
|
3638
|
+
function mainInsertionText(insertion, choice) {
|
|
3639
|
+
if (choice === "otherwise") return insertion.otherwise;
|
|
3640
|
+
if (choice === "filled") return insertion.filled;
|
|
3641
|
+
return ownEntry(insertion.choices, choice) ?? null;
|
|
3642
|
+
}
|
|
3643
|
+
function translationTextFromInput(mainText, value) {
|
|
3644
|
+
if (value.trim() !== "") return value;
|
|
3645
|
+
return mainText !== null && mainText.trim() === "" ? "" : null;
|
|
3646
|
+
}
|
|
3647
|
+
function insertionTextSlots(insertion) {
|
|
3648
|
+
return [...Object.keys(insertion.choices), ...insertion.filled !== null ? ["filled"] : [], "otherwise"];
|
|
3649
|
+
}
|
|
3650
|
+
function insertionSlotLabel(choice) {
|
|
3651
|
+
if (choice === "filled") return "any other filled value";
|
|
3652
|
+
if (choice === "otherwise") return "otherwise";
|
|
3653
|
+
return `'${choice}'`;
|
|
3654
|
+
}
|
|
3655
|
+
function joinWords(items) {
|
|
3656
|
+
if (items.length <= 1) return items.join("");
|
|
3657
|
+
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
|
|
3658
|
+
}
|
|
3659
|
+
function describeMissingTexts(missing) {
|
|
3660
|
+
if (missing.length === 0) return "";
|
|
3661
|
+
return `no text for ${joinWords(missing.map((m) => `${insertionSlotLabel(m.choice)} in ${m.insertion}`))}`;
|
|
3662
|
+
}
|
|
3663
|
+
function saveDemotions(drafts, loaded) {
|
|
3664
|
+
const tags = [.../* @__PURE__ */ new Set([...demotedBySave(drafts, loaded), ...readyWithoutInsertions(drafts)])].sort();
|
|
3665
|
+
return tags.map((locale) => ({ locale, missing: insertionCoverage(drafts.insertions, ownEntry(drafts.translations, locale)?.insertions) }));
|
|
3666
|
+
}
|
|
3667
|
+
function demoteForSave(drafts) {
|
|
3668
|
+
let out = drafts;
|
|
3669
|
+
for (const tag of readyWithoutInsertions(drafts)) out = setLanguageStatus(out, tag, "draft");
|
|
3670
|
+
return out;
|
|
3671
|
+
}
|
|
3672
|
+
function draftInsertionProblems(drafts) {
|
|
3673
|
+
const out = [];
|
|
3674
|
+
const parsed = Insertions.safeParse(drafts.insertions);
|
|
3675
|
+
if (!parsed.success) {
|
|
3676
|
+
for (const issue of parsed.error.issues) out.push({ locale: null, reason: "invalid", path: issue.path.join("."), message: issue.message });
|
|
3677
|
+
return out;
|
|
3678
|
+
}
|
|
3679
|
+
for (const p of insertionProblems(drafts.insertions)) out.push({ ...p, locale: null });
|
|
3680
|
+
const places = (subject, preheader, document) => ({ subject, preheader, document: JSON.stringify(document) });
|
|
3681
|
+
for (const p of placementProblems(places(drafts.main.subject, drafts.main.preheader, drafts.main.document), drafts.insertions)) out.push({ ...p, locale: null });
|
|
3682
|
+
for (const [tag, version] of Object.entries(drafts.translations)) {
|
|
3683
|
+
if (!drafts.changed.includes(tag)) continue;
|
|
3684
|
+
for (const p of translationInsertionProblems(drafts.insertions, version.insertions ?? {})) out.push({ ...p, locale: tag });
|
|
3685
|
+
for (const p of placementProblems(places(version.subject, version.preheader, version.document), drafts.insertions)) out.push({ ...p, locale: tag });
|
|
3686
|
+
}
|
|
3687
|
+
return out;
|
|
3688
|
+
}
|
|
3689
|
+
var PREVIEW_FILLED = ":filled";
|
|
3690
|
+
var PREVIEW_EMPTY = ":empty";
|
|
3691
|
+
function previewControls(insertions) {
|
|
3692
|
+
return Object.entries(insertionInputs(insertions)).map(([by, input]) => ({
|
|
3693
|
+
by,
|
|
3694
|
+
insertions: input.insertions,
|
|
3695
|
+
options: [
|
|
3696
|
+
...input.keys.map((key) => ({ value: key, label: key })),
|
|
3697
|
+
...input.filled ? [{ value: PREVIEW_FILLED, label: "any other value" }] : [],
|
|
3698
|
+
{ value: PREVIEW_EMPTY, label: "empty" }
|
|
3699
|
+
]
|
|
3700
|
+
}));
|
|
3701
|
+
}
|
|
3702
|
+
function defaultPreviewPick(control) {
|
|
3703
|
+
return control.options[0].value;
|
|
3704
|
+
}
|
|
3705
|
+
function effectivePreviewPicks(controls, picks) {
|
|
3706
|
+
const out = {};
|
|
3707
|
+
for (const control of controls) {
|
|
3708
|
+
const picked = ownEntry(picks, control.by);
|
|
3709
|
+
out[control.by] = picked !== void 0 && control.options.some((o) => o.value === picked) ? picked : defaultPreviewPick(control);
|
|
3710
|
+
}
|
|
3711
|
+
return out;
|
|
3712
|
+
}
|
|
3713
|
+
function sampleFilledValue(by, keys) {
|
|
3714
|
+
const sample = by === "first_name" ? "Alex" : by === "last_name" ? "Morgan" : "Sample value";
|
|
3715
|
+
return keys.includes(sample.toLowerCase()) ? "Sample value" : sample;
|
|
3716
|
+
}
|
|
3717
|
+
function previewInsertions(main, translation, missing) {
|
|
3718
|
+
if (translation === void 0) return main;
|
|
3719
|
+
const aligned = alignTranslationInsertions(main, translation);
|
|
3720
|
+
const out = {};
|
|
3721
|
+
for (const [name, def] of Object.entries(main)) {
|
|
3722
|
+
const t = ownEntry(aligned, name);
|
|
3723
|
+
const choices = {};
|
|
3724
|
+
for (const key of Object.keys(def.choices)) choices[key] = ownEntry(t.choices, key) ?? missing;
|
|
3725
|
+
out[name] = { by: def.by, choices, filled: def.filled === null ? null : t.filled ?? missing, otherwise: t.otherwise ?? missing };
|
|
3726
|
+
}
|
|
3727
|
+
return out;
|
|
3728
|
+
}
|
|
3729
|
+
function previewPersonalization(insertions, picks) {
|
|
3730
|
+
const values = {};
|
|
3731
|
+
const controlled = /* @__PURE__ */ new Set();
|
|
3732
|
+
const shown = effectivePreviewPicks(previewControls(insertions), picks);
|
|
3733
|
+
for (const [by, input] of Object.entries(insertionInputs(insertions))) {
|
|
3734
|
+
controlled.add(by);
|
|
3735
|
+
const pick = shown[by];
|
|
3736
|
+
if (pick === PREVIEW_EMPTY) continue;
|
|
3737
|
+
values[by] = pick === PREVIEW_FILLED ? sampleFilledValue(by, input.keys) : pick;
|
|
3738
|
+
}
|
|
3739
|
+
const sample = sampleMergeValue(values);
|
|
3740
|
+
return {
|
|
3741
|
+
insertions: resolveInsertions(insertions, (name) => ownEntry(values, name)),
|
|
3742
|
+
value: (name, fallback) => controlled.has(name) ? sample(name, fallback) : `{{${name}${fallback === void 0 ? "" : `|${fallback}`}}}`
|
|
3743
|
+
};
|
|
3744
|
+
}
|
|
3745
|
+
var REFUSAL_HINTS = {
|
|
3746
|
+
ready_needs_insertions: "Write the missing texts in that language, or set it back to draft, then save.",
|
|
3747
|
+
insertion_name: "Rename the insertion: the names first_name, last_name, email and unsubscribe_url are filled by the service.",
|
|
3748
|
+
insertion_by: "Choose the insertion by a value the client or the contact provides, not by the unsubscribe link or another insertion.",
|
|
3749
|
+
insertion_choice_key: "Use lowercase letters, digits, hyphens and underscores for a choice.",
|
|
3750
|
+
insertion_text: "An insertion text can hold merge fields such as {{first_name}}, but not another insertion.",
|
|
3751
|
+
insertion_with_fallback: 'Remove the |fallback from the insertion in the subject, preheader or body: its "otherwise" text is the fallback.',
|
|
3752
|
+
unknown_translation_insertion: "A language names an insertion or a choice the main language no longer has. Reload the template and try again."
|
|
3753
|
+
};
|
|
3754
|
+
function insertionRefusalHint(details) {
|
|
3755
|
+
const reason = details?.reason;
|
|
3756
|
+
if (typeof reason !== "string" || !has(REFUSAL_HINTS, reason)) return null;
|
|
3757
|
+
const hint = REFUSAL_HINTS[reason];
|
|
3758
|
+
if (reason !== "ready_needs_insertions") return hint;
|
|
3759
|
+
const missing = Array.isArray(details?.missing) ? details.missing.filter((m) => typeof m === "object" && m !== null && typeof m.insertion === "string" && typeof m.choice === "string") : [];
|
|
3760
|
+
return missing.length > 0 ? `The language has ${describeMissingTexts(missing)}. ${hint}` : hint;
|
|
3761
|
+
}
|
|
3044
3762
|
|
|
3045
3763
|
// src/providers.ts
|
|
3046
|
-
var
|
|
3764
|
+
var import_zod11 = require("zod");
|
|
3047
3765
|
var PROVIDER_KINDS = ["smtp", "resend"];
|
|
3048
|
-
var ProviderKind =
|
|
3049
|
-
var ProviderPolicy =
|
|
3050
|
-
daily_recipient_budget:
|
|
3051
|
-
min_interval_ms:
|
|
3052
|
-
max_recipients_per_message:
|
|
3766
|
+
var ProviderKind = import_zod11.z.enum(PROVIDER_KINDS);
|
|
3767
|
+
var ProviderPolicy = import_zod11.z.object({
|
|
3768
|
+
daily_recipient_budget: import_zod11.z.number().int().min(1).max(1e7),
|
|
3769
|
+
min_interval_ms: import_zod11.z.number().int().min(0).max(36e5),
|
|
3770
|
+
max_recipients_per_message: import_zod11.z.number().int().min(1).max(1e3)
|
|
3053
3771
|
});
|
|
3054
3772
|
var ICLOUD_SMTP_POLICY = {
|
|
3055
3773
|
daily_recipient_budget: 800,
|
|
@@ -3057,17 +3775,17 @@ var ICLOUD_SMTP_POLICY = {
|
|
|
3057
3775
|
max_recipients_per_message: 1
|
|
3058
3776
|
};
|
|
3059
3777
|
var SMTP_SECURITY = ["tls", "starttls"];
|
|
3060
|
-
var SmtpConfigBase =
|
|
3061
|
-
host:
|
|
3062
|
-
port:
|
|
3778
|
+
var SmtpConfigBase = import_zod11.z.object({
|
|
3779
|
+
host: import_zod11.z.string().min(1).max(255),
|
|
3780
|
+
port: import_zod11.z.number().int().min(1).max(65535),
|
|
3063
3781
|
/** `tls` connects encrypted (465), `starttls` upgrades (587). Never plaintext. */
|
|
3064
|
-
security:
|
|
3065
|
-
username:
|
|
3782
|
+
security: import_zod11.z.enum(SMTP_SECURITY),
|
|
3783
|
+
username: import_zod11.z.string().min(1).max(255)
|
|
3066
3784
|
});
|
|
3067
|
-
var ResendConfigBase =
|
|
3068
|
-
var ProviderRejections =
|
|
3069
|
-
count:
|
|
3070
|
-
last_error:
|
|
3785
|
+
var ResendConfigBase = import_zod11.z.object({});
|
|
3786
|
+
var ProviderRejections = import_zod11.z.object({
|
|
3787
|
+
count: import_zod11.z.number().int().min(0),
|
|
3788
|
+
last_error: import_zod11.z.string().nullable(),
|
|
3071
3789
|
last_at: Timestamp.nullable(),
|
|
3072
3790
|
/**
|
|
3073
3791
|
* The last time the bounce circuit breaker tripped on one of this provider's
|
|
@@ -3077,59 +3795,59 @@ var ProviderRejections = import_zod10.z.object({
|
|
|
3077
3795
|
* run's bounce blocks were undone and the mailing paused. `sample` is the
|
|
3078
3796
|
* reply that tripped it.
|
|
3079
3797
|
*/
|
|
3080
|
-
anomaly:
|
|
3798
|
+
anomaly: import_zod11.z.object({
|
|
3081
3799
|
at: Timestamp,
|
|
3082
3800
|
/** `mailing`: one mailing was paused. `provider`: 5 refusals in a row with the same reply across the provider's sends (test sends and one-recipient mailings included) within 24 hours. */
|
|
3083
|
-
scope:
|
|
3801
|
+
scope: import_zod11.z.enum(["mailing", "provider"]),
|
|
3084
3802
|
/**
|
|
3085
3803
|
* True while the provider-wide breaker is open: no bounce blocks through
|
|
3086
3804
|
* this provider, test sends refused and mailings not started or resumed
|
|
3087
3805
|
* (`provider_anomaly`), until an admin clears it (`providers.clearAnomaly`).
|
|
3088
3806
|
*/
|
|
3089
|
-
blocking:
|
|
3807
|
+
blocking: import_zod11.z.boolean(),
|
|
3090
3808
|
mailing_id: Id.nullable(),
|
|
3091
|
-
reason:
|
|
3092
|
-
sample:
|
|
3809
|
+
reason: import_zod11.z.string(),
|
|
3810
|
+
sample: import_zod11.z.string()
|
|
3093
3811
|
}).nullable()
|
|
3094
3812
|
});
|
|
3095
|
-
var ProviderEvents =
|
|
3096
|
-
status:
|
|
3097
|
-
source:
|
|
3098
|
-
url:
|
|
3099
|
-
error:
|
|
3813
|
+
var ProviderEvents = import_zod11.z.object({
|
|
3814
|
+
status: import_zod11.z.enum(["active", "needs_secret"]),
|
|
3815
|
+
source: import_zod11.z.enum(["automatic", "manual"]).nullable(),
|
|
3816
|
+
url: import_zod11.z.string().url(),
|
|
3817
|
+
error: import_zod11.z.string().nullable(),
|
|
3100
3818
|
/**
|
|
3101
3819
|
* Events that named an email this provider never sent through the service
|
|
3102
3820
|
* (another workspace or system sharing the Resend account): counted, never
|
|
3103
3821
|
* acted on.
|
|
3104
3822
|
*/
|
|
3105
|
-
unmatched:
|
|
3823
|
+
unmatched: import_zod11.z.number().int().min(0)
|
|
3106
3824
|
});
|
|
3107
3825
|
var RESEND_EVENT_TYPES = ["email.bounced", "email.complained", "email.delivery_delayed"];
|
|
3108
|
-
var ResendSigningSecret =
|
|
3109
|
-
var ProviderEventsSecret =
|
|
3826
|
+
var ResendSigningSecret = import_zod11.z.string().max(200).regex(/^whsec_[A-Za-z0-9+/]{16,}={0,2}$/, "must be the signing secret Resend shows, starting with whsec_");
|
|
3827
|
+
var ProviderEventsSecret = import_zod11.z.object({ signing_secret: ResendSigningSecret });
|
|
3110
3828
|
var ProviderFields = {
|
|
3111
|
-
name:
|
|
3112
|
-
from_name:
|
|
3829
|
+
name: import_zod11.z.string().min(1).max(120),
|
|
3830
|
+
from_name: import_zod11.z.string().min(1).max(120),
|
|
3113
3831
|
from_email: Email,
|
|
3114
3832
|
reply_to: Email.nullable(),
|
|
3115
3833
|
policy: ProviderPolicy
|
|
3116
3834
|
};
|
|
3117
|
-
var Provider =
|
|
3118
|
-
|
|
3835
|
+
var Provider = import_zod11.z.discriminatedUnion("kind", [
|
|
3836
|
+
import_zod11.z.object({
|
|
3119
3837
|
id: Id,
|
|
3120
|
-
kind:
|
|
3838
|
+
kind: import_zod11.z.literal("smtp"),
|
|
3121
3839
|
config: SmtpConfigBase,
|
|
3122
|
-
has_secret:
|
|
3840
|
+
has_secret: import_zod11.z.boolean(),
|
|
3123
3841
|
...ProviderFields,
|
|
3124
3842
|
rejections: ProviderRejections,
|
|
3125
3843
|
created_at: Timestamp,
|
|
3126
3844
|
updated_at: Timestamp
|
|
3127
3845
|
}),
|
|
3128
|
-
|
|
3846
|
+
import_zod11.z.object({
|
|
3129
3847
|
id: Id,
|
|
3130
|
-
kind:
|
|
3848
|
+
kind: import_zod11.z.literal("resend"),
|
|
3131
3849
|
config: ResendConfigBase,
|
|
3132
|
-
has_secret:
|
|
3850
|
+
has_secret: import_zod11.z.boolean(),
|
|
3133
3851
|
...ProviderFields,
|
|
3134
3852
|
rejections: ProviderRejections,
|
|
3135
3853
|
events: ProviderEvents,
|
|
@@ -3137,31 +3855,31 @@ var Provider = import_zod10.z.discriminatedUnion("kind", [
|
|
|
3137
3855
|
updated_at: Timestamp
|
|
3138
3856
|
})
|
|
3139
3857
|
]);
|
|
3140
|
-
var ProviderCreate =
|
|
3141
|
-
|
|
3142
|
-
kind:
|
|
3143
|
-
config: SmtpConfigBase.extend({ password:
|
|
3858
|
+
var ProviderCreate = import_zod11.z.discriminatedUnion("kind", [
|
|
3859
|
+
import_zod11.z.object({
|
|
3860
|
+
kind: import_zod11.z.literal("smtp"),
|
|
3861
|
+
config: SmtpConfigBase.extend({ password: import_zod11.z.string().min(1).max(1024) }),
|
|
3144
3862
|
...ProviderFields
|
|
3145
3863
|
}),
|
|
3146
|
-
|
|
3147
|
-
kind:
|
|
3148
|
-
config: ResendConfigBase.extend({ api_key:
|
|
3864
|
+
import_zod11.z.object({
|
|
3865
|
+
kind: import_zod11.z.literal("resend"),
|
|
3866
|
+
config: ResendConfigBase.extend({ api_key: import_zod11.z.string().min(1).max(1024) }),
|
|
3149
3867
|
...ProviderFields
|
|
3150
3868
|
})
|
|
3151
3869
|
]);
|
|
3152
|
-
var ProviderUpdate =
|
|
3153
|
-
|
|
3154
|
-
kind:
|
|
3155
|
-
config: SmtpConfigBase.extend({ password:
|
|
3870
|
+
var ProviderUpdate = import_zod11.z.discriminatedUnion("kind", [
|
|
3871
|
+
import_zod11.z.object({
|
|
3872
|
+
kind: import_zod11.z.literal("smtp"),
|
|
3873
|
+
config: SmtpConfigBase.extend({ password: import_zod11.z.string().min(1).max(1024).optional() }).partial().optional(),
|
|
3156
3874
|
name: ProviderFields.name.optional(),
|
|
3157
3875
|
from_name: ProviderFields.from_name.optional(),
|
|
3158
3876
|
from_email: ProviderFields.from_email.optional(),
|
|
3159
3877
|
reply_to: ProviderFields.reply_to.optional(),
|
|
3160
3878
|
policy: ProviderPolicy.partial().optional()
|
|
3161
3879
|
}),
|
|
3162
|
-
|
|
3163
|
-
kind:
|
|
3164
|
-
config: ResendConfigBase.extend({ api_key:
|
|
3880
|
+
import_zod11.z.object({
|
|
3881
|
+
kind: import_zod11.z.literal("resend"),
|
|
3882
|
+
config: ResendConfigBase.extend({ api_key: import_zod11.z.string().min(1).max(1024).optional() }).optional(),
|
|
3165
3883
|
name: ProviderFields.name.optional(),
|
|
3166
3884
|
from_name: ProviderFields.from_name.optional(),
|
|
3167
3885
|
from_email: ProviderFields.from_email.optional(),
|
|
@@ -3169,48 +3887,48 @@ var ProviderUpdate = import_zod10.z.discriminatedUnion("kind", [
|
|
|
3169
3887
|
policy: ProviderPolicy.partial().optional()
|
|
3170
3888
|
})
|
|
3171
3889
|
]);
|
|
3172
|
-
var ProviderUsage =
|
|
3890
|
+
var ProviderUsage = import_zod11.z.object({
|
|
3173
3891
|
provider_id: Id,
|
|
3174
|
-
recipients_last_24h:
|
|
3175
|
-
remaining_budget:
|
|
3892
|
+
recipients_last_24h: import_zod11.z.number().int().min(0),
|
|
3893
|
+
remaining_budget: import_zod11.z.number().int().min(0),
|
|
3176
3894
|
/** When enough of the rolling window frees up to send again, if exhausted. */
|
|
3177
3895
|
next_capacity_at: Timestamp.nullable()
|
|
3178
3896
|
});
|
|
3179
|
-
var ProviderVerifyResult =
|
|
3180
|
-
ok:
|
|
3181
|
-
error:
|
|
3897
|
+
var ProviderVerifyResult = import_zod11.z.object({
|
|
3898
|
+
ok: import_zod11.z.boolean(),
|
|
3899
|
+
error: import_zod11.z.string().nullable()
|
|
3182
3900
|
});
|
|
3183
3901
|
|
|
3184
3902
|
// src/contacts.ts
|
|
3185
|
-
var
|
|
3186
|
-
var Topic =
|
|
3903
|
+
var import_zod12 = require("zod");
|
|
3904
|
+
var Topic = import_zod12.z.object({
|
|
3187
3905
|
id: Id,
|
|
3188
3906
|
slug: Slug,
|
|
3189
|
-
name:
|
|
3907
|
+
name: import_zod12.z.string().min(1).max(120),
|
|
3190
3908
|
/** Shown on the hosted preference page. */
|
|
3191
|
-
description:
|
|
3909
|
+
description: import_zod12.z.string().max(1e3).nullable(),
|
|
3192
3910
|
/** Translations of `name` and `description` per locale, for the hosted page. */
|
|
3193
|
-
translations:
|
|
3194
|
-
|
|
3911
|
+
translations: import_zod12.z.record(
|
|
3912
|
+
import_zod12.z.object({ name: import_zod12.z.string().min(1).max(120), description: import_zod12.z.string().max(1e3).nullable() })
|
|
3195
3913
|
),
|
|
3196
3914
|
created_at: Timestamp,
|
|
3197
3915
|
updated_at: Timestamp
|
|
3198
3916
|
});
|
|
3199
|
-
var TopicCreate =
|
|
3917
|
+
var TopicCreate = import_zod12.z.object({
|
|
3200
3918
|
slug: Slug,
|
|
3201
|
-
name:
|
|
3202
|
-
description:
|
|
3919
|
+
name: import_zod12.z.string().min(1).max(120),
|
|
3920
|
+
description: import_zod12.z.string().max(1e3).optional(),
|
|
3203
3921
|
translations: Topic.shape.translations.optional()
|
|
3204
3922
|
});
|
|
3205
3923
|
var TopicUpdate = TopicCreate.omit({ slug: true }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
3206
|
-
var Contact =
|
|
3924
|
+
var Contact = import_zod12.z.object({
|
|
3207
3925
|
id: Id,
|
|
3208
|
-
external_id:
|
|
3926
|
+
external_id: import_zod12.z.string().min(1).max(255).nullable(),
|
|
3209
3927
|
/** Stored lowercased, unique per workspace. */
|
|
3210
3928
|
email: Email,
|
|
3211
|
-
first_name:
|
|
3212
|
-
last_name:
|
|
3213
|
-
locale:
|
|
3929
|
+
first_name: import_zod12.z.string().max(200).nullable(),
|
|
3930
|
+
last_name: import_zod12.z.string().max(200).nullable(),
|
|
3931
|
+
locale: import_zod12.z.string().min(2).max(35).nullable(),
|
|
3214
3932
|
/**
|
|
3215
3933
|
* The person's own time zone, an IANA name such as `Europe/Berlin` (A1).
|
|
3216
3934
|
* Every wall clock an automation waits for is read in it, falling back to the
|
|
@@ -3218,65 +3936,65 @@ var Contact = import_zod11.z.object({
|
|
|
3218
3936
|
*/
|
|
3219
3937
|
timezone: TimezoneName.nullable(),
|
|
3220
3938
|
properties: Properties,
|
|
3221
|
-
topics:
|
|
3939
|
+
topics: import_zod12.z.array(Slug),
|
|
3222
3940
|
/** The slugs of the contact's tags (S4). */
|
|
3223
|
-
tags:
|
|
3941
|
+
tags: import_zod12.z.array(Slug),
|
|
3224
3942
|
created_at: Timestamp,
|
|
3225
3943
|
updated_at: Timestamp
|
|
3226
3944
|
});
|
|
3227
|
-
var ContactUpsert =
|
|
3228
|
-
external_id:
|
|
3945
|
+
var ContactUpsert = import_zod12.z.object({
|
|
3946
|
+
external_id: import_zod12.z.string().min(1).max(255).optional(),
|
|
3229
3947
|
email: Email.optional(),
|
|
3230
|
-
first_name:
|
|
3231
|
-
last_name:
|
|
3232
|
-
locale:
|
|
3948
|
+
first_name: import_zod12.z.string().max(200).nullable().optional(),
|
|
3949
|
+
last_name: import_zod12.z.string().max(200).nullable().optional(),
|
|
3950
|
+
locale: import_zod12.z.string().min(2).max(35).nullable().optional(),
|
|
3233
3951
|
timezone: TimezoneName.nullable().optional(),
|
|
3234
3952
|
properties: Properties.optional(),
|
|
3235
|
-
topics:
|
|
3953
|
+
topics: import_zod12.z.array(Slug).max(100).optional()
|
|
3236
3954
|
}).refine((v) => v.external_id !== void 0 || v.email !== void 0, {
|
|
3237
3955
|
message: "external_id or email is required",
|
|
3238
3956
|
path: ["email"]
|
|
3239
3957
|
});
|
|
3240
|
-
var ContactUpsertResult =
|
|
3958
|
+
var ContactUpsertResult = import_zod12.z.object({
|
|
3241
3959
|
contact: Contact,
|
|
3242
|
-
created:
|
|
3960
|
+
created: import_zod12.z.boolean()
|
|
3243
3961
|
});
|
|
3244
3962
|
var ContactListQuery = PageQuery.extend({
|
|
3245
|
-
email:
|
|
3246
|
-
external_id:
|
|
3963
|
+
email: import_zod12.z.string().min(1).max(254).optional(),
|
|
3964
|
+
external_id: import_zod12.z.string().min(1).max(255).optional(),
|
|
3247
3965
|
topic: Slug.optional()
|
|
3248
3966
|
});
|
|
3249
|
-
var ContactErased =
|
|
3250
|
-
ok:
|
|
3251
|
-
erased_messages:
|
|
3252
|
-
erased_recipients:
|
|
3253
|
-
suppressions_kept:
|
|
3967
|
+
var ContactErased = import_zod12.z.object({
|
|
3968
|
+
ok: import_zod12.z.literal(true),
|
|
3969
|
+
erased_messages: import_zod12.z.number().int().min(0),
|
|
3970
|
+
erased_recipients: import_zod12.z.number().int().min(0),
|
|
3971
|
+
suppressions_kept: import_zod12.z.number().int().min(0)
|
|
3254
3972
|
});
|
|
3255
3973
|
var SUPPRESSION_REASONS = ["unsubscribed", "bounced", "complained", "manual"];
|
|
3256
|
-
var SuppressionReason =
|
|
3257
|
-
var Suppression =
|
|
3974
|
+
var SuppressionReason = import_zod12.z.enum(SUPPRESSION_REASONS);
|
|
3975
|
+
var Suppression = import_zod12.z.object({
|
|
3258
3976
|
id: Id,
|
|
3259
3977
|
email: Email,
|
|
3260
3978
|
reason: SuppressionReason,
|
|
3261
3979
|
topic: Slug.nullable(),
|
|
3262
3980
|
source_message_id: Id.nullable(),
|
|
3263
|
-
note:
|
|
3981
|
+
note: import_zod12.z.string().max(1e3).nullable(),
|
|
3264
3982
|
created_at: Timestamp
|
|
3265
3983
|
});
|
|
3266
|
-
var SuppressionCreate =
|
|
3984
|
+
var SuppressionCreate = import_zod12.z.object({
|
|
3267
3985
|
email: Email,
|
|
3268
|
-
reason:
|
|
3986
|
+
reason: import_zod12.z.enum(["manual", "unsubscribed"]),
|
|
3269
3987
|
topic: Slug.nullable().optional(),
|
|
3270
|
-
note:
|
|
3988
|
+
note: import_zod12.z.string().max(1e3).optional()
|
|
3271
3989
|
});
|
|
3272
3990
|
var SuppressionListQuery = PageQuery.extend({
|
|
3273
|
-
email:
|
|
3991
|
+
email: import_zod12.z.string().min(1).max(254).optional(),
|
|
3274
3992
|
reason: SuppressionReason.optional(),
|
|
3275
3993
|
topic: Slug.optional()
|
|
3276
3994
|
});
|
|
3277
3995
|
|
|
3278
3996
|
// src/webhooks.ts
|
|
3279
|
-
var
|
|
3997
|
+
var import_zod13 = require("zod");
|
|
3280
3998
|
var WEBHOOK_EVENT_TYPES = [
|
|
3281
3999
|
"message.sent",
|
|
3282
4000
|
"message.failed",
|
|
@@ -3320,40 +4038,47 @@ var WEBHOOK_EVENT_TYPES = [
|
|
|
3320
4038
|
"template.updated",
|
|
3321
4039
|
"template.deleted"
|
|
3322
4040
|
];
|
|
3323
|
-
var WebhookEventType =
|
|
3324
|
-
var MessageEventBase =
|
|
4041
|
+
var WebhookEventType = import_zod13.z.enum(WEBHOOK_EVENT_TYPES);
|
|
4042
|
+
var MessageEventBase = import_zod13.z.object({
|
|
3325
4043
|
message_id: Id,
|
|
3326
4044
|
mailing_id: Id.nullable(),
|
|
3327
4045
|
mailing_metadata: MailingMetadata.nullable(),
|
|
3328
4046
|
recipient_id: Id.nullable(),
|
|
3329
4047
|
contact_id: Id.nullable(),
|
|
3330
|
-
external_id:
|
|
4048
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3331
4049
|
email: Email,
|
|
3332
|
-
subject:
|
|
4050
|
+
subject: import_zod13.z.string(),
|
|
3333
4051
|
topic: Slug.nullable(),
|
|
3334
|
-
provider_message_id:
|
|
3335
|
-
is_test:
|
|
4052
|
+
provider_message_id: import_zod13.z.string().nullable(),
|
|
4053
|
+
is_test: import_zod13.z.boolean()
|
|
3336
4054
|
});
|
|
3337
4055
|
var MessageSentData = MessageEventBase.extend({
|
|
3338
4056
|
/** The final HTML as sent, so the client can archive it beside its own records. */
|
|
3339
|
-
html:
|
|
3340
|
-
sent_at: Timestamp
|
|
4057
|
+
html: import_zod13.z.string(),
|
|
4058
|
+
sent_at: Timestamp,
|
|
4059
|
+
/**
|
|
4060
|
+
* The choice each insertion made (see `Message.insertions`); absent or null
|
|
4061
|
+
* when the message had none. A `matched: false` entry is a value the
|
|
4062
|
+
* template has no choice for: the client sent something its form should
|
|
4063
|
+
* not offer.
|
|
4064
|
+
*/
|
|
4065
|
+
insertions: InsertionChoices.nullish()
|
|
3341
4066
|
});
|
|
3342
4067
|
var MessageFailedData = MessageEventBase.extend({
|
|
3343
|
-
error:
|
|
4068
|
+
error: import_zod13.z.string(),
|
|
3344
4069
|
/** False when the provider rejected permanently; the recipient will not be retried. */
|
|
3345
|
-
retryable:
|
|
4070
|
+
retryable: import_zod13.z.boolean(),
|
|
3346
4071
|
failed_at: Timestamp
|
|
3347
4072
|
});
|
|
3348
4073
|
var UNSUBSCRIBE_SOURCES = ["hosted_page", "one_click", "api", "dashboard", "signup_form"];
|
|
3349
|
-
var ContactUnsubscribedData =
|
|
4074
|
+
var ContactUnsubscribedData = import_zod13.z.object({
|
|
3350
4075
|
contact_id: Id.nullable(),
|
|
3351
|
-
external_id:
|
|
4076
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3352
4077
|
email: Email,
|
|
3353
4078
|
/** The topic unsubscribed from; null means every topic. */
|
|
3354
4079
|
topic: Slug.nullable(),
|
|
3355
4080
|
mailing_id: Id.nullable(),
|
|
3356
|
-
source:
|
|
4081
|
+
source: import_zod13.z.enum(UNSUBSCRIBE_SOURCES),
|
|
3357
4082
|
unsubscribed_at: Timestamp
|
|
3358
4083
|
});
|
|
3359
4084
|
var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: true }).extend({
|
|
@@ -3362,142 +4087,142 @@ var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: tr
|
|
|
3362
4087
|
* breaker undid a `bounced` block it judged false (a `contact.bounced` for the
|
|
3363
4088
|
* same address and mailing went out before); `topic` is null.
|
|
3364
4089
|
*/
|
|
3365
|
-
source:
|
|
4090
|
+
source: import_zod13.z.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
|
|
3366
4091
|
resubscribed_at: Timestamp
|
|
3367
4092
|
});
|
|
3368
|
-
var ContactBouncedData =
|
|
4093
|
+
var ContactBouncedData = import_zod13.z.object({
|
|
3369
4094
|
contact_id: Id.nullable(),
|
|
3370
|
-
external_id:
|
|
4095
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3371
4096
|
email: Email,
|
|
3372
4097
|
/** `bounced` for a hard bounce, `complained` for a spam complaint. */
|
|
3373
|
-
reason:
|
|
4098
|
+
reason: import_zod13.z.enum(["bounced", "complained"]),
|
|
3374
4099
|
message_id: Id.nullable(),
|
|
3375
|
-
diagnostic:
|
|
4100
|
+
diagnostic: import_zod13.z.string().nullable(),
|
|
3376
4101
|
bounced_at: Timestamp
|
|
3377
4102
|
});
|
|
3378
|
-
var MailingFinishedData =
|
|
4103
|
+
var MailingFinishedData = import_zod13.z.object({
|
|
3379
4104
|
mailing_id: Id,
|
|
3380
4105
|
mailing_metadata: MailingMetadata,
|
|
3381
|
-
status:
|
|
4106
|
+
status: import_zod13.z.enum(["sent", "partially_failed", "cancelled"]),
|
|
3382
4107
|
counts: MailingCounts,
|
|
3383
4108
|
finished_at: Timestamp
|
|
3384
4109
|
});
|
|
3385
|
-
var ContactSubscribedData =
|
|
4110
|
+
var ContactSubscribedData = import_zod13.z.object({
|
|
3386
4111
|
contact_id: Id,
|
|
3387
|
-
external_id:
|
|
4112
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3388
4113
|
email: Email,
|
|
3389
|
-
topics:
|
|
3390
|
-
source:
|
|
4114
|
+
topics: import_zod13.z.array(Slug),
|
|
4115
|
+
source: import_zod13.z.enum(["signup_form", "api", "dashboard", "automation"]),
|
|
3391
4116
|
signup_form_id: Id.nullable(),
|
|
3392
|
-
signup_form_version:
|
|
4117
|
+
signup_form_version: import_zod13.z.number().int().min(1).nullable(),
|
|
3393
4118
|
subscribed_at: Timestamp
|
|
3394
4119
|
});
|
|
3395
|
-
var ImportFinishedData =
|
|
4120
|
+
var ImportFinishedData = import_zod13.z.object({
|
|
3396
4121
|
import_id: Id,
|
|
3397
|
-
status:
|
|
4122
|
+
status: import_zod13.z.enum(["completed", "cancelled", "failed"]),
|
|
3398
4123
|
result: ImportReport.nullable(),
|
|
3399
|
-
error:
|
|
4124
|
+
error: import_zod13.z.string().nullable(),
|
|
3400
4125
|
finished_at: Timestamp
|
|
3401
4126
|
});
|
|
3402
|
-
var MailingScheduledData =
|
|
4127
|
+
var MailingScheduledData = import_zod13.z.object({
|
|
3403
4128
|
mailing_id: Id,
|
|
3404
4129
|
mailing_metadata: MailingMetadata,
|
|
3405
4130
|
scheduled_at: Timestamp.nullable()
|
|
3406
4131
|
});
|
|
3407
|
-
var MailingStartedData =
|
|
4132
|
+
var MailingStartedData = import_zod13.z.object({
|
|
3408
4133
|
mailing_id: Id,
|
|
3409
4134
|
mailing_metadata: MailingMetadata,
|
|
3410
|
-
trigger:
|
|
3411
|
-
recipients:
|
|
4135
|
+
trigger: import_zod13.z.enum(["send", "schedule"]),
|
|
4136
|
+
recipients: import_zod13.z.number().int().min(0),
|
|
3412
4137
|
started_at: Timestamp
|
|
3413
4138
|
});
|
|
3414
|
-
var MailingScheduleFailedData =
|
|
4139
|
+
var MailingScheduleFailedData = import_zod13.z.object({
|
|
3415
4140
|
mailing_id: Id,
|
|
3416
4141
|
mailing_metadata: MailingMetadata,
|
|
3417
|
-
code:
|
|
3418
|
-
message:
|
|
4142
|
+
code: import_zod13.z.string(),
|
|
4143
|
+
message: import_zod13.z.string(),
|
|
3419
4144
|
failed_at: Timestamp
|
|
3420
4145
|
});
|
|
3421
|
-
var MailingAbWinnerSelectedData =
|
|
4146
|
+
var MailingAbWinnerSelectedData = import_zod13.z.object({
|
|
3422
4147
|
mailing_id: Id,
|
|
3423
4148
|
mailing_metadata: MailingMetadata,
|
|
3424
|
-
winner:
|
|
3425
|
-
decided_by:
|
|
3426
|
-
variants:
|
|
4149
|
+
winner: import_zod13.z.string(),
|
|
4150
|
+
decided_by: import_zod13.z.enum(["metric", "manual"]),
|
|
4151
|
+
variants: import_zod13.z.array(VariantAnalytics),
|
|
3427
4152
|
decided_at: Timestamp
|
|
3428
4153
|
});
|
|
3429
|
-
var ContactTaggedData =
|
|
4154
|
+
var ContactTaggedData = import_zod13.z.object({
|
|
3430
4155
|
contact_id: Id,
|
|
3431
|
-
external_id:
|
|
4156
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3432
4157
|
email: Email,
|
|
3433
4158
|
/** Only the tags the contact did not already have. */
|
|
3434
|
-
tags:
|
|
3435
|
-
source:
|
|
4159
|
+
tags: import_zod13.z.array(Slug).min(1),
|
|
4160
|
+
source: import_zod13.z.enum(["api", "dashboard", "signup_form", "import", "automation"]),
|
|
3436
4161
|
tagged_at: Timestamp
|
|
3437
4162
|
});
|
|
3438
|
-
var ContactUpdatedData =
|
|
4163
|
+
var ContactUpdatedData = import_zod13.z.object({
|
|
3439
4164
|
contact_id: Id,
|
|
3440
|
-
external_id:
|
|
4165
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3441
4166
|
email: Email,
|
|
3442
|
-
changed:
|
|
4167
|
+
changed: import_zod13.z.array(import_zod13.z.string()).min(1),
|
|
3443
4168
|
properties: Properties,
|
|
3444
|
-
source:
|
|
4169
|
+
source: import_zod13.z.enum(["api", "dashboard", "signup_form", "import", "automation"]),
|
|
3445
4170
|
updated_at: Timestamp
|
|
3446
4171
|
});
|
|
3447
|
-
var AutomationRunEventBase =
|
|
4172
|
+
var AutomationRunEventBase = import_zod13.z.object({
|
|
3448
4173
|
automation_id: Id,
|
|
3449
|
-
automation_name:
|
|
4174
|
+
automation_name: import_zod13.z.string(),
|
|
3450
4175
|
run_id: Id,
|
|
3451
|
-
version:
|
|
4176
|
+
version: import_zod13.z.number().int().min(1),
|
|
3452
4177
|
contact_id: Id,
|
|
3453
|
-
external_id:
|
|
4178
|
+
external_id: import_zod13.z.string().nullable(),
|
|
3454
4179
|
email: Email
|
|
3455
4180
|
});
|
|
3456
4181
|
var AutomationRunStartedData = AutomationRunEventBase.extend({
|
|
3457
4182
|
/** What let them in: a trigger kind, or `manual` for an explicit enrolment. */
|
|
3458
|
-
trigger_kind:
|
|
4183
|
+
trigger_kind: import_zod13.z.string(),
|
|
3459
4184
|
entered_at: Timestamp
|
|
3460
4185
|
});
|
|
3461
4186
|
var AutomationRunCompletedData = AutomationRunEventBase.extend({
|
|
3462
|
-
last_step_id:
|
|
4187
|
+
last_step_id: import_zod13.z.string().nullable(),
|
|
3463
4188
|
completed_at: Timestamp
|
|
3464
4189
|
});
|
|
3465
4190
|
var AutomationRunExitedData = AutomationRunEventBase.extend({
|
|
3466
4191
|
reason: AutomationEndReason,
|
|
3467
|
-
step_id:
|
|
4192
|
+
step_id: import_zod13.z.string().nullable(),
|
|
3468
4193
|
exited_at: Timestamp
|
|
3469
4194
|
});
|
|
3470
4195
|
var AutomationRunFailedData = AutomationRunEventBase.extend({
|
|
3471
|
-
reason:
|
|
3472
|
-
step_id:
|
|
4196
|
+
reason: import_zod13.z.string(),
|
|
4197
|
+
step_id: import_zod13.z.string().nullable(),
|
|
3473
4198
|
failed_at: Timestamp
|
|
3474
4199
|
});
|
|
3475
4200
|
var AutomationWebhookData = AutomationRunEventBase.extend({
|
|
3476
|
-
step_id:
|
|
4201
|
+
step_id: import_zod13.z.string(),
|
|
3477
4202
|
/** The step's own label, when it was given one, so a receiver can branch on a name. */
|
|
3478
|
-
step_label:
|
|
4203
|
+
step_label: import_zod13.z.string().nullable(),
|
|
3479
4204
|
/** Whatever the step carries, verbatim. Null when it carries nothing. */
|
|
3480
4205
|
payload: JsonValue.nullable(),
|
|
3481
4206
|
fired_at: Timestamp
|
|
3482
4207
|
});
|
|
3483
|
-
var TemplateEventBase =
|
|
4208
|
+
var TemplateEventBase = import_zod13.z.object({
|
|
3484
4209
|
template_id: Id,
|
|
3485
|
-
name:
|
|
3486
|
-
version:
|
|
3487
|
-
source:
|
|
4210
|
+
name: import_zod13.z.string().min(1).max(200),
|
|
4211
|
+
version: import_zod13.z.number().int().min(1),
|
|
4212
|
+
source: import_zod13.z.enum(["api", "dashboard", "mjml_import"])
|
|
3488
4213
|
});
|
|
3489
4214
|
var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
|
|
3490
|
-
var TemplateUpdatedData = TemplateEventBase.extend({ archived:
|
|
4215
|
+
var TemplateUpdatedData = TemplateEventBase.extend({ archived: import_zod13.z.boolean(), updated_at: Timestamp });
|
|
3491
4216
|
var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
|
|
3492
|
-
var envelope = (type, data) =>
|
|
4217
|
+
var envelope = (type, data) => import_zod13.z.object({
|
|
3493
4218
|
/** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
|
|
3494
4219
|
id: Id,
|
|
3495
|
-
type:
|
|
4220
|
+
type: import_zod13.z.literal(type),
|
|
3496
4221
|
created_at: Timestamp,
|
|
3497
4222
|
workspace_id: Id,
|
|
3498
4223
|
data
|
|
3499
4224
|
});
|
|
3500
|
-
var WebhookEvent =
|
|
4225
|
+
var WebhookEvent = import_zod13.z.discriminatedUnion("type", [
|
|
3501
4226
|
envelope("message.sent", MessageSentData),
|
|
3502
4227
|
envelope("message.failed", MessageFailedData),
|
|
3503
4228
|
envelope("contact.unsubscribed", ContactUnsubscribedData),
|
|
@@ -3521,16 +4246,16 @@ var WebhookEvent = import_zod12.z.discriminatedUnion("type", [
|
|
|
3521
4246
|
envelope("template.updated", TemplateUpdatedData),
|
|
3522
4247
|
envelope("template.deleted", TemplateDeletedData)
|
|
3523
4248
|
]);
|
|
3524
|
-
var WebhookEndpoint =
|
|
4249
|
+
var WebhookEndpoint = import_zod13.z.object({
|
|
3525
4250
|
id: Id,
|
|
3526
|
-
url:
|
|
3527
|
-
description:
|
|
3528
|
-
events:
|
|
3529
|
-
enabled:
|
|
4251
|
+
url: import_zod13.z.string().url(),
|
|
4252
|
+
description: import_zod13.z.string().max(500).nullable(),
|
|
4253
|
+
events: import_zod13.z.array(WebhookEventType).min(1),
|
|
4254
|
+
enabled: import_zod13.z.boolean(),
|
|
3530
4255
|
created_at: Timestamp,
|
|
3531
4256
|
updated_at: Timestamp
|
|
3532
4257
|
});
|
|
3533
|
-
var WebhookUrl =
|
|
4258
|
+
var WebhookUrl = import_zod13.z.string().url().refine((u) => {
|
|
3534
4259
|
try {
|
|
3535
4260
|
const url = new URL(u);
|
|
3536
4261
|
if (url.protocol === "https:") return true;
|
|
@@ -3539,35 +4264,35 @@ var WebhookUrl = import_zod12.z.string().url().refine((u) => {
|
|
|
3539
4264
|
return false;
|
|
3540
4265
|
}
|
|
3541
4266
|
}, "must be https (http only for localhost)");
|
|
3542
|
-
var WebhookEndpointCreate =
|
|
4267
|
+
var WebhookEndpointCreate = import_zod13.z.object({
|
|
3543
4268
|
url: WebhookUrl,
|
|
3544
|
-
description:
|
|
3545
|
-
events:
|
|
3546
|
-
enabled:
|
|
4269
|
+
description: import_zod13.z.string().max(500).optional(),
|
|
4270
|
+
events: import_zod13.z.array(WebhookEventType).min(1),
|
|
4271
|
+
enabled: import_zod13.z.boolean().optional()
|
|
3547
4272
|
});
|
|
3548
|
-
var WebhookEndpointUpdate =
|
|
4273
|
+
var WebhookEndpointUpdate = import_zod13.z.object({
|
|
3549
4274
|
url: WebhookUrl,
|
|
3550
|
-
description:
|
|
3551
|
-
events:
|
|
3552
|
-
enabled:
|
|
4275
|
+
description: import_zod13.z.string().max(500).nullable(),
|
|
4276
|
+
events: import_zod13.z.array(WebhookEventType).min(1),
|
|
4277
|
+
enabled: import_zod13.z.boolean()
|
|
3553
4278
|
}).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
|
|
3554
|
-
var WebhookEndpointWithSecret =
|
|
4279
|
+
var WebhookEndpointWithSecret = import_zod13.z.object({
|
|
3555
4280
|
endpoint: WebhookEndpoint,
|
|
3556
|
-
secret:
|
|
4281
|
+
secret: import_zod13.z.string().min(32)
|
|
3557
4282
|
});
|
|
3558
4283
|
var WEBHOOK_DELIVERY_STATUSES = ["pending", "succeeded", "failed"];
|
|
3559
|
-
var WebhookDeliveryStatus =
|
|
4284
|
+
var WebhookDeliveryStatus = import_zod13.z.enum(WEBHOOK_DELIVERY_STATUSES);
|
|
3560
4285
|
var WEBHOOK_MAX_ATTEMPTS = 8;
|
|
3561
4286
|
var WEBHOOK_RETRY_DELAYS_SECONDS = [30, 120, 600, 1800, 3600, 7200, 21600];
|
|
3562
|
-
var WebhookDelivery =
|
|
4287
|
+
var WebhookDelivery = import_zod13.z.object({
|
|
3563
4288
|
id: Id,
|
|
3564
4289
|
endpoint_id: Id,
|
|
3565
4290
|
event_id: Id,
|
|
3566
4291
|
event_type: WebhookEventType,
|
|
3567
4292
|
status: WebhookDeliveryStatus,
|
|
3568
|
-
attempts:
|
|
3569
|
-
last_status_code:
|
|
3570
|
-
last_error:
|
|
4293
|
+
attempts: import_zod13.z.number().int().min(0),
|
|
4294
|
+
last_status_code: import_zod13.z.number().int().min(100).max(599).nullable(),
|
|
4295
|
+
last_error: import_zod13.z.string().nullable(),
|
|
3571
4296
|
next_attempt_at: Timestamp.nullable(),
|
|
3572
4297
|
delivered_at: Timestamp.nullable(),
|
|
3573
4298
|
created_at: Timestamp
|
|
@@ -3576,7 +4301,7 @@ var WebhookDeliveryListQuery = PageQuery.extend({
|
|
|
3576
4301
|
status: WebhookDeliveryStatus.optional(),
|
|
3577
4302
|
event_type: WebhookEventType.optional()
|
|
3578
4303
|
});
|
|
3579
|
-
var WebhookDeliveryParams =
|
|
4304
|
+
var WebhookDeliveryParams = import_zod13.z.object({ id: Id, delivery_id: Id });
|
|
3580
4305
|
|
|
3581
4306
|
// src/webhook-signing.ts
|
|
3582
4307
|
var WEBHOOK_SIGNATURE_HEADER = "x-mail-signature";
|
|
@@ -3643,79 +4368,79 @@ async function verifyWebhook(input) {
|
|
|
3643
4368
|
}
|
|
3644
4369
|
|
|
3645
4370
|
// src/billing.ts
|
|
3646
|
-
var
|
|
4371
|
+
var import_zod14 = require("zod");
|
|
3647
4372
|
var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
|
|
3648
|
-
var PlanId =
|
|
3649
|
-
var PlanLimits =
|
|
3650
|
-
monthly_messages:
|
|
3651
|
-
contacts:
|
|
3652
|
-
members:
|
|
3653
|
-
providers:
|
|
3654
|
-
webhook_endpoints:
|
|
4373
|
+
var PlanId = import_zod14.z.enum(PLAN_IDS);
|
|
4374
|
+
var PlanLimits = import_zod14.z.object({
|
|
4375
|
+
monthly_messages: import_zod14.z.number().int().min(0).nullable(),
|
|
4376
|
+
contacts: import_zod14.z.number().int().min(0).nullable(),
|
|
4377
|
+
members: import_zod14.z.number().int().min(1).nullable(),
|
|
4378
|
+
providers: import_zod14.z.number().int().min(1).nullable(),
|
|
4379
|
+
webhook_endpoints: import_zod14.z.number().int().min(0).nullable(),
|
|
3655
4380
|
/** Automations that may be live (active or paused) at once (A1). */
|
|
3656
|
-
active_automations:
|
|
4381
|
+
active_automations: import_zod14.z.number().int().min(0).nullable(),
|
|
3657
4382
|
/** Steps one automation may hold. Null is the flow's own cap, not unlimited. */
|
|
3658
|
-
automation_steps:
|
|
4383
|
+
automation_steps: import_zod14.z.number().int().min(1).nullable()
|
|
3659
4384
|
});
|
|
3660
|
-
var PlanFeatures =
|
|
4385
|
+
var PlanFeatures = import_zod14.z.object({
|
|
3661
4386
|
/** A/B tests of subject and content on a mailing (S4). */
|
|
3662
|
-
ab_testing:
|
|
4387
|
+
ab_testing: import_zod14.z.boolean(),
|
|
3663
4388
|
/** Open and click tracking, still off until a workspace turns it on (`settings.tracking_enabled`). */
|
|
3664
|
-
tracking:
|
|
4389
|
+
tracking: import_zod14.z.boolean(),
|
|
3665
4390
|
/** Sending and hosting pages on the workspace's own domain (a later phase). */
|
|
3666
|
-
custom_domains:
|
|
4391
|
+
custom_domains: import_zod14.z.boolean(),
|
|
3667
4392
|
/** Automations: flows a person walks through step by step (A1). */
|
|
3668
|
-
automations:
|
|
4393
|
+
automations: import_zod14.z.boolean()
|
|
3669
4394
|
});
|
|
3670
4395
|
var PLAN_FEATURES = ["ab_testing", "tracking", "custom_domains", "automations"];
|
|
3671
|
-
var Plan =
|
|
4396
|
+
var Plan = import_zod14.z.object({
|
|
3672
4397
|
id: PlanId,
|
|
3673
|
-
name:
|
|
4398
|
+
name: import_zod14.z.string().min(1),
|
|
3674
4399
|
/** Price in the smallest currency unit per month; null for plans not sold. */
|
|
3675
|
-
monthly_price_cents:
|
|
3676
|
-
currency:
|
|
4400
|
+
monthly_price_cents: import_zod14.z.number().int().min(0).nullable(),
|
|
4401
|
+
currency: import_zod14.z.string().length(3),
|
|
3677
4402
|
limits: PlanLimits,
|
|
3678
4403
|
features: PlanFeatures
|
|
3679
4404
|
});
|
|
3680
|
-
var CatalogPlan = Plan.extend({ sellable:
|
|
3681
|
-
var Subscription =
|
|
4405
|
+
var CatalogPlan = Plan.extend({ sellable: import_zod14.z.boolean() });
|
|
4406
|
+
var Subscription = import_zod14.z.object({
|
|
3682
4407
|
workspace_id: Id,
|
|
3683
4408
|
plan: PlanId,
|
|
3684
|
-
status:
|
|
4409
|
+
status: import_zod14.z.enum(["active", "trialing", "past_due", "cancelled"]),
|
|
3685
4410
|
current_period_start: Timestamp,
|
|
3686
4411
|
current_period_end: Timestamp,
|
|
3687
|
-
cancel_at_period_end:
|
|
4412
|
+
cancel_at_period_end: import_zod14.z.boolean(),
|
|
3688
4413
|
/**
|
|
3689
4414
|
* A design partner outside billing (plan `design_partner`, no limits, no
|
|
3690
4415
|
* checkout). Set only by an operator, never through the API.
|
|
3691
4416
|
*/
|
|
3692
|
-
billing_exempt:
|
|
4417
|
+
billing_exempt: import_zod14.z.boolean()
|
|
3693
4418
|
});
|
|
3694
4419
|
var USAGE_METRICS = ["messages", "contacts", "members", "providers", "webhook_endpoints", "active_automations"];
|
|
3695
|
-
var UsageMetric =
|
|
4420
|
+
var UsageMetric = import_zod14.z.enum(USAGE_METRICS);
|
|
3696
4421
|
var USAGE_WARNING_RATIO = 0.8;
|
|
3697
|
-
var UsageWarning =
|
|
4422
|
+
var UsageWarning = import_zod14.z.object({
|
|
3698
4423
|
metric: UsageMetric,
|
|
3699
|
-
used:
|
|
3700
|
-
limit:
|
|
4424
|
+
used: import_zod14.z.number().int().min(0),
|
|
4425
|
+
limit: import_zod14.z.number().int().min(0),
|
|
3701
4426
|
/** `approaching`: at or above USAGE_WARNING_RATIO of the limit; `reached`: at or above the limit. */
|
|
3702
|
-
level:
|
|
4427
|
+
level: import_zod14.z.enum(["approaching", "reached"])
|
|
3703
4428
|
});
|
|
3704
|
-
var Usage =
|
|
4429
|
+
var Usage = import_zod14.z.object({
|
|
3705
4430
|
plan: PlanId,
|
|
3706
4431
|
period_start: Timestamp,
|
|
3707
4432
|
period_end: Timestamp,
|
|
3708
|
-
metrics:
|
|
3709
|
-
warnings:
|
|
4433
|
+
metrics: import_zod14.z.record(UsageMetric, import_zod14.z.object({ used: import_zod14.z.number().int().min(0), limit: import_zod14.z.number().int().min(0).nullable() })),
|
|
4434
|
+
warnings: import_zod14.z.array(UsageWarning)
|
|
3710
4435
|
});
|
|
3711
|
-
var CheckoutRequest =
|
|
3712
|
-
plan:
|
|
3713
|
-
success_url:
|
|
3714
|
-
cancel_url:
|
|
4436
|
+
var CheckoutRequest = import_zod14.z.object({
|
|
4437
|
+
plan: import_zod14.z.enum(["starter", "growth"]),
|
|
4438
|
+
success_url: import_zod14.z.string().url(),
|
|
4439
|
+
cancel_url: import_zod14.z.string().url()
|
|
3715
4440
|
});
|
|
3716
|
-
var CheckoutSession =
|
|
3717
|
-
var PortalRequest =
|
|
3718
|
-
var PortalSession =
|
|
4441
|
+
var CheckoutSession = import_zod14.z.object({ url: import_zod14.z.string().url() });
|
|
4442
|
+
var PortalRequest = import_zod14.z.object({ return_url: import_zod14.z.string().url() });
|
|
4443
|
+
var PortalSession = import_zod14.z.object({ url: import_zod14.z.string().url() });
|
|
3719
4444
|
var USAGE_WARNING_HEADER = "x-mail-usage-warning";
|
|
3720
4445
|
function formatUsageWarningHeader(warnings) {
|
|
3721
4446
|
if (warnings.length === 0) return null;
|
|
@@ -3735,7 +4460,7 @@ function parseUsageWarningHeader(value) {
|
|
|
3735
4460
|
}
|
|
3736
4461
|
|
|
3737
4462
|
// src/routes.ts
|
|
3738
|
-
var
|
|
4463
|
+
var import_zod15 = require("zod");
|
|
3739
4464
|
var list = (item) => page(item);
|
|
3740
4465
|
var foundationRoutes = {
|
|
3741
4466
|
"workspaces.create": {
|
|
@@ -3943,7 +4668,7 @@ var templateRoutes = {
|
|
|
3943
4668
|
path: "/v1/templates/:id/export",
|
|
3944
4669
|
params: IdParams,
|
|
3945
4670
|
query: TemplateExportQuery,
|
|
3946
|
-
response:
|
|
4671
|
+
response: import_zod15.z.string(),
|
|
3947
4672
|
responseType: "text",
|
|
3948
4673
|
status: 200,
|
|
3949
4674
|
access: "read",
|
|
@@ -4267,7 +4992,7 @@ var sendingRoutes = {
|
|
|
4267
4992
|
path: "/v1/mailings/:id/export",
|
|
4268
4993
|
params: IdParams,
|
|
4269
4994
|
query: MailingExportQuery,
|
|
4270
|
-
response:
|
|
4995
|
+
response: import_zod15.z.string(),
|
|
4271
4996
|
responseType: "text",
|
|
4272
4997
|
status: 200,
|
|
4273
4998
|
access: "read",
|
|
@@ -4500,7 +5225,7 @@ var platformRoutes = {
|
|
|
4500
5225
|
"contactProperties.list": {
|
|
4501
5226
|
method: "GET",
|
|
4502
5227
|
path: "/v1/contact-properties",
|
|
4503
|
-
response:
|
|
5228
|
+
response: import_zod15.z.object({ data: import_zod15.z.array(ContactPropertyDefinition) }),
|
|
4504
5229
|
status: 200,
|
|
4505
5230
|
access: "read",
|
|
4506
5231
|
phase: "S4"
|
|
@@ -4799,7 +5524,7 @@ var billingRoutes = {
|
|
|
4799
5524
|
"billing.plans": {
|
|
4800
5525
|
method: "GET",
|
|
4801
5526
|
path: "/v1/billing/plans",
|
|
4802
|
-
response:
|
|
5527
|
+
response: import_zod15.z.object({ data: import_zod15.z.array(CatalogPlan) }),
|
|
4803
5528
|
status: 200,
|
|
4804
5529
|
access: "read",
|
|
4805
5530
|
phase: "S5"
|
|
@@ -4941,7 +5666,7 @@ var automationRoutes = {
|
|
|
4941
5666
|
method: "POST",
|
|
4942
5667
|
path: "/v1/automations/:id/pause",
|
|
4943
5668
|
params: IdParams,
|
|
4944
|
-
body:
|
|
5669
|
+
body: import_zod15.z.object({}).strict(),
|
|
4945
5670
|
response: Automation,
|
|
4946
5671
|
status: 200,
|
|
4947
5672
|
access: "write",
|
|
@@ -4951,7 +5676,7 @@ var automationRoutes = {
|
|
|
4951
5676
|
method: "POST",
|
|
4952
5677
|
path: "/v1/automations/:id/resume",
|
|
4953
5678
|
params: IdParams,
|
|
4954
|
-
body:
|
|
5679
|
+
body: import_zod15.z.object({}).strict(),
|
|
4955
5680
|
response: Automation,
|
|
4956
5681
|
status: 200,
|
|
4957
5682
|
access: "write",
|
|
@@ -4961,7 +5686,7 @@ var automationRoutes = {
|
|
|
4961
5686
|
method: "POST",
|
|
4962
5687
|
path: "/v1/automations/:id/archive",
|
|
4963
5688
|
params: IdParams,
|
|
4964
|
-
body:
|
|
5689
|
+
body: import_zod15.z.object({}).strict(),
|
|
4965
5690
|
response: Automation,
|
|
4966
5691
|
status: 200,
|
|
4967
5692
|
access: "write",
|
|
@@ -5095,13 +5820,13 @@ function matchRoute(method, pathname) {
|
|
|
5095
5820
|
for (const [id, route] of Object.entries(routes)) {
|
|
5096
5821
|
if (route.method !== method) continue;
|
|
5097
5822
|
const names = [];
|
|
5098
|
-
const
|
|
5823
|
+
const pattern2 = new RegExp(
|
|
5099
5824
|
"^" + route.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-z_]+)/g, (_, name) => {
|
|
5100
5825
|
names.push(name);
|
|
5101
5826
|
return "([^/]+)";
|
|
5102
5827
|
}) + "$"
|
|
5103
5828
|
);
|
|
5104
|
-
const match =
|
|
5829
|
+
const match = pattern2.exec(pathname);
|
|
5105
5830
|
if (!match) continue;
|
|
5106
5831
|
const params = {};
|
|
5107
5832
|
names.forEach((name, i) => {
|
|
@@ -5268,6 +5993,8 @@ function matchRoute(method, pathname) {
|
|
|
5268
5993
|
IMPORT_SKIP_REASONS,
|
|
5269
5994
|
IMPORT_STATUSES,
|
|
5270
5995
|
IMPORT_TERMINAL_STATUSES,
|
|
5996
|
+
INSERTION_PROBLEM_REASONS,
|
|
5997
|
+
INSERTION_TEXT_SLOTS,
|
|
5271
5998
|
INVITE_REFUSAL_REASONS,
|
|
5272
5999
|
INVITE_STATUSES,
|
|
5273
6000
|
Id,
|
|
@@ -5285,6 +6012,13 @@ function matchRoute(method, pathname) {
|
|
|
5285
6012
|
ImportStatus,
|
|
5286
6013
|
ImportWarning,
|
|
5287
6014
|
ImportedAsset,
|
|
6015
|
+
Insertion,
|
|
6016
|
+
InsertionChoice,
|
|
6017
|
+
InsertionChoiceKey,
|
|
6018
|
+
InsertionChoices,
|
|
6019
|
+
InsertionName,
|
|
6020
|
+
InsertionText,
|
|
6021
|
+
Insertions,
|
|
5288
6022
|
Invite,
|
|
5289
6023
|
InviteAccept,
|
|
5290
6024
|
InviteAccepted,
|
|
@@ -5312,6 +6046,9 @@ function matchRoute(method, pathname) {
|
|
|
5312
6046
|
MAX_IMPORT_BYTES,
|
|
5313
6047
|
MAX_IMPORT_ROWS,
|
|
5314
6048
|
MAX_IMPORT_URL_LENGTH,
|
|
6049
|
+
MAX_INSERTIONS,
|
|
6050
|
+
MAX_INSERTION_CHOICES,
|
|
6051
|
+
MAX_INSERTION_TEXT_LENGTH,
|
|
5315
6052
|
MAX_INVITE_TTL_DAYS,
|
|
5316
6053
|
MAX_IN_LIST,
|
|
5317
6054
|
MAX_LETTER_RECIPIENTS,
|
|
@@ -5384,6 +6121,8 @@ function matchRoute(method, pathname) {
|
|
|
5384
6121
|
Ok,
|
|
5385
6122
|
PLAN_FEATURES,
|
|
5386
6123
|
PLAN_IDS,
|
|
6124
|
+
PREVIEW_EMPTY,
|
|
6125
|
+
PREVIEW_FILLED,
|
|
5387
6126
|
PROVIDER_KINDS,
|
|
5388
6127
|
PageQuery,
|
|
5389
6128
|
Plan,
|
|
@@ -5484,6 +6223,8 @@ function matchRoute(method, pathname) {
|
|
|
5484
6223
|
TopicCreate,
|
|
5485
6224
|
TopicUpdate,
|
|
5486
6225
|
TrackingSettings,
|
|
6226
|
+
TranslationInsertion,
|
|
6227
|
+
TranslationInsertions,
|
|
5487
6228
|
UNSUBSCRIBE_PATH_PREFIX,
|
|
5488
6229
|
UNSUBSCRIBE_SOURCES,
|
|
5489
6230
|
USAGE_METRICS,
|
|
@@ -5525,7 +6266,10 @@ function matchRoute(method, pathname) {
|
|
|
5525
6266
|
WorkspaceSettings,
|
|
5526
6267
|
WorkspaceUpdate,
|
|
5527
6268
|
acceptsIdempotencyKey,
|
|
6269
|
+
addInsertion,
|
|
6270
|
+
addInsertionChoice,
|
|
5528
6271
|
addLanguageDraft,
|
|
6272
|
+
alignTranslationInsertions,
|
|
5529
6273
|
alwaysWaits,
|
|
5530
6274
|
automationFilterFamily,
|
|
5531
6275
|
automationRoutes,
|
|
@@ -5534,15 +6278,27 @@ function matchRoute(method, pathname) {
|
|
|
5534
6278
|
buildTemplatePatch,
|
|
5535
6279
|
canTransition,
|
|
5536
6280
|
canonicalLocale,
|
|
6281
|
+
choiceKeyProblem,
|
|
6282
|
+
chooseInsertion,
|
|
5537
6283
|
cycles,
|
|
6284
|
+
defaultPreviewPick,
|
|
5538
6285
|
defaultSettings,
|
|
6286
|
+
demoteForSave,
|
|
6287
|
+
demotedBySave,
|
|
6288
|
+
describeMissingTexts,
|
|
5539
6289
|
draftDocument,
|
|
6290
|
+
draftInsertionProblems,
|
|
5540
6291
|
draftsDirty,
|
|
5541
6292
|
draftsFromTemplate,
|
|
5542
6293
|
draftsPatch,
|
|
6294
|
+
editInsertionText,
|
|
5543
6295
|
editLanguageDraft,
|
|
6296
|
+
editMainInsertionText,
|
|
6297
|
+
editMainInsertions,
|
|
6298
|
+
effectivePreviewPicks,
|
|
5544
6299
|
emptyFlow,
|
|
5545
6300
|
errorBody,
|
|
6301
|
+
escapeMergeHtml,
|
|
5546
6302
|
exportFilename,
|
|
5547
6303
|
filterConditions,
|
|
5548
6304
|
filterDepth,
|
|
@@ -5552,30 +6308,65 @@ function matchRoute(method, pathname) {
|
|
|
5552
6308
|
formatExportWarningsHeader,
|
|
5553
6309
|
formatUsageWarningHeader,
|
|
5554
6310
|
foundationRoutes,
|
|
6311
|
+
insertionByProblem,
|
|
6312
|
+
insertionChoices,
|
|
6313
|
+
insertionCoverage,
|
|
6314
|
+
insertionInputs,
|
|
6315
|
+
insertionNameProblem,
|
|
6316
|
+
insertionProblems,
|
|
6317
|
+
insertionRefusalHint,
|
|
6318
|
+
insertionSlotLabel,
|
|
6319
|
+
insertionTextSlots,
|
|
6320
|
+
insertionTokens,
|
|
6321
|
+
insertionValue,
|
|
5555
6322
|
inviteRoutes,
|
|
5556
6323
|
isDayEntirelyPast,
|
|
5557
6324
|
isPublishable,
|
|
5558
6325
|
isRetryableError,
|
|
5559
6326
|
keepLanguageDocument,
|
|
6327
|
+
languageInsertions,
|
|
5560
6328
|
languageName,
|
|
5561
6329
|
languageTabs,
|
|
6330
|
+
mainInsertionText,
|
|
5562
6331
|
matchLocale,
|
|
5563
6332
|
matchRoute,
|
|
6333
|
+
missingInsertionTexts,
|
|
5564
6334
|
missingRequiredMergeFields,
|
|
6335
|
+
normalizeChoiceKey,
|
|
5565
6336
|
nowSeconds,
|
|
5566
6337
|
operationsWithAccess,
|
|
6338
|
+
ownEntry,
|
|
5567
6339
|
page,
|
|
5568
6340
|
parseExportWarningsHeader,
|
|
5569
6341
|
parseUsageWarningHeader,
|
|
6342
|
+
personalizeHtml,
|
|
6343
|
+
personalizeText,
|
|
6344
|
+
placementProblems,
|
|
5570
6345
|
platformRoutes,
|
|
6346
|
+
previewControls,
|
|
6347
|
+
previewInsertions,
|
|
6348
|
+
previewPersonalization,
|
|
5571
6349
|
primaryLanguage,
|
|
5572
6350
|
reachable,
|
|
6351
|
+
readyWithoutInsertions,
|
|
5573
6352
|
readyWithoutSubject,
|
|
5574
6353
|
rebaseDrafts,
|
|
6354
|
+
removeInsertion,
|
|
6355
|
+
removeInsertionChoice,
|
|
5575
6356
|
removeLanguageDraft,
|
|
6357
|
+
renameInsertion,
|
|
6358
|
+
renameInsertionChecked,
|
|
6359
|
+
renameInsertionChoice,
|
|
6360
|
+
renameInsertionChoiceChecked,
|
|
6361
|
+
resolveInsertions,
|
|
5576
6362
|
restorePatch,
|
|
5577
6363
|
routes,
|
|
6364
|
+
sameJson,
|
|
6365
|
+
sampleMergeValue,
|
|
6366
|
+
saveDemotions,
|
|
5578
6367
|
sendingRoutes,
|
|
6368
|
+
setInsertionBy,
|
|
6369
|
+
setInsertionFilled,
|
|
5579
6370
|
setLanguageStatus,
|
|
5580
6371
|
signWebhook,
|
|
5581
6372
|
statusForError,
|
|
@@ -5586,7 +6377,10 @@ function matchRoute(method, pathname) {
|
|
|
5586
6377
|
testGroupSize,
|
|
5587
6378
|
tightLoops,
|
|
5588
6379
|
timingSafeEqualString,
|
|
6380
|
+
translationInsertionProblems,
|
|
6381
|
+
translationTextFromInput,
|
|
5589
6382
|
triggerKey,
|
|
6383
|
+
unplacedInsertions,
|
|
5590
6384
|
validateAutomation,
|
|
5591
6385
|
verifyWebhook
|
|
5592
6386
|
});
|