@marlinjai/mail-contract 0.5.0 → 0.6.0

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