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