@marlinjai/mail-contract 0.5.0 → 0.6.1

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