@marlinjai/mail-contract 0.4.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.
Files changed (5) hide show
  1. package/dist/index.d.mts +16086 -2134
  2. package/dist/index.d.ts +16086 -2134
  3. package/dist/index.js +1955 -762
  4. package/dist/index.mjs +1853 -762
  5. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -150,6 +150,7 @@ __export(index_exports, {
150
150
  EXPORT_WARNING_COUNT_HEADER: () => EXPORT_WARNING_COUNT_HEADER,
151
151
  Email: () => Email,
152
152
  EmailStep: () => EmailStep,
153
+ EmailStepLanguage: () => EmailStepLanguage,
153
154
  ErrorBody: () => ErrorBody,
154
155
  ErrorCodeSchema: () => ErrorCodeSchema,
155
156
  Event: () => Event,
@@ -174,6 +175,8 @@ __export(index_exports, {
174
175
  IMPORT_SKIP_REASONS: () => IMPORT_SKIP_REASONS,
175
176
  IMPORT_STATUSES: () => IMPORT_STATUSES,
176
177
  IMPORT_TERMINAL_STATUSES: () => IMPORT_TERMINAL_STATUSES,
178
+ INSERTION_PROBLEM_REASONS: () => INSERTION_PROBLEM_REASONS,
179
+ INSERTION_TEXT_SLOTS: () => INSERTION_TEXT_SLOTS,
177
180
  INVITE_REFUSAL_REASONS: () => INVITE_REFUSAL_REASONS,
178
181
  INVITE_STATUSES: () => INVITE_STATUSES,
179
182
  Id: () => Id,
@@ -191,6 +194,13 @@ __export(index_exports, {
191
194
  ImportStatus: () => ImportStatus,
192
195
  ImportWarning: () => ImportWarning,
193
196
  ImportedAsset: () => ImportedAsset,
197
+ Insertion: () => Insertion,
198
+ InsertionChoice: () => InsertionChoice,
199
+ InsertionChoiceKey: () => InsertionChoiceKey,
200
+ InsertionChoices: () => InsertionChoices,
201
+ InsertionName: () => InsertionName,
202
+ InsertionText: () => InsertionText,
203
+ Insertions: () => Insertions,
194
204
  Invite: () => Invite,
195
205
  InviteAccept: () => InviteAccept,
196
206
  InviteAccepted: () => InviteAccepted,
@@ -201,6 +211,8 @@ __export(index_exports, {
201
211
  JsonValue: () => JsonValue,
202
212
  LETTER_RECIPIENT_REFUSAL: () => LETTER_RECIPIENT_REFUSAL,
203
213
  LIST_UNSUBSCRIBE_POST_VALUE: () => LIST_UNSUBSCRIBE_POST_VALUE,
214
+ LOCALE_TAG_PATTERN: () => LOCALE_TAG_PATTERN,
215
+ LocaleTag: () => LocaleTag,
204
216
  MAILING_ACTIONS: () => MAILING_ACTIONS,
205
217
  MAILING_KINDS: () => MAILING_KINDS,
206
218
  MAILING_STATUSES: () => MAILING_STATUSES,
@@ -216,6 +228,9 @@ __export(index_exports, {
216
228
  MAX_IMPORT_BYTES: () => MAX_IMPORT_BYTES,
217
229
  MAX_IMPORT_ROWS: () => MAX_IMPORT_ROWS,
218
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,
219
234
  MAX_INVITE_TTL_DAYS: () => MAX_INVITE_TTL_DAYS,
220
235
  MAX_IN_LIST: () => MAX_IN_LIST,
221
236
  MAX_LETTER_RECIPIENTS: () => MAX_LETTER_RECIPIENTS,
@@ -225,12 +240,15 @@ __export(index_exports, {
225
240
  MAX_MJML_ELEMENTS: () => MAX_MJML_ELEMENTS,
226
241
  MAX_MJML_IMPORT_BYTES: () => MAX_MJML_IMPORT_BYTES,
227
242
  MAX_PAGE_LIMIT: () => MAX_PAGE_LIMIT,
243
+ MAX_PREHEADER_LENGTH: () => MAX_PREHEADER_LENGTH,
228
244
  MAX_RECIPIENTS_PER_BATCH: () => MAX_RECIPIENTS_PER_BATCH,
229
245
  MAX_RE_ENTRY_COOLDOWN_SECONDS: () => MAX_RE_ENTRY_COOLDOWN_SECONDS,
230
246
  MAX_SAVED_SECTIONS: () => MAX_SAVED_SECTIONS,
231
247
  MAX_SPLIT_BRANCHES: () => MAX_SPLIT_BRANCHES,
232
248
  MAX_STEPS: () => MAX_STEPS,
233
249
  MAX_STEP_VISITS: () => MAX_STEP_VISITS,
250
+ MAX_SUBJECT_LENGTH: () => MAX_SUBJECT_LENGTH,
251
+ MAX_TEMPLATE_TRANSLATIONS: () => MAX_TEMPLATE_TRANSLATIONS,
234
252
  MAX_TRIGGERS: () => MAX_TRIGGERS,
235
253
  MEMBER_ROLES: () => MEMBER_ROLES,
236
254
  MERGE_FIELD_PATTERN: () => MERGE_FIELD_PATTERN,
@@ -248,6 +266,9 @@ __export(index_exports, {
248
266
  MailingExportQuery: () => MailingExportQuery,
249
267
  MailingFinishedData: () => MailingFinishedData,
250
268
  MailingKind: () => MailingKind,
269
+ MailingLanguage: () => MailingLanguage,
270
+ MailingLanguageReport: () => MailingLanguageReport,
271
+ MailingLanguageStatus: () => MailingLanguageStatus,
251
272
  MailingListQuery: () => MailingListQuery,
252
273
  MailingMetadata: () => MailingMetadata,
253
274
  MailingRetryFailedRequest: () => MailingRetryFailedRequest,
@@ -282,6 +303,8 @@ __export(index_exports, {
282
303
  Ok: () => Ok,
283
304
  PLAN_FEATURES: () => PLAN_FEATURES,
284
305
  PLAN_IDS: () => PLAN_IDS,
306
+ PREVIEW_EMPTY: () => PREVIEW_EMPTY,
307
+ PREVIEW_FILLED: () => PREVIEW_FILLED,
285
308
  PROVIDER_KINDS: () => PROVIDER_KINDS,
286
309
  PageQuery: () => PageQuery,
287
310
  Plan: () => Plan,
@@ -348,6 +371,7 @@ __export(index_exports, {
348
371
  SuppressionCreate: () => SuppressionCreate,
349
372
  SuppressionListQuery: () => SuppressionListQuery,
350
373
  SuppressionReason: () => SuppressionReason,
374
+ TEMPLATE_LANGUAGE_STATUSES: () => TEMPLATE_LANGUAGE_STATUSES,
351
375
  TERMINAL_MAILING_STATUSES: () => TERMINAL_MAILING_STATUSES,
352
376
  Tag: () => Tag,
353
377
  TagAssignment: () => TagAssignment,
@@ -363,8 +387,13 @@ __export(index_exports, {
363
387
  TemplateImportPreview: () => TemplateImportPreview,
364
388
  TemplateImportPreviewRequest: () => TemplateImportPreviewRequest,
365
389
  TemplateImportResult: () => TemplateImportResult,
390
+ TemplateLanguageStatus: () => TemplateLanguageStatus,
366
391
  TemplateListQuery: () => TemplateListQuery,
392
+ TemplatePreheader: () => TemplatePreheader,
393
+ TemplateSubject: () => TemplateSubject,
367
394
  TemplateSummary: () => TemplateSummary,
395
+ TemplateTranslation: () => TemplateTranslation,
396
+ TemplateTranslations: () => TemplateTranslations,
368
397
  TemplateUpdate: () => TemplateUpdate,
369
398
  TemplateUpdatedData: () => TemplateUpdatedData,
370
399
  TemplateVersion: () => TemplateVersion,
@@ -376,6 +405,8 @@ __export(index_exports, {
376
405
  TopicCreate: () => TopicCreate,
377
406
  TopicUpdate: () => TopicUpdate,
378
407
  TrackingSettings: () => TrackingSettings,
408
+ TranslationInsertion: () => TranslationInsertion,
409
+ TranslationInsertions: () => TranslationInsertions,
379
410
  UNSUBSCRIBE_PATH_PREFIX: () => UNSUBSCRIBE_PATH_PREFIX,
380
411
  UNSUBSCRIBE_SOURCES: () => UNSUBSCRIBE_SOURCES,
381
412
  USAGE_METRICS: () => USAGE_METRICS,
@@ -417,16 +448,39 @@ __export(index_exports, {
417
448
  WorkspaceSettings: () => WorkspaceSettings,
418
449
  WorkspaceUpdate: () => WorkspaceUpdate,
419
450
  acceptsIdempotencyKey: () => acceptsIdempotencyKey,
451
+ addInsertion: () => addInsertion,
452
+ addInsertionChoice: () => addInsertionChoice,
453
+ addLanguageDraft: () => addLanguageDraft,
454
+ alignTranslationInsertions: () => alignTranslationInsertions,
420
455
  alwaysWaits: () => alwaysWaits,
421
456
  automationFilterFamily: () => automationFilterFamily,
422
457
  automationRoutes: () => automationRoutes,
423
458
  billingRoutes: () => billingRoutes,
424
459
  buildPath: () => buildPath,
460
+ buildTemplatePatch: () => buildTemplatePatch,
425
461
  canTransition: () => canTransition,
462
+ canonicalLocale: () => canonicalLocale,
463
+ choiceKeyProblem: () => choiceKeyProblem,
464
+ chooseInsertion: () => chooseInsertion,
426
465
  cycles: () => cycles,
466
+ defaultPreviewPick: () => defaultPreviewPick,
427
467
  defaultSettings: () => defaultSettings,
468
+ demoteForSave: () => demoteForSave,
469
+ demotedBySave: () => demotedBySave,
470
+ describeMissingTexts: () => describeMissingTexts,
471
+ draftDocument: () => draftDocument,
472
+ draftInsertionProblems: () => draftInsertionProblems,
473
+ draftsDirty: () => draftsDirty,
474
+ draftsFromTemplate: () => draftsFromTemplate,
475
+ draftsPatch: () => draftsPatch,
476
+ editInsertionText: () => editInsertionText,
477
+ editLanguageDraft: () => editLanguageDraft,
478
+ editMainInsertionText: () => editMainInsertionText,
479
+ editMainInsertions: () => editMainInsertions,
480
+ effectivePreviewPicks: () => effectivePreviewPicks,
428
481
  emptyFlow: () => emptyFlow,
429
482
  errorBody: () => errorBody,
483
+ escapeMergeHtml: () => escapeMergeHtml,
430
484
  exportFilename: () => exportFilename,
431
485
  filterConditions: () => filterConditions,
432
486
  filterDepth: () => filterDepth,
@@ -436,30 +490,78 @@ __export(index_exports, {
436
490
  formatExportWarningsHeader: () => formatExportWarningsHeader,
437
491
  formatUsageWarningHeader: () => formatUsageWarningHeader,
438
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,
439
504
  inviteRoutes: () => inviteRoutes,
440
505
  isDayEntirelyPast: () => isDayEntirelyPast,
441
506
  isPublishable: () => isPublishable,
442
507
  isRetryableError: () => isRetryableError,
508
+ keepLanguageDocument: () => keepLanguageDocument,
509
+ languageInsertions: () => languageInsertions,
510
+ languageName: () => languageName,
511
+ languageTabs: () => languageTabs,
512
+ mainInsertionText: () => mainInsertionText,
513
+ matchLocale: () => matchLocale,
443
514
  matchRoute: () => matchRoute,
515
+ missingInsertionTexts: () => missingInsertionTexts,
444
516
  missingRequiredMergeFields: () => missingRequiredMergeFields,
517
+ normalizeChoiceKey: () => normalizeChoiceKey,
445
518
  nowSeconds: () => nowSeconds,
446
519
  operationsWithAccess: () => operationsWithAccess,
520
+ ownEntry: () => ownEntry,
447
521
  page: () => page,
448
522
  parseExportWarningsHeader: () => parseExportWarningsHeader,
449
523
  parseUsageWarningHeader: () => parseUsageWarningHeader,
524
+ personalizeHtml: () => personalizeHtml,
525
+ personalizeText: () => personalizeText,
526
+ placementProblems: () => placementProblems,
450
527
  platformRoutes: () => platformRoutes,
528
+ previewControls: () => previewControls,
529
+ previewInsertions: () => previewInsertions,
530
+ previewPersonalization: () => previewPersonalization,
531
+ primaryLanguage: () => primaryLanguage,
451
532
  reachable: () => reachable,
533
+ readyWithoutInsertions: () => readyWithoutInsertions,
534
+ readyWithoutSubject: () => readyWithoutSubject,
535
+ rebaseDrafts: () => rebaseDrafts,
536
+ removeInsertion: () => removeInsertion,
537
+ removeInsertionChoice: () => removeInsertionChoice,
538
+ removeLanguageDraft: () => removeLanguageDraft,
539
+ renameInsertion: () => renameInsertion,
540
+ renameInsertionChecked: () => renameInsertionChecked,
541
+ renameInsertionChoice: () => renameInsertionChoice,
542
+ renameInsertionChoiceChecked: () => renameInsertionChoiceChecked,
543
+ resolveInsertions: () => resolveInsertions,
544
+ restorePatch: () => restorePatch,
452
545
  routes: () => routes,
546
+ sampleMergeValue: () => sampleMergeValue,
547
+ saveDemotions: () => saveDemotions,
453
548
  sendingRoutes: () => sendingRoutes,
549
+ setInsertionBy: () => setInsertionBy,
550
+ setInsertionFilled: () => setInsertionFilled,
551
+ setLanguageStatus: () => setLanguageStatus,
454
552
  signWebhook: () => signWebhook,
455
553
  statusForError: () => statusForError,
456
554
  stepTargets: () => stepTargets,
555
+ storedTemplateLine: () => storedTemplateLine,
457
556
  stronglyConnected: () => stronglyConnected,
458
557
  templateRoutes: () => templateRoutes,
459
558
  testGroupSize: () => testGroupSize,
460
559
  tightLoops: () => tightLoops,
461
560
  timingSafeEqualString: () => timingSafeEqualString,
561
+ translationInsertionProblems: () => translationInsertionProblems,
562
+ translationTextFromInput: () => translationTextFromInput,
462
563
  triggerKey: () => triggerKey,
564
+ unplacedInsertions: () => unplacedInsertions,
463
565
  validateAutomation: () => validateAutomation,
464
566
  verifyWebhook: () => verifyWebhook
465
567
  });
@@ -489,6 +591,29 @@ function page(item) {
489
591
  }
490
592
  var IdParams = import_zod.z.object({ id: Id });
491
593
  var Ok = import_zod.z.object({ ok: import_zod.z.literal(true) });
594
+ var LOCALE_TAG_PATTERN = /^[A-Za-z]{2,3}(?:-[A-Za-z]{4})?(?:-(?:[A-Za-z]{2}|[0-9]{3}))?$/;
595
+ var LocaleTag = import_zod.z.string().min(2).max(35).regex(LOCALE_TAG_PATTERN, "a language tag such as en, fr or pt-BR");
596
+ function canonicalLocale(tag) {
597
+ if (typeof tag !== "string") return null;
598
+ const trimmed = tag.trim().replace(/_/g, "-");
599
+ if (!LOCALE_TAG_PATTERN.test(trimmed)) return null;
600
+ return trimmed.split("-").map((part, i) => {
601
+ if (i === 0) return part.toLowerCase();
602
+ if (part.length === 4) return part[0].toUpperCase() + part.slice(1).toLowerCase();
603
+ return part.toUpperCase();
604
+ }).join("-");
605
+ }
606
+ function primaryLanguage(tag) {
607
+ return canonicalLocale(tag)?.split("-")[0] ?? null;
608
+ }
609
+ function matchLocale(wanted, available) {
610
+ const canonical = canonicalLocale(wanted);
611
+ if (!canonical) return null;
612
+ const exact = available.find((a) => canonicalLocale(a) === canonical);
613
+ if (exact !== void 0) return exact;
614
+ const primary = canonical.split("-")[0];
615
+ return available.find((a) => primaryLanguage(a) === primary) ?? null;
616
+ }
492
617
 
493
618
  // src/errors.ts
494
619
  var import_zod2 = require("zod");
@@ -589,119 +714,483 @@ var API_VERSION_PREFIX = "/v1";
589
714
  var HEALTH_PATH = "/healthz";
590
715
 
591
716
  // src/workspace.ts
592
- var import_zod8 = require("zod");
717
+ var import_zod9 = require("zod");
593
718
 
594
719
  // src/automations.ts
595
- var import_zod7 = require("zod");
720
+ var import_zod8 = require("zod");
596
721
 
597
722
  // src/templates.ts
723
+ var import_zod4 = require("zod");
724
+
725
+ // src/insertions.ts
598
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
599
1015
  var DOCUMENT_SCHEMA_VERSIONS = ["1.0", "1.1"];
600
- var DocumentSchemaVersion = import_zod3.z.enum(DOCUMENT_SCHEMA_VERSIONS);
601
- var TemplateDocument = import_zod3.z.object({
1016
+ var DocumentSchemaVersion = import_zod4.z.enum(DOCUMENT_SCHEMA_VERSIONS);
1017
+ var TemplateDocument = import_zod4.z.object({
602
1018
  version: DocumentSchemaVersion,
603
- metadata: import_zod3.z.record(import_zod3.z.unknown()),
604
- 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()))
605
1021
  }).passthrough();
606
1022
  var MAX_DOCUMENT_BYTES = 1e6;
607
- var Template = import_zod3.z.object({
1023
+ var MAX_SUBJECT_LENGTH = 998;
1024
+ var MAX_PREHEADER_LENGTH = 500;
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");
1027
+ var TEMPLATE_LANGUAGE_STATUSES = ["draft", "ready"];
1028
+ var TemplateLanguageStatus = import_zod4.z.enum(TEMPLATE_LANGUAGE_STATUSES);
1029
+ var TemplateTranslation = import_zod4.z.object({
1030
+ subject: TemplateSubject.nullable(),
1031
+ preheader: TemplatePreheader.nullable(),
1032
+ document: TemplateDocument,
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()
1045
+ });
1046
+ var MAX_TEMPLATE_TRANSLATIONS = 19;
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({
608
1049
  id: Id,
609
- name: import_zod3.z.string().min(1).max(200),
610
- 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(),
1052
+ /**
1053
+ * The main language: what `subject`, `preheader` and `document` are written
1054
+ * in, and what every recipient without a ready version of their own gets.
1055
+ * Set from the workspace's `default_locale` when the template is created
1056
+ * unless given, and never changed by a later change of that setting.
1057
+ */
1058
+ locale: LocaleTag,
1059
+ /** The main language's subject line; null when the mailing that uses the template gives one. */
1060
+ subject: TemplateSubject.nullable(),
1061
+ preheader: TemplatePreheader.nullable(),
611
1062
  document: TemplateDocument,
612
- /** Increments on every saved change. */
613
- version: import_zod3.z.number().int().min(1),
614
- thumbnail_url: import_zod3.z.string().url().nullable(),
1063
+ /**
1064
+ * The other languages. A version whose language the workspace no longer
1065
+ * lists is kept, but no send uses it until the language is listed again.
1066
+ */
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,
1075
+ /** Increments on every saved change, of any language. */
1076
+ version: import_zod4.z.number().int().min(1),
1077
+ thumbnail_url: import_zod4.z.string().url().nullable(),
615
1078
  archived_at: Timestamp.nullable(),
616
1079
  created_at: Timestamp,
617
1080
  updated_at: Timestamp
618
1081
  });
619
- var TemplateSummary = Template.omit({ document: true });
620
- var TemplateCreate = import_zod3.z.object({
621
- name: import_zod3.z.string().min(1).max(200),
622
- description: import_zod3.z.string().max(2e3).optional(),
623
- document: TemplateDocument
624
- });
625
- var TemplateUpdate = import_zod3.z.object({
626
- base_version: import_zod3.z.number().int().min(1),
627
- name: import_zod3.z.string().min(1).max(200).optional(),
628
- description: import_zod3.z.string().max(2e3).nullable().optional(),
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())
1086
+ });
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(),
1090
+ locale: LocaleTag.optional(),
1091
+ subject: TemplateSubject.nullable().optional(),
1092
+ preheader: TemplatePreheader.nullable().optional(),
1093
+ document: TemplateDocument,
1094
+ translations: TemplateTranslations.optional(),
1095
+ insertions: Insertions.optional()
1096
+ });
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(),
1101
+ locale: LocaleTag.optional(),
1102
+ subject: TemplateSubject.nullable().optional(),
1103
+ preheader: TemplatePreheader.nullable().optional(),
629
1104
  document: TemplateDocument.optional(),
630
- 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()
631
1114
  }).refine(
632
- (v) => v.name !== void 0 || v.description !== void 0 || v.document !== 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,
633
1116
  "at least one field besides base_version"
634
1117
  );
635
- var SavedSectionBody = import_zod3.z.record(import_zod3.z.unknown());
1118
+ var SavedSectionBody = import_zod4.z.record(import_zod4.z.unknown());
636
1119
  var MAX_SAVED_SECTIONS = 50;
637
- var SavedSection = import_zod3.z.object({
1120
+ var SavedSection = import_zod4.z.object({
638
1121
  id: Id,
639
- name: import_zod3.z.string().min(1).max(200),
640
- 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(),
641
1124
  section: SavedSectionBody,
642
- created_by: import_zod3.z.string().nullable(),
1125
+ created_by: import_zod4.z.string().nullable(),
643
1126
  created_at: Timestamp,
644
1127
  updated_at: Timestamp
645
1128
  });
646
- var SavedSectionCreate = import_zod3.z.object({
647
- name: import_zod3.z.string().min(1).max(200),
648
- 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(),
649
1132
  section: SavedSectionBody
650
1133
  });
651
- var SavedSectionUpdate = import_zod3.z.object({
652
- name: import_zod3.z.string().min(1).max(200).optional(),
653
- 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(),
654
1137
  section: SavedSectionBody.optional()
655
1138
  }).refine((v) => v.name !== void 0 || v.description !== void 0 || v.section !== void 0, "at least one field");
656
1139
  var TemplateListQuery = PageQuery.extend({
657
- archived: import_zod3.z.enum(["true", "false"]).optional()
1140
+ archived: import_zod4.z.enum(["true", "false"]).optional()
658
1141
  });
659
- var TemplateVersion = import_zod3.z.object({
1142
+ var TemplateVersion = import_zod4.z.object({
660
1143
  template_id: Id,
661
- version: import_zod3.z.number().int().min(1),
1144
+ version: import_zod4.z.number().int().min(1),
1145
+ locale: LocaleTag,
1146
+ subject: TemplateSubject.nullable(),
1147
+ preheader: TemplatePreheader.nullable(),
662
1148
  document: TemplateDocument,
663
- created_by: import_zod3.z.string().nullable(),
1149
+ translations: TemplateTranslations,
1150
+ insertions: Insertions,
1151
+ created_by: import_zod4.z.string().nullable(),
664
1152
  created_at: Timestamp
665
1153
  });
666
- var TemplateVersionParams = import_zod3.z.object({
1154
+ var TemplateVersionParams = import_zod4.z.object({
667
1155
  id: Id,
668
- version: import_zod3.z.coerce.number().int().min(1)
1156
+ version: import_zod4.z.coerce.number().int().min(1)
669
1157
  });
670
- var CompileMessage = import_zod3.z.object({
671
- message: import_zod3.z.string().min(1),
1158
+ var CompileMessage = import_zod4.z.object({
1159
+ message: import_zod4.z.string().min(1),
672
1160
  /** Where in the document or the MJML the message points, when known. */
673
- path: import_zod3.z.string().optional(),
674
- 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()
675
1163
  });
676
- var CompileResult = import_zod3.z.object({
677
- mjml: import_zod3.z.string(),
678
- html: import_zod3.z.string(),
679
- warnings: import_zod3.z.array(CompileMessage),
680
- 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)
681
1169
  });
682
- var TemplateCompileRequest = import_zod3.z.object({
683
- 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(),
1172
+ locale: LocaleTag.optional()
684
1173
  });
685
- var CompileRequest = import_zod3.z.object({
1174
+ var CompileRequest = import_zod4.z.object({
686
1175
  document: TemplateDocument
687
1176
  });
688
1177
  var ASSET_CONTENT_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
689
- var AssetContentType = import_zod3.z.enum(ASSET_CONTENT_TYPES);
1178
+ var AssetContentType = import_zod4.z.enum(ASSET_CONTENT_TYPES);
690
1179
  var MAX_ASSET_BYTES = 10 * 1024 * 1024;
691
- var Asset = import_zod3.z.object({
1180
+ var Asset = import_zod4.z.object({
692
1181
  id: Id,
693
- url: import_zod3.z.string().url(),
1182
+ url: import_zod4.z.string().url(),
694
1183
  content_type: AssetContentType,
695
- size_bytes: import_zod3.z.number().int().min(1).max(MAX_ASSET_BYTES),
696
- width: import_zod3.z.number().int().min(1).nullable(),
697
- height: import_zod3.z.number().int().min(1).nullable(),
698
- 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),
699
1188
  created_at: Timestamp
700
1189
  });
701
1190
  var ASSET_UPLOAD_FIELD = "file";
702
1191
  var MAX_IMPORT_URL_LENGTH = 2048;
703
- var AssetImport = import_zod3.z.object({
704
- 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) => {
705
1194
  try {
706
1195
  const url = new URL(u);
707
1196
  if (url.protocol === "https:") return true;
@@ -711,7 +1200,7 @@ var AssetImport = import_zod3.z.object({
711
1200
  }
712
1201
  }, "must be https (http only for localhost)"),
713
1202
  /** The stored file name; derived from the address when omitted. */
714
- filename: import_zod3.z.string().min(1).max(255).optional()
1203
+ filename: import_zod4.z.string().min(1).max(255).optional()
715
1204
  });
716
1205
  var MAX_MJML_IMPORT_BYTES = 512 * 1024;
717
1206
  var MAX_MJML_DEPTH = 32;
@@ -726,55 +1215,56 @@ var MJML_IMPORT_REFUSALS = [
726
1215
  "too_complex",
727
1216
  "invalid_document"
728
1217
  ];
729
- var ImportWarning = import_zod3.z.object({
730
- severity: import_zod3.z.enum(["info", "warning"]),
1218
+ var ImportWarning = import_zod4.z.object({
1219
+ severity: import_zod4.z.enum(["info", "warning"]),
731
1220
  /** Stable kind, e.g. `kept_as_html`, `unknown_component`, `remote_image_not_imported`. */
732
- code: import_zod3.z.string().min(1).max(64),
1221
+ code: import_zod4.z.string().min(1).max(64),
733
1222
  /** Where in the source, e.g. `mj-body > mj-section[2] > mj-column[1] > mj-social[1]`. */
734
- path: import_zod3.z.string(),
735
- line: import_zod3.z.number().int().min(1).optional(),
736
- message: import_zod3.z.string().min(1),
737
- 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()
738
1227
  });
739
- var RemoteImage = import_zod3.z.object({
740
- url: import_zod3.z.string().min(1),
1228
+ var RemoteImage = import_zod4.z.object({
1229
+ url: import_zod4.z.string().min(1),
741
1230
  /** The element and attribute it is loaded from, e.g. `<img src>`. */
742
- where: import_zod3.z.string()
1231
+ where: import_zod4.z.string()
743
1232
  });
744
- var ImportedAsset = import_zod3.z.object({
745
- source_url: import_zod3.z.string().min(1),
1233
+ var ImportedAsset = import_zod4.z.object({
1234
+ source_url: import_zod4.z.string().min(1),
746
1235
  asset_id: Id,
747
- url: import_zod3.z.string().url()
1236
+ url: import_zod4.z.string().url()
748
1237
  });
749
- var mjmlSource = import_zod3.z.string().min(1, "paste or upload the MJML");
750
- var TemplateImportPreviewRequest = import_zod3.z.object({ mjml: mjmlSource });
751
- 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({
752
1241
  document: TemplateDocument,
753
- warnings: import_zod3.z.array(ImportWarning),
1242
+ warnings: import_zod4.z.array(ImportWarning),
754
1243
  /** The document compiled under the workspace's asset policy, exactly as a send would. */
755
1244
  compiled: CompileResult,
756
1245
  /** Images loaded from outside the service; under `service_only` each is also a compile error. */
757
- remote_images: import_zod3.z.array(RemoteImage),
758
- 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"])
759
1248
  });
760
- var TemplateImport = import_zod3.z.object({
761
- name: import_zod3.z.string().min(1).max(200),
762
- 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(),
763
1252
  mjml: mjmlSource,
764
- import_remote_assets: import_zod3.z.boolean().optional()
1253
+ import_remote_assets: import_zod4.z.boolean().optional()
765
1254
  });
766
- var TemplateImportResult = import_zod3.z.object({
1255
+ var TemplateImportResult = import_zod4.z.object({
767
1256
  template: Template,
768
- warnings: import_zod3.z.array(ImportWarning),
769
- imported_assets: import_zod3.z.array(ImportedAsset)
1257
+ warnings: import_zod4.z.array(ImportWarning),
1258
+ imported_assets: import_zod4.z.array(ImportedAsset)
770
1259
  });
771
1260
  var EXPORT_FORMATS = ["mjml", "html"];
772
- var ExportFormat = import_zod3.z.enum(EXPORT_FORMATS);
773
- var TemplateExportQuery = import_zod3.z.object({
1261
+ var ExportFormat = import_zod4.z.enum(EXPORT_FORMATS);
1262
+ var TemplateExportQuery = import_zod4.z.object({
774
1263
  format: ExportFormat,
775
- version: import_zod3.z.coerce.number().int().min(1).optional()
1264
+ version: import_zod4.z.coerce.number().int().min(1).optional(),
1265
+ locale: LocaleTag.optional()
776
1266
  });
777
- var MailingExportQuery = import_zod3.z.object({ format: ExportFormat });
1267
+ var MailingExportQuery = import_zod4.z.object({ format: ExportFormat });
778
1268
  var EXPORT_CONTENT_TYPES = {
779
1269
  mjml: "text/plain; charset=utf-8",
780
1270
  html: "text/html; charset=utf-8"
@@ -793,7 +1283,7 @@ function formatExportWarningsHeader(messages) {
793
1283
  function parseExportWarningsHeader(value) {
794
1284
  if (!value) return [];
795
1285
  try {
796
- 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)));
797
1287
  return parsed.success ? parsed.data : [];
798
1288
  } catch {
799
1289
  return [];
@@ -805,32 +1295,32 @@ function exportFilename(name, format) {
805
1295
  }
806
1296
 
807
1297
  // src/platform.ts
808
- var import_zod6 = require("zod");
1298
+ var import_zod7 = require("zod");
809
1299
 
810
1300
  // src/mailings.ts
811
- var import_zod5 = require("zod");
1301
+ var import_zod6 = require("zod");
812
1302
 
813
1303
  // src/ab-test.ts
814
- var import_zod4 = require("zod");
815
- var AbVariant = import_zod4.z.object({
816
- key: import_zod4.z.string().regex(/^[a-z]$/, "a single lowercase letter"),
817
- 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(),
818
1308
  /** Replaces the mailing's document for this variant. */
819
- document: import_zod4.z.record(import_zod4.z.unknown()).optional(),
1309
+ document: import_zod5.z.record(import_zod5.z.unknown()).optional(),
820
1310
  /**
821
1311
  * Keeps the document this variant key already has in the mailing's current
822
1312
  * test, so a test can be changed without sending every document again. The
823
1313
  * service refuses it (`validation_failed`) when that variant has none.
824
1314
  */
825
- keep_document: import_zod4.z.literal(true).optional()
1315
+ keep_document: import_zod5.z.literal(true).optional()
826
1316
  });
827
1317
  var AB_WINNER_METRICS = ["opens", "clicks", "manual"];
828
- var AbTestConfig = import_zod4.z.object({
829
- 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),
830
1320
  /** Share of recipients in the test group, split evenly across variants; the rest get the winner. */
831
- test_fraction: import_zod4.z.number().gt(0).lte(1),
832
- winner_metric: import_zod4.z.enum(AB_WINNER_METRICS),
833
- 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()
834
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), {
835
1325
  message: "every variant changes the subject, the document or both",
836
1326
  path: ["variants"]
@@ -842,19 +1332,19 @@ var AbTestConfig = import_zod4.z.object({
842
1332
  path: ["decide_after_minutes"]
843
1333
  });
844
1334
  var AB_TEST_STATUSES = ["pending", "testing", "awaiting_pick", "decided"];
845
- var AbTestState = import_zod4.z.object({
846
- 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() })),
847
- test_fraction: import_zod4.z.number(),
848
- winner_metric: import_zod4.z.enum(AB_WINNER_METRICS),
849
- decide_after_minutes: import_zod4.z.number().int().nullable(),
850
- 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),
851
1341
  /** When the metric decides; null for manual or before the start. */
852
1342
  decide_at: Timestamp.nullable(),
853
- winner: import_zod4.z.string().nullable(),
854
- 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(),
855
1345
  decided_at: Timestamp.nullable()
856
1346
  });
857
- 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]$/) });
858
1348
  function testGroupSize(total, fraction, variants) {
859
1349
  return Math.min(total, Math.max(variants, Math.ceil(total * fraction)));
860
1350
  }
@@ -869,9 +1359,9 @@ var MAILING_STATUSES = [
869
1359
  "partially_failed",
870
1360
  "cancelled"
871
1361
  ];
872
- var MailingStatus = import_zod5.z.enum(MAILING_STATUSES);
1362
+ var MailingStatus = import_zod6.z.enum(MAILING_STATUSES);
873
1363
  var MAILING_ACTIONS = ["send", "schedule", "unschedule", "pause", "resume", "cancel", "retry-failed"];
874
- var MailingAction = import_zod5.z.enum(MAILING_ACTIONS);
1364
+ var MailingAction = import_zod6.z.enum(MAILING_ACTIONS);
875
1365
  var MAILING_TRANSITIONS = {
876
1366
  draft: ["send", "schedule", "cancel"],
877
1367
  scheduled: ["send", "schedule", "unschedule", "cancel"],
@@ -886,26 +1376,46 @@ function canTransition(status, action) {
886
1376
  }
887
1377
  var EDITABLE_MAILING_STATUSES = ["draft", "scheduled"];
888
1378
  var TERMINAL_MAILING_STATUSES = ["sent", "cancelled"];
889
- 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");
890
- var MailingCounts = import_zod5.z.object({
891
- total: import_zod5.z.number().int().min(0),
892
- queued: import_zod5.z.number().int().min(0),
893
- sending: import_zod5.z.number().int().min(0),
894
- sent: import_zod5.z.number().int().min(0),
895
- failed: import_zod5.z.number().int().min(0),
896
- 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)
897
1387
  });
898
1388
  var MAILING_KINDS = ["broadcast", "automation", "notification"];
899
- var MailingKind = import_zod5.z.enum(MAILING_KINDS);
900
- var Mailing = import_zod5.z.object({
1389
+ var MailingKind = import_zod6.z.enum(MAILING_KINDS);
1390
+ var Mailing = import_zod6.z.object({
901
1391
  id: Id,
902
1392
  kind: MailingKind,
903
- name: import_zod5.z.string().max(200).nullable(),
904
- subject: import_zod5.z.string().min(1).max(998),
905
- preheader: import_zod5.z.string().max(500).nullable(),
906
- /** The template the document was taken from, if any. The snapshot is what is sent. */
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(),
1396
+ /**
1397
+ * The template the document was taken from, if any. The snapshot is what is
1398
+ * sent in the main language; the template's other language versions are
1399
+ * taken, the ready ones, when the mailing starts (see "Languages" in the
1400
+ * contract's documentation).
1401
+ */
907
1402
  template_id: Id.nullable(),
908
1403
  document: TemplateDocument,
1404
+ /**
1405
+ * The main language: what `subject`, `preheader` and `document` are written
1406
+ * in, and what every recipient without a ready version in their own language
1407
+ * gets. The template's main language, or the workspace's default for a
1408
+ * mailing made from a document.
1409
+ */
1410
+ locale: LocaleTag,
1411
+ /**
1412
+ * The language every recipient is treated as reading, or null to read each
1413
+ * recipient's own (their `merge.locale`, else their contact's language). A
1414
+ * letter sets it to the language its one reader wrote in; a broadcast can use
1415
+ * it to send one language version to everyone. It is matched like anyone's
1416
+ * language: without a ready version in it, the main language goes out.
1417
+ */
1418
+ send_locale: LocaleTag.nullable(),
909
1419
  /**
910
1420
  * The topic the mailing is sent under. Null for a letter (a one-to-one
911
1421
  * mailing with at most one recipient, which belongs to no topic and whose
@@ -927,15 +1437,17 @@ var Mailing = import_zod5.z.object({
927
1437
  * recipients refused as dead addresses in one run (see the provider's
928
1438
  * `rejections.anomaly`). Resuming clears it and starts a new run.
929
1439
  */
930
- pause_reason: import_zod5.z.string().nullable().default(null),
1440
+ pause_reason: import_zod6.z.string().nullable().default(null),
931
1441
  created_at: Timestamp,
932
1442
  updated_at: Timestamp
933
1443
  });
934
1444
  var MailingSummary = Mailing.omit({ document: true });
935
1445
  var MailingContent = {
936
- name: import_zod5.z.string().max(200).optional(),
937
- subject: import_zod5.z.string().min(1).max(998),
938
- 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(),
1449
+ /** See `Mailing.send_locale`. */
1450
+ send_locale: LocaleTag.nullable().optional(),
939
1451
  /**
940
1452
  * Omitted or null makes the mailing a letter: one recipient at most, sent to
941
1453
  * anybody not blocked on every topic, with an unsubscribe link that offers
@@ -946,20 +1458,25 @@ var MailingContent = {
946
1458
  provider_id: Id,
947
1459
  metadata: MailingMetadata.optional()
948
1460
  };
949
- var MailingCreate = import_zod5.z.object({
1461
+ var MailingCreate = import_zod6.z.object({
950
1462
  ...MailingContent,
1463
+ subject: MailingContent.subject.optional(),
951
1464
  document: TemplateDocument.optional(),
952
1465
  template_id: Id.optional()
953
1466
  }).refine((v) => v.document === void 0 !== (v.template_id === void 0), {
954
1467
  message: "exactly one of document or template_id",
955
1468
  path: ["document"]
1469
+ }).refine((v) => v.template_id !== void 0 || v.subject !== void 0, {
1470
+ message: "a mailing made from a document needs a subject",
1471
+ path: ["subject"]
956
1472
  });
957
- var MailingUpdate = import_zod5.z.object({
958
- 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(),
959
1475
  subject: MailingContent.subject.optional(),
960
- preheader: import_zod5.z.string().max(500).nullable().optional(),
1476
+ preheader: import_zod6.z.string().max(500).nullable().optional(),
961
1477
  /** Null turns a draft into a letter, which it can only be with at most one recipient. */
962
1478
  topic: Slug.nullable().optional(),
1479
+ send_locale: LocaleTag.nullable().optional(),
963
1480
  provider_id: Id.optional(),
964
1481
  metadata: MailingMetadata.optional(),
965
1482
  document: TemplateDocument.optional()
@@ -970,29 +1487,30 @@ var MailingListQuery = PageQuery.extend({
970
1487
  kind: MailingKind.optional(),
971
1488
  automation_id: Id.optional()
972
1489
  });
973
- var MailingSendRequest = import_zod5.z.object({}).strict();
974
- var MailingRetryFailedRequest = import_zod5.z.object({
1490
+ var MailingSendRequest = import_zod6.z.object({}).strict();
1491
+ var MailingRetryFailedRequest = import_zod6.z.object({
975
1492
  /**
976
1493
  * Also requeue recipients whose outcome is unknown after a crash. They may
977
1494
  * already have received the message, so this is a human's decision, never a
978
1495
  * default.
979
1496
  */
980
- include_outcome_unknown: import_zod5.z.boolean().optional()
1497
+ include_outcome_unknown: import_zod6.z.boolean().optional()
981
1498
  }).strict();
982
- var MailingActionRequest = import_zod5.z.object({}).strict();
983
- var MailingTestRequest = import_zod5.z.object({
1499
+ var MailingActionRequest = import_zod6.z.object({}).strict();
1500
+ var MailingTestRequest = import_zod6.z.object({
984
1501
  to: Email,
985
- merge: Properties.optional()
1502
+ merge: Properties.optional(),
1503
+ locale: LocaleTag.optional()
986
1504
  });
987
- var MailingTestResult = import_zod5.z.object({
1505
+ var MailingTestResult = import_zod6.z.object({
988
1506
  message_id: Id,
989
- provider_message_id: import_zod5.z.string().nullable()
1507
+ provider_message_id: import_zod6.z.string().nullable()
990
1508
  });
991
1509
  var RECIPIENT_STATUSES = ["queued", "sending", "sent", "failed", "skipped"];
992
- var RecipientStatus = import_zod5.z.enum(RECIPIENT_STATUSES);
1510
+ var RecipientStatus = import_zod6.z.enum(RECIPIENT_STATUSES);
993
1511
  var SKIP_REASONS = ["suppressed", "not_subscribed", "contact_erased", "cancelled", "outcome_unknown"];
994
- var SkipReason = import_zod5.z.enum(SKIP_REASONS);
995
- var Recipient = import_zod5.z.object({
1512
+ var SkipReason = import_zod6.z.enum(SKIP_REASONS);
1513
+ var Recipient = import_zod6.z.object({
996
1514
  id: Id,
997
1515
  mailing_id: Id,
998
1516
  contact_id: Id.nullable(),
@@ -1000,84 +1518,125 @@ var Recipient = import_zod5.z.object({
1000
1518
  merge: Properties,
1001
1519
  status: RecipientStatus,
1002
1520
  skip_reason: SkipReason.nullable(),
1003
- attempts: import_zod5.z.number().int().min(0),
1521
+ attempts: import_zod6.z.number().int().min(0),
1004
1522
  message_id: Id.nullable(),
1005
- last_error: import_zod5.z.string().nullable(),
1523
+ last_error: import_zod6.z.string().nullable(),
1006
1524
  created_at: Timestamp,
1007
1525
  updated_at: Timestamp
1008
1526
  });
1009
1527
  var MAX_RECIPIENTS_PER_BATCH = 1e3;
1010
1528
  var MAX_LETTER_RECIPIENTS = 1;
1011
1529
  var LETTER_RECIPIENT_REFUSAL = "letter_has_one_recipient";
1012
- var RecipientInput = import_zod5.z.object({
1530
+ var RecipientInput = import_zod6.z.object({
1013
1531
  contact_id: Id.optional(),
1014
- external_id: import_zod5.z.string().min(1).max(255).optional(),
1532
+ external_id: import_zod6.z.string().min(1).max(255).optional(),
1015
1533
  email: Email.optional(),
1016
1534
  merge: Properties.optional()
1017
1535
  }).refine((v) => v.contact_id !== void 0 || v.external_id !== void 0 || v.email !== void 0, {
1018
1536
  message: "contact_id, external_id or email is required",
1019
1537
  path: ["email"]
1020
1538
  });
1021
- var RecipientBatch = import_zod5.z.object({
1022
- 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)
1023
1541
  });
1024
1542
  var RECIPIENT_REJECTIONS = ["unknown_contact", "duplicate_in_batch"];
1025
- var RecipientBatchResult = import_zod5.z.object({
1543
+ var RecipientBatchResult = import_zod6.z.object({
1026
1544
  /** Newly queued. */
1027
- added: import_zod5.z.number().int().min(0),
1545
+ added: import_zod6.z.number().int().min(0),
1028
1546
  /** Already on the mailing from an earlier batch: left unchanged. */
1029
- already_present: import_zod5.z.number().int().min(0),
1547
+ already_present: import_zod6.z.number().int().min(0),
1030
1548
  /** Items not added, by their index in the request. */
1031
- 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) }))
1032
1550
  });
1033
1551
  var RecipientListQuery = PageQuery.extend({
1034
1552
  status: RecipientStatus.optional(),
1035
1553
  skip_reason: SkipReason.optional()
1036
1554
  });
1037
1555
  var MESSAGE_OUTCOMES = ["sent", "failed"];
1038
- var MessageOutcome = import_zod5.z.enum(MESSAGE_OUTCOMES);
1039
- var MessageSummary = import_zod5.z.object({
1556
+ var MessageOutcome = import_zod6.z.enum(MESSAGE_OUTCOMES);
1557
+ var MessageSummary = import_zod6.z.object({
1040
1558
  id: Id,
1041
1559
  mailing_id: Id.nullable(),
1042
1560
  recipient_id: Id.nullable(),
1043
1561
  contact_id: Id.nullable(),
1044
1562
  to: Email,
1045
- subject: import_zod5.z.string(),
1563
+ subject: import_zod6.z.string(),
1046
1564
  provider_id: Id,
1047
- provider_message_id: import_zod5.z.string().nullable(),
1565
+ provider_message_id: import_zod6.z.string().nullable(),
1048
1566
  outcome: MessageOutcome,
1049
- error: import_zod5.z.string().nullable(),
1050
- is_test: import_zod5.z.boolean(),
1051
- 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),
1570
+ /** The language version the message was sent in; null for messages archived before languages existed. */
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(),
1052
1579
  created_at: Timestamp
1053
1580
  });
1054
- var Message = MessageSummary.extend({ html: import_zod5.z.string() });
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({
1584
+ locale: LocaleTag,
1585
+ status: MailingLanguageStatus,
1586
+ /** Queued recipients who will get this version. */
1587
+ planned: import_zod6.z.number().int().min(0),
1588
+ /** Messages already sent in this language (tests excluded). */
1589
+ sent: import_zod6.z.number().int().min(0)
1590
+ });
1591
+ var MailingLanguageReport = import_zod6.z.object({
1592
+ main_locale: LocaleTag,
1593
+ send_locale: LocaleTag.nullable(),
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
+ )
1613
+ });
1055
1614
  var MessageListQuery = PageQuery.extend({
1056
1615
  mailing_id: Id.optional(),
1057
1616
  outcome: MessageOutcome.optional()
1058
1617
  });
1059
1618
 
1060
1619
  // src/platform.ts
1061
- var Tag = import_zod6.z.object({
1620
+ var Tag = import_zod7.z.object({
1062
1621
  id: Id,
1063
1622
  slug: Slug,
1064
- name: import_zod6.z.string().min(1).max(120),
1065
- 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),
1066
1625
  created_at: Timestamp
1067
1626
  });
1068
- var TagCreate = import_zod6.z.object({ slug: Slug, name: import_zod6.z.string().min(1).max(120) });
1069
- var TagAssignment = import_zod6.z.object({
1070
- 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)
1071
1630
  });
1072
1631
  var CONTACT_PROPERTY_TYPES = ["string", "number", "boolean", "date"];
1073
- var ContactPropertyType = import_zod6.z.enum(CONTACT_PROPERTY_TYPES);
1074
- var ContactPropertyKey = import_zod6.z.string().regex(/^[A-Za-z0-9_.-]{1,64}$/, "letters, digits, dot, underscore and hyphen, 1 to 64");
1075
- 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({
1076
1635
  key: ContactPropertyKey,
1077
- label: import_zod6.z.string().min(1).max(120),
1636
+ label: import_zod7.z.string().min(1).max(120),
1078
1637
  type: ContactPropertyType
1079
1638
  });
1080
- var ContactPropertyParams = import_zod6.z.object({ key: ContactPropertyKey });
1639
+ var ContactPropertyParams = import_zod7.z.object({ key: ContactPropertyKey });
1081
1640
  var FILTER_OPERATORS = [
1082
1641
  "eq",
1083
1642
  "neq",
@@ -1093,8 +1652,8 @@ var FILTER_OPERATORS = [
1093
1652
  "in",
1094
1653
  "not_in"
1095
1654
  ];
1096
- var FilterOperator = import_zod6.z.enum(FILTER_OPERATORS);
1097
- var FilterField = import_zod6.z.string().regex(
1655
+ var FilterOperator = import_zod7.z.enum(FILTER_OPERATORS);
1656
+ var FilterField = import_zod7.z.string().regex(
1098
1657
  /^(email|first_name|last_name|locale|created_at|tag|topic|engagement:(opened|clicked)|property:[A-Za-z0-9_.-]{1,64})$/,
1099
1658
  "unknown filter field"
1100
1659
  );
@@ -1110,18 +1669,18 @@ var FILTER_FIELD_OPERATORS = {
1110
1669
  property: FILTER_OPERATORS
1111
1670
  };
1112
1671
  var MAX_FILTER_DEPTH = 5;
1113
- 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), {
1114
1673
  message: "exists and not_exists take no value; every other operator needs one",
1115
1674
  path: ["value"]
1116
1675
  }).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
1117
1676
  message: "in and not_in take an array",
1118
1677
  path: ["value"]
1119
1678
  });
1120
- var SegmentFilter = import_zod6.z.lazy(
1121
- () => import_zod6.z.union([
1122
- import_zod6.z.object({ and: import_zod6.z.array(SegmentFilter).min(1).max(50) }).strict(),
1123
- import_zod6.z.object({ or: import_zod6.z.array(SegmentFilter).min(1).max(50) }).strict(),
1124
- 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(),
1125
1684
  FilterCondition
1126
1685
  ])
1127
1686
  );
@@ -1209,42 +1768,42 @@ function untypedValueProblem(c, every) {
1209
1768
  if (orderOp) return every(isFilterNumber, "numbers (define the property to compare dates)");
1210
1769
  return every((v) => isScalar(v) || v === null, "strings, numbers, booleans or null");
1211
1770
  }
1212
- var Segment = import_zod6.z.object({
1771
+ var Segment = import_zod7.z.object({
1213
1772
  id: Id,
1214
- name: import_zod6.z.string().min(1).max(120),
1773
+ name: import_zod7.z.string().min(1).max(120),
1215
1774
  filter: SegmentFilter,
1216
1775
  /** Computed on read. */
1217
- contact_count: import_zod6.z.number().int().min(0),
1776
+ contact_count: import_zod7.z.number().int().min(0),
1218
1777
  created_at: Timestamp,
1219
1778
  updated_at: Timestamp
1220
1779
  });
1221
- var SegmentCreate = import_zod6.z.object({ name: import_zod6.z.string().min(1).max(120), filter: BoundedSegmentFilter });
1222
- var SegmentPreviewRequest = import_zod6.z.object({ filter: BoundedSegmentFilter });
1223
- var SegmentPreview = import_zod6.z.object({
1224
- 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),
1225
1784
  /** Up to five matching contacts, for a human to sanity-check the filter. */
1226
- 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)
1227
1786
  });
1228
- var MailingAudienceFromSegment = import_zod6.z.object({ segment_id: Id });
1787
+ var MailingAudienceFromSegment = import_zod7.z.object({ segment_id: Id });
1229
1788
  var SIGNUP_FORM_FIELDS = ["first_name", "last_name"];
1230
1789
  var HOSTED_PAGE_LOCALES = ["en", "de", "it", "fr", "es"];
1231
- var SignupFormTranslation = import_zod6.z.object({
1232
- title: import_zod6.z.string().min(1).max(120),
1233
- 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)
1234
1793
  });
1235
- var SignupForm = import_zod6.z.object({
1794
+ var SignupForm = import_zod7.z.object({
1236
1795
  id: Id,
1237
- name: import_zod6.z.string().min(1).max(120),
1796
+ name: import_zod7.z.string().min(1).max(120),
1238
1797
  /** The heading of the hosted page. */
1239
- title: import_zod6.z.string().min(1).max(120),
1798
+ title: import_zod7.z.string().min(1).max(120),
1240
1799
  /** What the person agrees to, shown above the button and recorded with the confirmation. */
1241
- consent_text: import_zod6.z.string().min(1).max(2e3),
1242
- translations: import_zod6.z.record(SignupFormTranslation),
1243
- topics: import_zod6.z.array(Slug).min(1),
1244
- 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),
1245
1804
  /** Fields shown besides email. */
1246
- fields: import_zod6.z.array(import_zod6.z.enum(SIGNUP_FORM_FIELDS)),
1247
- 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),
1248
1807
  /** The provider the confirmation mail is sent through. */
1249
1808
  provider_id: Id,
1250
1809
  /**
@@ -1253,11 +1812,11 @@ var SignupForm = import_zod6.z.object({
1253
1812
  */
1254
1813
  confirmation_template_id: Id.nullable(),
1255
1814
  /** Where the person lands after confirming. Null shows the hosted confirmation page. */
1256
- redirect_url: import_zod6.z.string().url().nullable(),
1815
+ redirect_url: import_zod7.z.string().url().nullable(),
1257
1816
  /** Origins allowed to embed the form and call the submission route from a browser. */
1258
- 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),
1259
1818
  /** Bumped on every change; recorded with each confirmation. */
1260
- version: import_zod6.z.number().int().min(1),
1819
+ version: import_zod7.z.number().int().min(1),
1261
1820
  created_at: Timestamp,
1262
1821
  updated_at: Timestamp
1263
1822
  });
@@ -1272,56 +1831,56 @@ var SignupFormCreate = SignupForm.omit({
1272
1831
  tags: SignupForm.shape.tags.optional(),
1273
1832
  fields: SignupForm.shape.fields.optional(),
1274
1833
  confirmation_template_id: Id.nullable().optional(),
1275
- redirect_url: import_zod6.z.string().url().nullable().optional(),
1834
+ redirect_url: import_zod7.z.string().url().nullable().optional(),
1276
1835
  allowed_origins: SignupForm.shape.allowed_origins.optional()
1277
1836
  });
1278
- var SignupFormEmbed = import_zod6.z.object({
1279
- hosted_url: import_zod6.z.string().url(),
1837
+ var SignupFormEmbed = import_zod7.z.object({
1838
+ hosted_url: import_zod7.z.string().url(),
1280
1839
  /** A plain HTML form posting to the hosted page; works without JavaScript. */
1281
- html: import_zod6.z.string(),
1840
+ html: import_zod7.z.string(),
1282
1841
  /** A small optional script that adds the time check and submits in place. */
1283
- script_url: import_zod6.z.string().url(),
1842
+ script_url: import_zod7.z.string().url(),
1284
1843
  /** Where a browser fetches a fresh `form_token` from (GET, public, CORS for `allowed_origins`). */
1285
- token_url: import_zod6.z.string().url()
1844
+ token_url: import_zod7.z.string().url()
1286
1845
  });
1287
- var SignupSubmission = import_zod6.z.object({
1846
+ var SignupSubmission = import_zod7.z.object({
1288
1847
  email: Email,
1289
- first_name: import_zod6.z.string().max(200).optional(),
1290
- last_name: import_zod6.z.string().max(200).optional(),
1291
- 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(),
1292
1851
  /** Honeypot: must be empty. */
1293
- website: import_zod6.z.string().max(0).optional(),
1852
+ website: import_zod7.z.string().max(0).optional(),
1294
1853
  /**
1295
1854
  * The signed render time from the form's token URL: a submission sooner than a
1296
1855
  * human could fill the form, or long after, is refused.
1297
1856
  */
1298
- form_token: import_zod6.z.string().min(1).max(512)
1857
+ form_token: import_zod7.z.string().min(1).max(512)
1299
1858
  });
1300
1859
  var IMPORT_STATUSES = ["uploaded", "validating", "validated", "committing", "completed", "failed", "cancelled"];
1301
- var ImportStatus = import_zod6.z.enum(IMPORT_STATUSES);
1860
+ var ImportStatus = import_zod7.z.enum(IMPORT_STATUSES);
1302
1861
  var IMPORT_TERMINAL_STATUSES = ["completed", "failed", "cancelled"];
1303
- var ImportColumnMapping = import_zod6.z.record(
1304
- 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)$/)
1305
1864
  );
1306
1865
  var IMPORT_FILE_FIELD = "file";
1307
1866
  var MAX_IMPORT_BYTES = 50 * 1024 * 1024;
1308
1867
  var MAX_IMPORT_ROWS = 2e5;
1309
- var ImportMappingRequest = import_zod6.z.object({
1868
+ var ImportMappingRequest = import_zod7.z.object({
1310
1869
  /** CSV header -> contact field. Exactly one column maps to `email`. */
1311
1870
  mapping: ImportColumnMapping.refine(
1312
1871
  (m) => Object.values(m).filter((v) => v === "email").length === 1,
1313
1872
  "exactly one column maps to email"
1314
1873
  ),
1315
- topics: import_zod6.z.array(Slug).max(100),
1316
- 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),
1317
1876
  /** Existing contacts get the mapped fields overwritten; false only adds topics, tags and missing fields. */
1318
- update_existing: import_zod6.z.boolean().optional(),
1877
+ update_existing: import_zod7.z.boolean().optional(),
1319
1878
  /** The importer states the people consented; the service records who said so and when. */
1320
- consent_confirmed: import_zod6.z.literal(true)
1879
+ consent_confirmed: import_zod7.z.literal(true)
1321
1880
  });
1322
- 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) });
1323
1882
  var IMPORT_ROW_OUTCOMES = ["created", "updated", "unchanged", "suppressed", "skipped"];
1324
- var ImportRowOutcome = import_zod6.z.enum(IMPORT_ROW_OUTCOMES);
1883
+ var ImportRowOutcome = import_zod7.z.enum(IMPORT_ROW_OUTCOMES);
1325
1884
  var IMPORT_SKIP_REASONS = [
1326
1885
  "missing_email",
1327
1886
  "invalid_email",
@@ -1330,90 +1889,90 @@ var IMPORT_SKIP_REASONS = [
1330
1889
  "external_id_conflict",
1331
1890
  "wrong_column_count"
1332
1891
  ];
1333
- var ImportSkipReason = import_zod6.z.enum(IMPORT_SKIP_REASONS);
1334
- var ImportReport = import_zod6.z.object({
1335
- created: import_zod6.z.number().int().min(0),
1336
- updated: import_zod6.z.number().int().min(0),
1337
- unchanged: import_zod6.z.number().int().min(0),
1338
- suppressed: import_zod6.z.number().int().min(0),
1339
- skipped: import_zod6.z.number().int().min(0),
1340
- 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)),
1341
1900
  /** Rows whose contact was subscribed without some topic, because the address is blocked for it. */
1342
- topics_withheld: import_zod6.z.number().int().min(0)
1901
+ topics_withheld: import_zod7.z.number().int().min(0)
1343
1902
  });
1344
- var ImportJob = import_zod6.z.object({
1903
+ var ImportJob = import_zod7.z.object({
1345
1904
  id: Id,
1346
1905
  status: ImportStatus,
1347
- file_name: import_zod6.z.string().max(255).nullable(),
1348
- file_bytes: import_zod6.z.number().int().min(0),
1349
- 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),
1350
1909
  /** The header row, as in the file. */
1351
- columns: import_zod6.z.array(import_zod6.z.string()),
1910
+ columns: import_zod7.z.array(import_zod7.z.string()),
1352
1911
  /** The first rows, for the mapping screen. */
1353
- 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),
1354
1913
  /** A mapping guessed from the headers, for the mapping screen to start from. */
1355
1914
  suggested_mapping: ImportColumnMapping,
1356
1915
  mapping: ImportColumnMapping.nullable(),
1357
- mapping_version: import_zod6.z.number().int().min(0),
1358
- topics: import_zod6.z.array(Slug),
1359
- tags: import_zod6.z.array(Slug),
1360
- 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(),
1361
1920
  /** The dry run of the current mapping; null before one finished. */
1362
1921
  dry_run: ImportReport.nullable(),
1363
1922
  /** What the commit has written so far; null before it started. */
1364
1923
  result: ImportReport.nullable(),
1365
1924
  /** Rows the current phase (dry run or commit) has processed. */
1366
- processed_rows: import_zod6.z.number().int().min(0),
1925
+ processed_rows: import_zod7.z.number().int().min(0),
1367
1926
  /** Why a `failed` import failed. */
1368
- error: import_zod6.z.string().nullable(),
1927
+ error: import_zod7.z.string().nullable(),
1369
1928
  created_at: Timestamp,
1370
1929
  updated_at: Timestamp,
1371
1930
  finished_at: Timestamp.nullable()
1372
1931
  });
1373
- var ImportRow = import_zod6.z.object({
1932
+ var ImportRow = import_zod7.z.object({
1374
1933
  /** The line in the file, 1 being the first line after the header. */
1375
- row: import_zod6.z.number().int().min(1),
1376
- email: import_zod6.z.string().nullable(),
1934
+ row: import_zod7.z.number().int().min(1),
1935
+ email: import_zod7.z.string().nullable(),
1377
1936
  outcome: ImportRowOutcome,
1378
1937
  reason: ImportSkipReason.nullable(),
1379
1938
  /** A human explanation for a skipped row (which value, which column). */
1380
- message: import_zod6.z.string().nullable(),
1939
+ message: import_zod7.z.string().nullable(),
1381
1940
  contact_id: Id.nullable()
1382
1941
  });
1383
1942
  var ImportRowListQuery = PageQuery.extend({ outcome: ImportRowOutcome.optional() });
1384
- var MailingScheduleRequest = import_zod6.z.object({ send_at: Timestamp });
1385
- var TrackingSettings = import_zod6.z.object({ opens: import_zod6.z.boolean(), clicks: import_zod6.z.boolean() });
1386
- var VariantAnalytics = import_zod6.z.object({
1387
- key: import_zod6.z.string(),
1388
- sent: import_zod6.z.number().int().min(0),
1389
- unique_opens: import_zod6.z.number().int().min(0).nullable(),
1390
- unique_clicks: import_zod6.z.number().int().min(0).nullable()
1391
- });
1392
- 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({
1393
1952
  mailing_id: Id,
1394
1953
  counts: MailingCounts,
1395
1954
  /** What was tracked for this mailing, fixed when it started; null before it started. */
1396
1955
  tracking: TrackingSettings.nullable(),
1397
1956
  /** Unique recipients with a human open. Null when opens were not tracked. */
1398
- unique_opens: import_zod6.z.number().int().min(0).nullable(),
1957
+ unique_opens: import_zod7.z.number().int().min(0).nullable(),
1399
1958
  /**
1400
1959
  * Recipients whose only opens came from Apple Mail Privacy Protection, which
1401
1960
  * loads every image on delivery: counted apart, never as human opens.
1402
1961
  */
1403
- apple_mpp_opens: import_zod6.z.number().int().min(0).nullable(),
1962
+ apple_mpp_opens: import_zod7.z.number().int().min(0).nullable(),
1404
1963
  /** Opens and clicks from security scanners and prefetchers, filtered out of the unique counts. */
1405
- machine_events: import_zod6.z.number().int().min(0).nullable(),
1406
- unique_clicks: import_zod6.z.number().int().min(0).nullable(),
1407
- unsubscribes: import_zod6.z.number().int().min(0),
1408
- bounces: import_zod6.z.number().int().min(0),
1409
- complaints: import_zod6.z.number().int().min(0),
1410
- links: import_zod6.z.array(import_zod6.z.object({ url: import_zod6.z.string(), unique_clicks: import_zod6.z.number().int().min(0) })).nullable(),
1411
- 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()
1412
1971
  });
1413
1972
 
1414
1973
  // src/automations.ts
1415
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})$/;
1416
- 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");
1417
1976
  var AUTOMATION_FILTER_FIELD_OPERATORS = {
1418
1977
  ...FILTER_FIELD_OPERATORS,
1419
1978
  segment: ["eq", "neq", "in", "not_in"],
@@ -1423,18 +1982,18 @@ var AUTOMATION_FILTER_FIELD_OPERATORS = {
1423
1982
  step: ["eq", "neq"]
1424
1983
  };
1425
1984
  var automationFilterFamily = filterFieldFamily;
1426
- 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), {
1427
1986
  message: "exists and not_exists take no value; every other operator needs one",
1428
1987
  path: ["value"]
1429
1988
  }).refine((c) => c.op !== "in" && c.op !== "not_in" || Array.isArray(c.value), {
1430
1989
  message: "in and not_in take an array",
1431
1990
  path: ["value"]
1432
1991
  });
1433
- var AutomationFilter = import_zod7.z.lazy(
1434
- () => import_zod7.z.union([
1435
- import_zod7.z.object({ and: import_zod7.z.array(AutomationFilter).min(1).max(50) }).strict(),
1436
- import_zod7.z.object({ or: import_zod7.z.array(AutomationFilter).min(1).max(50) }).strict(),
1437
- 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(),
1438
1997
  AutomationFilterCondition
1439
1998
  ])
1440
1999
  );
@@ -1447,20 +2006,20 @@ function filterConditions(filter) {
1447
2006
  if ("not" in filter) return filterConditions(filter.not);
1448
2007
  return [filter];
1449
2008
  }
1450
- var StepId = import_zod7.z.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "letters, digits, underscore and hyphen, 1 to 64");
1451
- var TimeOfDay = import_zod7.z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "HH:MM in 24 hours");
1452
- var Weekday = import_zod7.z.number().int().min(0).max(6);
1453
- 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");
1454
2013
  var MAX_DELAY_SECONDS = 365 * 24 * 60 * 60;
1455
- 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) => {
1456
2015
  const [y, m, d] = v.split("-").map(Number);
1457
2016
  const t = new Date(Date.UTC(y, m - 1, d));
1458
2017
  return t.getUTCFullYear() === y && t.getUTCMonth() + 1 === m && t.getUTCDate() === d;
1459
2018
  }, "a real calendar date");
1460
- var OffsetDays = import_zod7.z.number().int().min(-365).max(365);
2019
+ var OffsetDays = import_zod8.z.number().int().min(-365).max(365);
1461
2020
  var DATE_PASSED_ANSWERS = ["skip", "stop", "continue"];
1462
- var DatePassedAnswer = import_zod7.z.enum(DATE_PASSED_ANSWERS);
1463
- 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");
1464
2023
  var AUTOMATION_TRIGGER_KINDS = [
1465
2024
  "topic_subscribed",
1466
2025
  "form_confirmed",
@@ -1473,17 +2032,17 @@ var AUTOMATION_TRIGGER_KINDS = [
1473
2032
  "segment_entered",
1474
2033
  "link_clicked"
1475
2034
  ];
1476
- var AutomationTriggerKind = import_zod7.z.enum(AUTOMATION_TRIGGER_KINDS);
2035
+ var AutomationTriggerKind = import_zod8.z.enum(AUTOMATION_TRIGGER_KINDS);
1477
2036
  var exclude = { exclude: BoundedAutomationFilter.nullish() };
1478
- var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
1479
- import_zod7.z.object({ kind: import_zod7.z.literal("topic_subscribed"), topic: Slug, ...exclude }).strict(),
1480
- import_zod7.z.object({ kind: import_zod7.z.literal("form_confirmed"), form_id: Id, ...exclude }).strict(),
1481
- 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(),
1482
2041
  /** `to` given: only a change that lands on this value fires. Absent: any change of the key. */
1483
- 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(),
1484
2043
  /** `where` filters the event's own data, so only `event:<key>` leaves belong in it. */
1485
- import_zod7.z.object({ kind: import_zod7.z.literal("event"), name: EventName, where: BoundedAutomationFilter.nullish(), ...exclude }).strict(),
1486
- 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(),
1487
2046
  /**
1488
2047
  * Every year on a date the contact carries: a birthday, a joining date, a
1489
2048
  * renewal. `offset_days` moves it ("three days before"), `at` is the wall
@@ -1493,8 +2052,8 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
1493
2052
  * A 29 February date fires on 1 March in a year that has no 29 February, so
1494
2053
  * nobody is skipped every fourth year.
1495
2054
  */
1496
- import_zod7.z.object({
1497
- kind: import_zod7.z.literal("date_anniversary"),
2055
+ import_zod8.z.object({
2056
+ kind: import_zod8.z.literal("date_anniversary"),
1498
2057
  key: ContactPropertyKey,
1499
2058
  offset_days: OffsetDays,
1500
2059
  at: TimeOfDay,
@@ -1506,8 +2065,8 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
1506
2065
  * not ask about a segment or about an event's data: there is no event, and a
1507
2066
  * segment is its own trigger kind.
1508
2067
  */
1509
- import_zod7.z.object({
1510
- kind: import_zod7.z.literal("exact_date"),
2068
+ import_zod8.z.object({
2069
+ kind: import_zod8.z.literal("exact_date"),
1511
2070
  on: DateOnly,
1512
2071
  at: TimeOfDay,
1513
2072
  where: BoundedAutomationFilter.nullish(),
@@ -1526,7 +2085,7 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
1526
2085
  * already exists. Entering those people as well is the publish dialog's
1527
2086
  * backfill question, answered by a person rather than by the sweep.
1528
2087
  */
1529
- 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(),
1530
2089
  /**
1531
2090
  * S4. When somebody clicks a link in a mailing that has already gone out.
1532
2091
  *
@@ -1536,7 +2095,7 @@ var AutomationTrigger = import_zod7.z.discriminatedUnion("kind", [
1536
2095
  * tracking off there is no click to hear about, so the validator refuses it
1537
2096
  * rather than leaving an automation that can never fire.
1538
2097
  */
1539
- 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()
1540
2099
  ]);
1541
2100
  var AUTOMATION_DATE_TRIGGER_KINDS = ["date_anniversary", "exact_date"];
1542
2101
  var MAX_TRIGGERS = 10;
@@ -1555,34 +2114,45 @@ var AUTOMATION_STEP_KINDS = [
1555
2114
  "webhook",
1556
2115
  "notify"
1557
2116
  ];
1558
- var AutomationStepKind = import_zod7.z.enum(AUTOMATION_STEP_KINDS);
2117
+ var AutomationStepKind = import_zod8.z.enum(AUTOMATION_STEP_KINDS);
1559
2118
  var next = { next: StepId.nullable() };
1560
- var Unchosen = import_zod7.z.literal("");
1561
- var DraftSlug = import_zod7.z.union([Slug, Unchosen]);
1562
- var DraftPropertyKey = import_zod7.z.union([ContactPropertyKey, Unchosen]);
1563
- var label = { label: import_zod7.z.string().max(120).nullish() };
1564
- var EmailStep = import_zod7.z.object({
1565
- kind: import_zod7.z.literal("email"),
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()
2129
+ }).strict();
2130
+ var EmailStep = import_zod8.z.object({
2131
+ kind: import_zod8.z.literal("email"),
1566
2132
  ...label,
1567
2133
  ...next,
1568
2134
  template_id: Id.nullish(),
1569
2135
  document: TemplateDocument.nullish(),
1570
- /** Empty while the step is being written; `not_chosen` refuses to publish it. */
1571
- subject: import_zod7.z.string().max(998),
1572
- preheader: import_zod7.z.string().max(500).nullish()
2136
+ /** Empty while the step is being written, or when its template has a subject; else `not_chosen` refuses to publish it. */
2137
+ subject: import_zod8.z.string().max(998),
2138
+ preheader: import_zod8.z.string().max(500).nullish(),
2139
+ locale: LocaleTag.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()
1573
2143
  }).strict();
1574
- var DelayStep = import_zod7.z.object({
1575
- kind: import_zod7.z.literal("delay"),
2144
+ var DelayStep = import_zod8.z.object({
2145
+ kind: import_zod8.z.literal("delay"),
1576
2146
  ...label,
1577
2147
  ...next,
1578
- wait: import_zod7.z.discriminatedUnion("mode", [
1579
- import_zod7.z.object({ mode: import_zod7.z.literal("duration"), seconds: import_zod7.z.number().int().min(1).max(MAX_DELAY_SECONDS) }).strict(),
1580
- import_zod7.z.object({ mode: import_zod7.z.literal("time_of_day"), at: TimeOfDay }).strict(),
1581
- 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(),
1582
2152
  /** A fixed day has no next occurrence, so `continue` is not one of its answers. */
1583
- import_zod7.z.object({ mode: import_zod7.z.literal("date"), on: DateOnly, at: TimeOfDay, passed: import_zod7.z.enum(["skip", "stop"]) }).strict(),
1584
- import_zod7.z.object({
1585
- 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"),
1586
2156
  key: ContactPropertyKey,
1587
2157
  offset_days: OffsetDays,
1588
2158
  at: TimeOfDay,
@@ -1590,8 +2160,8 @@ var DelayStep = import_zod7.z.object({
1590
2160
  }).strict()
1591
2161
  ])
1592
2162
  }).strict();
1593
- var ConditionStep = import_zod7.z.object({
1594
- kind: import_zod7.z.literal("condition"),
2163
+ var ConditionStep = import_zod8.z.object({
2164
+ kind: import_zod8.z.literal("condition"),
1595
2165
  ...label,
1596
2166
  filter: BoundedAutomationFilter,
1597
2167
  yes: StepId.nullable(),
@@ -1602,49 +2172,49 @@ var ConditionStep = import_zod7.z.object({
1602
2172
  * neither half means anything without the other: a window with no answer
1603
2173
  * for the end of it would be a silent decision.
1604
2174
  */
1605
- 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()
1606
2176
  }).strict();
1607
- 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");
1608
2178
  var MIN_SPLIT_BRANCHES = 2;
1609
2179
  var MAX_SPLIT_BRANCHES = 4;
1610
- var SplitStep = import_zod7.z.object({
1611
- kind: import_zod7.z.literal("split"),
2180
+ var SplitStep = import_zod8.z.object({
2181
+ kind: import_zod8.z.literal("split"),
1612
2182
  ...label,
1613
- branches: import_zod7.z.array(
1614
- import_zod7.z.object({
2183
+ branches: import_zod8.z.array(
2184
+ import_zod8.z.object({
1615
2185
  key: SplitBranchKey,
1616
2186
  /** A whole percentage. Zero is allowed: a branch kept in the flow but switched off. */
1617
- weight: import_zod7.z.number().int().min(0).max(100),
2187
+ weight: import_zod8.z.number().int().min(0).max(100),
1618
2188
  next: StepId.nullable()
1619
2189
  }).strict()
1620
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")
1621
2191
  }).strict();
1622
- 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();
1623
2193
  var MAX_STEP_VISITS = 20;
1624
- var WebhookStep = import_zod7.z.object({ kind: import_zod7.z.literal("webhook"), ...label, ...next, endpoint_id: Id.nullable(), payload: JsonValue.nullish() }).strict();
1625
- var NotifyRecipients = import_zod7.z.union([import_zod7.z.literal("owner"), import_zod7.z.literal("admins"), import_zod7.z.object({ member_id: Id }).strict()]);
1626
- var NotifyStep = import_zod7.z.object({
1627
- 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"),
1628
2198
  ...label,
1629
2199
  ...next,
1630
2200
  to: NotifyRecipients,
1631
2201
  /** Empty while the step is being written; `not_chosen` refuses to publish it. */
1632
- subject: import_zod7.z.string().max(998),
1633
- body: import_zod7.z.string().max(1e4)
2202
+ subject: import_zod8.z.string().max(998),
2203
+ body: import_zod8.z.string().max(1e4)
1634
2204
  }).strict();
1635
- var AutomationStep = import_zod7.z.discriminatedUnion("kind", [
2205
+ var AutomationStep = import_zod8.z.discriminatedUnion("kind", [
1636
2206
  EmailStep,
1637
2207
  DelayStep,
1638
2208
  ConditionStep,
1639
- import_zod7.z.object({ kind: import_zod7.z.literal("add_tag"), ...label, ...next, tag: DraftSlug }).strict(),
1640
- 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(),
1641
2211
  /** `value` null clears the property, as a contact upsert does. */
1642
- 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(),
1643
2213
  /** Subscribing never lifts a suppression: the service checks that, not the flow. */
1644
- 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(),
1645
2215
  /** `topic` unsubscribes from the automation's own topic, `all` from every topic. */
1646
- import_zod7.z.object({ kind: import_zod7.z.literal("unsubscribe"), ...label, ...next, scope: import_zod7.z.enum(["topic", "all"]) }).strict(),
1647
- 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(),
1648
2218
  SplitStep,
1649
2219
  GotoStep,
1650
2220
  WebhookStep,
@@ -1668,25 +2238,25 @@ function stepTargets(step) {
1668
2238
  }
1669
2239
  }
1670
2240
  var MAX_STEPS = 200;
1671
- var AutomationFlow = import_zod7.z.object({
1672
- 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),
1673
2243
  /** The first step. `null` is an empty flow. */
1674
2244
  entry: StepId.nullable(),
1675
- steps: import_zod7.z.record(StepId, AutomationStep)
2245
+ steps: import_zod8.z.record(StepId, AutomationStep)
1676
2246
  }).strict();
1677
2247
  var emptyFlow = () => ({ triggers: [], entry: null, steps: {} });
1678
- var QuietHours = import_zod7.z.object({
2248
+ var QuietHours = import_zod8.z.object({
1679
2249
  start: TimeOfDay,
1680
2250
  end: TimeOfDay,
1681
- weekdays: import_zod7.z.array(Weekday).min(1).max(7).nullish()
2251
+ weekdays: import_zod8.z.array(Weekday).min(1).max(7).nullish()
1682
2252
  }).strict();
1683
2253
  var RE_ENTRY_MODES = ["off", "after_exit"];
1684
- var ReEntryMode = import_zod7.z.enum(RE_ENTRY_MODES);
2254
+ var ReEntryMode = import_zod8.z.enum(RE_ENTRY_MODES);
1685
2255
  var MAX_RE_ENTRY_COOLDOWN_SECONDS = 365 * 24 * 60 * 60;
1686
- var AutomationSettings = import_zod7.z.object({
2256
+ var AutomationSettings = import_zod8.z.object({
1687
2257
  re_entry: ReEntryMode,
1688
2258
  /** Seconds a person must wait after leaving before entering again. Only with `after_exit`. */
1689
- 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(),
1690
2260
  quiet_hours: QuietHours.nullish(),
1691
2261
  /** Checked before every step; when it matches, the run ends. */
1692
2262
  exit_filter: BoundedAutomationFilter.nullish()
@@ -1744,18 +2314,18 @@ var AUTOMATION_ISSUE_CODES = [
1744
2314
  /** A one-off date that is already over everywhere on earth, so it would fire for nobody. */
1745
2315
  "date_in_the_past"
1746
2316
  ];
1747
- var AutomationIssueCode = import_zod7.z.enum(AUTOMATION_ISSUE_CODES);
1748
- var AutomationIssue = import_zod7.z.object({
2317
+ var AutomationIssueCode = import_zod8.z.enum(AUTOMATION_ISSUE_CODES);
2318
+ var AutomationIssue = import_zod8.z.object({
1749
2319
  code: AutomationIssueCode,
1750
2320
  /** The step the issue is about, when it is about one. */
1751
2321
  step_id: StepId.nullable(),
1752
2322
  /** Where inside the document, for the canvas to point at a field. */
1753
- path: import_zod7.z.array(import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()])),
1754
- 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()
1755
2325
  });
1756
- var AutomationValidation = import_zod7.z.object({
1757
- ok: import_zod7.z.boolean(),
1758
- 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)
1759
2329
  });
1760
2330
  function validateAutomation(flow, settings, ctx) {
1761
2331
  const issues = [];
@@ -1875,7 +2445,7 @@ function validateAutomation(flow, settings, ctx) {
1875
2445
  if (!step.template_id && !step.document) {
1876
2446
  add({ code: "empty_email", step_id: id, path: [...path, "template_id"], message: "This email has neither a template nor content." });
1877
2447
  }
1878
- if (step.subject.trim() === "") {
2448
+ if (step.subject.trim() === "" && !(step.template_id && known.templates_with_subject?.has(step.template_id))) {
1879
2449
  add({ code: "not_chosen", step_id: id, path: [...path, "subject"], message: "This email has no subject line." });
1880
2450
  }
1881
2451
  if (step.template_id) checkRef(known.templates, step.template_id, "template", [...path, "template_id"], add, id);
@@ -2146,7 +2716,7 @@ function cycles(flow) {
2146
2716
  return out.sort();
2147
2717
  }
2148
2718
  var AUTOMATION_STATUSES = ["draft", "active", "paused", "archived"];
2149
- var AutomationStatus = import_zod7.z.enum(AUTOMATION_STATUSES);
2719
+ var AutomationStatus = import_zod8.z.enum(AUTOMATION_STATUSES);
2150
2720
  var AUTOMATION_END_REASONS = [
2151
2721
  "completed",
2152
2722
  "exit_filter",
@@ -2165,9 +2735,9 @@ var AUTOMATION_END_REASONS = [
2165
2735
  "ended_by_hand",
2166
2736
  "failed"
2167
2737
  ];
2168
- var AutomationEndReason = import_zod7.z.enum(AUTOMATION_END_REASONS);
2738
+ var AutomationEndReason = import_zod8.z.enum(AUTOMATION_END_REASONS);
2169
2739
  var AUTOMATION_WAIT_REASONS = ["delay", "quiet_hours", "plan_limit", "sending", "condition"];
2170
- var AutomationWaitReason = import_zod7.z.enum(AUTOMATION_WAIT_REASONS);
2740
+ var AutomationWaitReason = import_zod8.z.enum(AUTOMATION_WAIT_REASONS);
2171
2741
  var AUTOMATION_ENTRY_REFUSALS = [
2172
2742
  "already_inside",
2173
2743
  "re_entry_off",
@@ -2178,26 +2748,26 @@ var AUTOMATION_ENTRY_REFUSALS = [
2178
2748
  "not_active",
2179
2749
  "unknown_contact"
2180
2750
  ];
2181
- var AutomationEntryRefusal = import_zod7.z.enum(AUTOMATION_ENTRY_REFUSALS);
2182
- var AutomationCounts = import_zod7.z.object({
2183
- started: import_zod7.z.number().int().min(0),
2184
- in_progress: import_zod7.z.number().int().min(0),
2185
- completed: import_zod7.z.number().int().min(0),
2186
- exited: import_zod7.z.number().int().min(0),
2187
- failed: import_zod7.z.number().int().min(0)
2188
- });
2189
- 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({
2190
2760
  id: Id,
2191
- name: import_zod7.z.string().min(1).max(200),
2761
+ name: import_zod8.z.string().min(1).max(200),
2192
2762
  status: AutomationStatus,
2193
2763
  /** Every automation sends under one topic; there are no transactional automations. */
2194
2764
  topic: Slug,
2195
2765
  provider_id: Id.nullable(),
2196
2766
  settings: AutomationSettings,
2197
2767
  /** The flow people are running through, null while the automation has never been published. */
2198
- published_version: import_zod7.z.number().int().min(1).nullable(),
2768
+ published_version: import_zod8.z.number().int().min(1).nullable(),
2199
2769
  /** The flow being edited. Increments on every save, like a template's version. */
2200
- draft_version: import_zod7.z.number().int().min(1),
2770
+ draft_version: import_zod8.z.number().int().min(1),
2201
2771
  counts: AutomationCounts,
2202
2772
  created_at: Timestamp,
2203
2773
  updated_at: Timestamp,
@@ -2210,16 +2780,16 @@ var AutomationWithFlow = Automation.extend({
2210
2780
  published_flow: AutomationFlow.nullable()
2211
2781
  });
2212
2782
  var AutomationSummary = Automation;
2213
- var AutomationCreate = import_zod7.z.object({
2214
- 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),
2215
2785
  topic: Slug,
2216
2786
  provider_id: Id.optional(),
2217
2787
  flow: AutomationFlow.optional(),
2218
2788
  settings: AutomationSettings.partial().optional()
2219
2789
  });
2220
- var AutomationUpdate = import_zod7.z.object({
2221
- base_version: import_zod7.z.number().int().min(1),
2222
- 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(),
2223
2793
  topic: Slug.optional(),
2224
2794
  provider_id: Id.optional(),
2225
2795
  flow: AutomationFlow.optional(),
@@ -2232,42 +2802,42 @@ var AutomationListQuery = PageQuery.extend({
2232
2802
  status: AutomationStatus.optional(),
2233
2803
  topic: Slug.optional()
2234
2804
  });
2235
- var AutomationPublishRequest = import_zod7.z.object({
2805
+ var AutomationPublishRequest = import_zod8.z.object({
2236
2806
  /**
2237
2807
  * Answer what this publish would do and change nothing, whatever the flow
2238
2808
  * looks like. Without it a publish that has no issues and moves nobody
2239
2809
  * simply goes ahead, which is right for an API caller and wrong for a
2240
2810
  * dialog that is only showing somebody what they are about to do.
2241
2811
  */
2242
- dry_run: import_zod7.z.boolean().optional(),
2243
- confirm: import_zod7.z.boolean().optional(),
2812
+ dry_run: import_zod8.z.boolean().optional(),
2813
+ confirm: import_zod8.z.boolean().optional(),
2244
2814
  /** Per removed step: the step its waiting runs move to, or null to end them. */
2245
- moves: import_zod7.z.record(StepId, StepId.nullable()).optional(),
2815
+ moves: import_zod8.z.record(StepId, StepId.nullable()).optional(),
2246
2816
  /** Also enter everyone who matches a trigger's condition right now. */
2247
- backfill: import_zod7.z.boolean().optional()
2817
+ backfill: import_zod8.z.boolean().optional()
2248
2818
  }).strict();
2249
- var AutomationPublishMove = import_zod7.z.object({
2819
+ var AutomationPublishMove = import_zod8.z.object({
2250
2820
  step_id: StepId,
2251
2821
  /** Why the step's people have to move: it is gone, or its delay changed. */
2252
- change: import_zod7.z.enum(["removed", "delay_changed", "kind_changed"]),
2253
- 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),
2254
2824
  /** Where they go: a step id, or null to end their runs with `step_removed`. */
2255
2825
  destination: StepId.nullable()
2256
2826
  });
2257
- var AutomationPublishResult = import_zod7.z.object({
2258
- published: import_zod7.z.boolean(),
2827
+ var AutomationPublishResult = import_zod8.z.object({
2828
+ published: import_zod8.z.boolean(),
2259
2829
  automation: Automation,
2260
2830
  /** Empty when nothing has to move; non-empty and `published` false asks for a confirmation. */
2261
- moves: import_zod7.z.array(AutomationPublishMove),
2831
+ moves: import_zod8.z.array(AutomationPublishMove),
2262
2832
  /** How many people the backfill entered, or would enter when it is not confirmed. */
2263
- backfill: import_zod7.z.number().int().min(0),
2264
- issues: import_zod7.z.array(AutomationIssue)
2265
- });
2266
- var AutomationDuplicateRequest = import_zod7.z.object({ name: import_zod7.z.string().min(1).max(200).optional() }).strict();
2267
- var AutomationEnrollRequest = import_zod7.z.object({
2268
- contact_ids: import_zod7.z.array(Id).min(1).max(1e3).optional(),
2269
- external_ids: import_zod7.z.array(import_zod7.z.string().min(1).max(255)).min(1).max(1e3).optional(),
2270
- 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(),
2271
2841
  segment_id: Id.optional(),
2272
2842
  /** Where to start them; the flow's entry step by default. */
2273
2843
  step_id: StepId.optional()
@@ -2275,34 +2845,34 @@ var AutomationEnrollRequest = import_zod7.z.object({
2275
2845
  (v) => [v.contact_ids, v.external_ids, v.emails, v.segment_id].filter((x) => x !== void 0).length === 1,
2276
2846
  "name the people exactly one way: contact_ids, external_ids, emails or segment_id"
2277
2847
  );
2278
- var AutomationEnrollment = import_zod7.z.object({
2848
+ var AutomationEnrollment = import_zod8.z.object({
2279
2849
  contact_id: Id.nullable(),
2280
2850
  email: Email.nullable(),
2281
- external_id: import_zod7.z.string().nullable(),
2282
- entered: import_zod7.z.boolean(),
2851
+ external_id: import_zod8.z.string().nullable(),
2852
+ entered: import_zod8.z.boolean(),
2283
2853
  run_id: Id.nullable(),
2284
2854
  reason: AutomationEntryRefusal.nullable()
2285
2855
  });
2286
- var AutomationEnrollResult = import_zod7.z.object({
2287
- entered: import_zod7.z.number().int().min(0),
2288
- refused: import_zod7.z.number().int().min(0),
2289
- 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)
2290
2860
  });
2291
- var AutomationRunStatus = import_zod7.z.enum(["active", "waiting", "completed", "exited", "failed"]);
2292
- 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({
2293
2863
  id: Id,
2294
2864
  automation_id: Id,
2295
2865
  contact_id: Id,
2296
2866
  email: Email,
2297
- external_id: import_zod7.z.string().nullable(),
2298
- 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),
2299
2869
  status: AutomationRunStatus,
2300
2870
  current_step_id: StepId.nullable(),
2301
2871
  next_action_at: Timestamp.nullable(),
2302
2872
  wait_reason: AutomationWaitReason.nullable(),
2303
- end_reason: import_zod7.z.string().nullable(),
2873
+ end_reason: import_zod8.z.string().nullable(),
2304
2874
  /** What went wrong the last time this run tried to take its step, null otherwise. */
2305
- last_error: import_zod7.z.string().nullable(),
2875
+ last_error: import_zod8.z.string().nullable(),
2306
2876
  entered_at: Timestamp,
2307
2877
  ended_at: Timestamp.nullable()
2308
2878
  });
@@ -2312,13 +2882,13 @@ var AutomationRunListQuery = PageQuery.extend({
2312
2882
  contact_id: Id.optional()
2313
2883
  });
2314
2884
  var ContactAutomationRun = AutomationRun.extend({
2315
- 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 })
2316
2886
  });
2317
2887
  var ContactAutomationListQuery = PageQuery.extend({ status: AutomationRunStatus.optional() });
2318
- var AutomationStepExecution = import_zod7.z.object({
2888
+ var AutomationStepExecution = import_zod8.z.object({
2319
2889
  step_id: StepId,
2320
2890
  step_kind: AutomationStepKind,
2321
- visit: import_zod7.z.number().int().min(1),
2891
+ visit: import_zod8.z.number().int().min(1),
2322
2892
  started_at: Timestamp,
2323
2893
  finished_at: Timestamp.nullable(),
2324
2894
  /**
@@ -2330,48 +2900,48 @@ var AutomationStepExecution = import_zod7.z.object({
2330
2900
  */
2331
2901
  outcome: Properties
2332
2902
  });
2333
- var AutomationJourney = import_zod7.z.object({
2903
+ var AutomationJourney = import_zod8.z.object({
2334
2904
  run: AutomationRun,
2335
- steps: import_zod7.z.array(AutomationStepExecution)
2905
+ steps: import_zod8.z.array(AutomationStepExecution)
2336
2906
  });
2337
- var AutomationStepReport = import_zod7.z.object({
2907
+ var AutomationStepReport = import_zod8.z.object({
2338
2908
  step_id: StepId,
2339
2909
  step_kind: AutomationStepKind,
2340
- reached: import_zod7.z.number().int().min(0),
2341
- waiting: import_zod7.z.number().int().min(0),
2342
- 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),
2343
2913
  /** Per branch, for a condition: how many took `yes` and how many `no`. */
2344
- 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)),
2345
2915
  /** The step mailing's id, for an email step of the published version. */
2346
2916
  mailing_id: Id.nullable()
2347
2917
  });
2348
- var AutomationReport = import_zod7.z.object({
2918
+ var AutomationReport = import_zod8.z.object({
2349
2919
  counts: AutomationCounts,
2350
2920
  /** Why the people who left did: `AutomationEndReason` to a count. */
2351
- 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)),
2352
2922
  /**
2353
2923
  * Why people a trigger named did not enter: `AutomationEntryRefusal` to a
2354
2924
  * count. Without it a pause, an exclusion or a missing subscription would
2355
2925
  * turn people away invisibly.
2356
2926
  */
2357
- refusals: import_zod7.z.record(import_zod7.z.number().int().min(0)),
2358
- steps: import_zod7.z.array(AutomationStepReport)
2359
- });
2360
- var AutomationExitRunRequest = import_zod7.z.object({ reason: import_zod7.z.string().max(200).optional() }).strict();
2361
- var AutomationRunParams = import_zod7.z.object({ id: Id, run_id: Id });
2362
- var AutomationStepParams = import_zod7.z.object({ id: Id, step_id: StepId });
2363
- var AutomationTestEmailRequest = import_zod7.z.object({ to: Email, merge: Properties.optional() }).strict();
2364
- var AutomationValidateRequest = import_zod7.z.object({ flow: AutomationFlow.optional(), settings: AutomationSettings.partial().optional() }).strict();
2365
- 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({
2366
2936
  name: EventName,
2367
- 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"),
2368
2938
  data: Properties.optional(),
2369
2939
  occurred_at: Timestamp.optional(),
2370
- id: import_zod7.z.string().min(1).max(128).optional()
2940
+ id: import_zod8.z.string().min(1).max(128).optional()
2371
2941
  }).strict();
2372
- var Event = import_zod7.z.object({
2942
+ var Event = import_zod8.z.object({
2373
2943
  id: Id,
2374
- client_event_id: import_zod7.z.string().nullable(),
2944
+ client_event_id: import_zod8.z.string().nullable(),
2375
2945
  name: EventName,
2376
2946
  contact_id: Id,
2377
2947
  data: Properties,
@@ -2382,59 +2952,42 @@ var Event = import_zod7.z.object({
2382
2952
  * hand. Each is then admitted or refused by the runtime on its own rules
2383
2953
  * (consent, exclusions, re-entry), which `automations.report` counts.
2384
2954
  */
2385
- entered: import_zod7.z.number().int().min(0)
2955
+ entered: import_zod8.z.number().int().min(0)
2386
2956
  });
2387
2957
  var EventListQuery = PageQuery.extend({
2388
2958
  name: EventName.optional(),
2389
2959
  contact_id: Id.optional()
2390
2960
  });
2391
2961
 
2392
- // src/unsubscribe.ts
2393
- var UNSUBSCRIBE_PATH_PREFIX = "/u/";
2394
- var LIST_UNSUBSCRIBE_POST_VALUE = "List-Unsubscribe=One-Click";
2395
- var RESERVED_MERGE_FIELDS = {
2396
- /** From the recipient's merge values, else the contact. Supports a fallback. */
2397
- first_name: { source: "contact", fallback: true, required_for_broadcast: false },
2398
- last_name: { source: "contact", fallback: true, required_for_broadcast: false },
2399
- email: { source: "contact", fallback: false, required_for_broadcast: false },
2400
- /** The hosted unsubscribe page for this recipient. A broadcast without it is refused. */
2401
- unsubscribe_url: { source: "service", fallback: false, required_for_broadcast: true }
2402
- };
2403
- var MERGE_FIELD_PATTERN = /\{\{\s*([a-z][a-z0-9_]*)\s*(?:\|([^}]*))?\}\}/g;
2404
- function findMergeFields(html) {
2405
- const uses = [];
2406
- for (const match of html.matchAll(MERGE_FIELD_PATTERN)) {
2407
- uses.push({ name: match[1], fallback: match[2] === void 0 ? null : match[2].trim() });
2408
- }
2409
- return uses;
2410
- }
2411
- function missingRequiredMergeFields(html) {
2412
- const used = new Set(findMergeFields(html).map((u) => u.name));
2413
- return Object.keys(RESERVED_MERGE_FIELDS).filter(
2414
- (name) => RESERVED_MERGE_FIELDS[name].required_for_broadcast && !used.has(name)
2415
- );
2416
- }
2417
-
2418
2962
  // src/workspace.ts
2419
- var CompanyId = import_zod8.z.string().min(1).max(64);
2963
+ var CompanyId = import_zod9.z.string().min(1).max(64);
2420
2964
  var ASSET_POLICIES = ["any", "service_only"];
2421
- var AssetPolicy = import_zod8.z.enum(ASSET_POLICIES);
2965
+ var AssetPolicy = import_zod9.z.enum(ASSET_POLICIES);
2422
2966
  var MAX_ALLOWED_ASSET_HOSTS = 20;
2423
- 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");
2424
2968
  var MAX_MERGE_DEFAULTS = 50;
2425
2969
  var MAX_MERGE_DEFAULT_LENGTH = 2e3;
2426
- 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);
2427
- var MergeDefaultValue = import_zod8.z.union([import_zod8.z.string().max(MAX_MERGE_DEFAULT_LENGTH), import_zod8.z.number().finite(), import_zod8.z.boolean()]);
2428
- 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), {
2429
2973
  message: `a reserved merge field (${Object.keys(RESERVED_MERGE_FIELDS).join(", ")}) cannot have a workspace default`
2430
2974
  });
2431
- var WorkspaceSettings = import_zod8.z.object({
2432
- /** Default language of the hosted unsubscribe page (BCP 47 tag, e.g. "de"). */
2433
- default_locale: import_zod8.z.string().min(2).max(35),
2434
- /** Languages the hosted pages are offered in. Includes `default_locale`. */
2435
- locales: import_zod8.z.array(import_zod8.z.string().min(2).max(35)).min(1).max(20),
2975
+ var WorkspaceSettings = import_zod9.z.object({
2976
+ /**
2977
+ * The workspace's main language (a {@link LocaleTag}, e.g. "de"): the main
2978
+ * language of a new template, and of the hosted pages when nothing else
2979
+ * decides.
2980
+ */
2981
+ default_locale: LocaleTag,
2982
+ /**
2983
+ * The languages the workspace writes in, including `default_locale`, none
2984
+ * twice (compared without regard to casing). A template can have a version in
2985
+ * each; the hosted pages are offered in those of them they support (English,
2986
+ * German, French, Italian, Spanish). The service stores canonical casing.
2987
+ */
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"),
2436
2989
  /** Open and click tracking. Off by default, opt-in per workspace. */
2437
- tracking_enabled: import_zod8.z.boolean(),
2990
+ tracking_enabled: import_zod9.z.boolean(),
2438
2991
  /** Where images, stylesheets and fonts in a mail may load from. `any` by default. */
2439
2992
  asset_policy: AssetPolicy,
2440
2993
  /**
@@ -2442,7 +2995,7 @@ var WorkspaceSettings = import_zod8.z.object({
2442
2995
  * the workspace's website, typically. Empty by default. Ignored under `any`,
2443
2996
  * which allows everything already.
2444
2997
  */
2445
- 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),
2446
2999
  /** Merge values every mail of the workspace can use (see `MergeDefaults`). Empty by default. */
2447
3000
  merge_defaults: MergeDefaults,
2448
3001
  /**
@@ -2451,10 +3004,10 @@ var WorkspaceSettings = import_zod8.z.object({
2451
3004
  */
2452
3005
  default_timezone: TimezoneName.nullable()
2453
3006
  });
2454
- var Workspace = import_zod8.z.object({
3007
+ var Workspace = import_zod9.z.object({
2455
3008
  id: Id,
2456
3009
  slug: Slug,
2457
- name: import_zod8.z.string().min(1).max(120),
3010
+ name: import_zod9.z.string().min(1).max(120),
2458
3011
  settings: WorkspaceSettings,
2459
3012
  /**
2460
3013
  * The auth-brain company (tenant) the workspace was created for, or null for
@@ -2465,13 +3018,13 @@ var Workspace = import_zod8.z.object({
2465
3018
  created_at: Timestamp,
2466
3019
  updated_at: Timestamp
2467
3020
  });
2468
- var WorkspaceCreate = import_zod8.z.object({
3021
+ var WorkspaceCreate = import_zod9.z.object({
2469
3022
  slug: Slug,
2470
- name: import_zod8.z.string().min(1).max(120),
3023
+ name: import_zod9.z.string().min(1).max(120),
2471
3024
  settings: WorkspaceSettings.partial().optional(),
2472
- owner: import_zod8.z.object({
3025
+ owner: import_zod9.z.object({
2473
3026
  email: Email,
2474
- name: import_zod8.z.string().max(200).nullable().optional()
3027
+ name: import_zod9.z.string().max(200).nullable().optional()
2475
3028
  }),
2476
3029
  /**
2477
3030
  * The signed-in person's active auth-brain company. The dashboard always
@@ -2481,49 +3034,49 @@ var WorkspaceCreate = import_zod8.z.object({
2481
3034
  */
2482
3035
  company_id: CompanyId.optional()
2483
3036
  });
2484
- var WorkspaceUpdate = import_zod8.z.object({
2485
- 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),
2486
3039
  settings: WorkspaceSettings.partial()
2487
3040
  }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
2488
- var WorkspaceMove = import_zod8.z.object({ company_id: CompanyId });
3041
+ var WorkspaceMove = import_zod9.z.object({ company_id: CompanyId });
2489
3042
  var MEMBER_ROLES = ["owner", "admin", "editor", "viewer"];
2490
- var MemberRole = import_zod8.z.enum(MEMBER_ROLES);
3043
+ var MemberRole = import_zod9.z.enum(MEMBER_ROLES);
2491
3044
  var WorkspaceMembership = Workspace.extend({ role: MemberRole });
2492
- var Member = import_zod8.z.object({
3045
+ var Member = import_zod9.z.object({
2493
3046
  id: Id,
2494
3047
  /** The person's auth-brain subject. */
2495
- subject: import_zod8.z.string().min(1).max(255),
3048
+ subject: import_zod9.z.string().min(1).max(255),
2496
3049
  email: Email,
2497
- name: import_zod8.z.string().max(200).nullable(),
3050
+ name: import_zod9.z.string().max(200).nullable(),
2498
3051
  role: MemberRole,
2499
3052
  created_at: Timestamp
2500
3053
  });
2501
- var MemberCreate = import_zod8.z.object({
2502
- 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),
2503
3056
  email: Email,
2504
- name: import_zod8.z.string().max(200).nullable().optional(),
3057
+ name: import_zod9.z.string().max(200).nullable().optional(),
2505
3058
  role: MemberRole
2506
3059
  });
2507
- var MemberUpdate = import_zod8.z.object({ role: MemberRole });
3060
+ var MemberUpdate = import_zod9.z.object({ role: MemberRole });
2508
3061
  var API_KEY_SCOPES = ["full", "read", "send"];
2509
- var ApiKeyScope = import_zod8.z.enum(API_KEY_SCOPES);
2510
- var ApiKey = import_zod8.z.object({
3062
+ var ApiKeyScope = import_zod9.z.enum(API_KEY_SCOPES);
3063
+ var ApiKey = import_zod9.z.object({
2511
3064
  id: Id,
2512
- name: import_zod8.z.string().min(1).max(120),
3065
+ name: import_zod9.z.string().min(1).max(120),
2513
3066
  /** The first characters of the key, for recognising it in a list. */
2514
- prefix: import_zod8.z.string().min(4).max(32),
3067
+ prefix: import_zod9.z.string().min(4).max(32),
2515
3068
  scope: ApiKeyScope,
2516
3069
  last_used_at: Timestamp.nullable(),
2517
3070
  revoked_at: Timestamp.nullable(),
2518
3071
  created_at: Timestamp
2519
3072
  });
2520
- var ApiKeyCreate = import_zod8.z.object({
2521
- 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),
2522
3075
  /** Omitted means `full`. */
2523
3076
  scope: ApiKeyScope.optional()
2524
3077
  });
2525
- var ApiKeyCreated = import_zod8.z.object({
2526
- key: import_zod8.z.string().min(20),
3078
+ var ApiKeyCreated = import_zod9.z.object({
3079
+ key: import_zod9.z.string().min(20),
2527
3080
  api_key: ApiKey
2528
3081
  });
2529
3082
  var AUDIT_ACTIONS = [
@@ -2611,29 +3164,29 @@ var AUDIT_ACTIONS = [
2611
3164
  "automation.run_ended",
2612
3165
  "event.recorded"
2613
3166
  ];
2614
- var AuditAction = import_zod8.z.enum(AUDIT_ACTIONS);
2615
- var AuditActor = import_zod8.z.discriminatedUnion("type", [
2616
- import_zod8.z.object({ type: import_zod8.z.literal("member"), member_id: Id, subject: import_zod8.z.string().min(1) }),
2617
- import_zod8.z.object({
2618
- 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"),
2619
3172
  api_key_id: Id,
2620
3173
  /**
2621
3174
  * Who the client's application said it was acting for, from
2622
3175
  * `ON_BEHALF_OF_HEADER`: reported by that application, never verified.
2623
3176
  * Absent when the call carried no label.
2624
3177
  */
2625
- 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()
2626
3179
  }),
2627
3180
  /** The service itself (the worker, a bounce, the hosted unsubscribe page). */
2628
- 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) })
2629
3182
  ]);
2630
- var AuditEntry = import_zod8.z.object({
3183
+ var AuditEntry = import_zod9.z.object({
2631
3184
  id: Id,
2632
3185
  action: AuditAction,
2633
3186
  actor: AuditActor,
2634
- target_type: import_zod8.z.string().min(1).max(64),
3187
+ target_type: import_zod9.z.string().min(1).max(64),
2635
3188
  target_id: Id.nullable(),
2636
- details: import_zod8.z.record(import_zod8.z.unknown()),
3189
+ details: import_zod9.z.record(import_zod9.z.unknown()),
2637
3190
  created_at: Timestamp
2638
3191
  });
2639
3192
  var AuditQuery = PageQuery.extend({
@@ -2642,12 +3195,12 @@ var AuditQuery = PageQuery.extend({
2642
3195
  });
2643
3196
 
2644
3197
  // src/invites.ts
2645
- var import_zod9 = require("zod");
3198
+ var import_zod10 = require("zod");
2646
3199
  var INVITE_STATUSES = ["pending", "accepted", "revoked", "expired"];
2647
- var InviteStatus = import_zod9.z.enum(INVITE_STATUSES);
3200
+ var InviteStatus = import_zod10.z.enum(INVITE_STATUSES);
2648
3201
  var DEFAULT_INVITE_TTL_DAYS = 7;
2649
3202
  var MAX_INVITE_TTL_DAYS = 30;
2650
- var Invite = import_zod9.z.object({
3203
+ var Invite = import_zod10.z.object({
2651
3204
  id: Id,
2652
3205
  /** Stored lowercased; only a person signed in with this address can accept. */
2653
3206
  email: Email,
@@ -2655,43 +3208,565 @@ var Invite = import_zod9.z.object({
2655
3208
  /** Derived from the timestamps below, never stored. */
2656
3209
  status: InviteStatus,
2657
3210
  /** The member who invited, while they are still one (null after they left). */
2658
- 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() }),
2659
3212
  expires_at: Timestamp,
2660
3213
  accepted_at: Timestamp.nullable(),
2661
3214
  revoked_at: Timestamp.nullable(),
2662
3215
  created_at: Timestamp
2663
3216
  });
2664
- var InviteCreate = import_zod9.z.object({
3217
+ var InviteCreate = import_zod10.z.object({
2665
3218
  email: Email,
2666
3219
  role: MemberRole,
2667
- 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()
2668
3221
  });
2669
- var InviteCreated = import_zod9.z.object({
3222
+ var InviteCreated = import_zod10.z.object({
2670
3223
  invite: Invite,
2671
- token: import_zod9.z.string().min(32).max(128)
3224
+ token: import_zod10.z.string().min(32).max(128)
2672
3225
  });
2673
3226
  var InviteListQuery = PageQuery.extend({
2674
3227
  status: InviteStatus.optional()
2675
3228
  });
2676
- var InviteAccept = import_zod9.z.object({
2677
- 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),
2678
3231
  email: Email,
2679
- name: import_zod9.z.string().max(200).nullable().optional()
3232
+ name: import_zod10.z.string().max(200).nullable().optional()
2680
3233
  });
2681
- var InviteAccepted = import_zod9.z.object({
3234
+ var InviteAccepted = import_zod10.z.object({
2682
3235
  workspace: WorkspaceMembership,
2683
- already_member: import_zod9.z.boolean()
3236
+ already_member: import_zod10.z.boolean()
2684
3237
  });
2685
3238
  var INVITE_REFUSAL_REASONS = ["email_mismatch", "inviter_lacks_role", "expired", "revoked", "accepted"];
2686
3239
 
3240
+ // src/template-languages.ts
3241
+ function languageTabs(mainLocale, versions, workspaceLocales) {
3242
+ const main = canonicalLocale(mainLocale) ?? mainLocale;
3243
+ const statuses = /* @__PURE__ */ new Map();
3244
+ for (const [tag, status] of Object.entries(versions)) {
3245
+ const canonical = canonicalLocale(tag) ?? tag;
3246
+ if (canonical !== main) statuses.set(canonical, status);
3247
+ }
3248
+ const tabs = [{ locale: main, kind: "main", status: "ready" }];
3249
+ const seen = /* @__PURE__ */ new Set([main]);
3250
+ for (const tag of workspaceLocales) {
3251
+ const canonical = canonicalLocale(tag) ?? tag;
3252
+ if (seen.has(canonical)) continue;
3253
+ seen.add(canonical);
3254
+ tabs.push({ locale: canonical, kind: "translation", status: statuses.get(canonical) ?? "missing" });
3255
+ }
3256
+ const kept = [...statuses.keys()].filter((tag) => !seen.has(tag)).sort();
3257
+ for (const tag of kept) tabs.push({ locale: tag, kind: "kept", status: statuses.get(tag) });
3258
+ return tabs;
3259
+ }
3260
+ function languageName(tag) {
3261
+ const canonical = canonicalLocale(tag) ?? tag;
3262
+ try {
3263
+ const names = new Intl.DisplayNames(["en"], { type: "language", fallback: "none" });
3264
+ const name = names.of(canonical);
3265
+ return name && name !== canonical ? name : canonical;
3266
+ } catch {
3267
+ return canonical;
3268
+ }
3269
+ }
3270
+ function storedTemplateLine(value) {
3271
+ if (value === null || value === void 0) return null;
3272
+ const trimmed = value.trim();
3273
+ return trimmed === "" ? null : trimmed;
3274
+ }
3275
+ function buildTemplatePatch(args) {
3276
+ const { loaded, main, documentChanged, translations, changed, removed } = args;
3277
+ const out = {};
3278
+ if (args.insertions !== void 0 && !sameJson(args.insertions, loaded.insertions ?? {})) out.insertions = args.insertions;
3279
+ const subject = storedTemplateLine(main.subject);
3280
+ if (subject !== storedTemplateLine(loaded.subject)) out.subject = subject;
3281
+ const preheader = storedTemplateLine(main.preheader);
3282
+ if (preheader !== storedTemplateLine(loaded.preheader)) out.preheader = preheader;
3283
+ if (documentChanged) out.document = main.document;
3284
+ const patch = {};
3285
+ for (const tag of changed) {
3286
+ const version = translations[tag];
3287
+ if (version && !removed.has(tag)) {
3288
+ patch[tag] = {
3289
+ subject: storedTemplateLine(version.subject),
3290
+ preheader: storedTemplateLine(version.preheader),
3291
+ document: version.document,
3292
+ status: version.status,
3293
+ ...version.insertions !== void 0 ? { insertions: version.insertions } : {}
3294
+ };
3295
+ }
3296
+ }
3297
+ for (const tag of removed) {
3298
+ if (Object.prototype.hasOwnProperty.call(loaded.translations, tag)) patch[tag] = null;
3299
+ }
3300
+ if (Object.keys(patch).length > 0) out.translations = patch;
3301
+ return out;
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
+ }
3307
+ function restorePatch(old, current, workspaceLocales) {
3308
+ const oldMain = canonicalLocale(old.locale) ?? old.locale;
3309
+ const listed = workspaceLocales ? new Set(workspaceLocales.map((t) => canonicalLocale(t) ?? t)) : null;
3310
+ const patch = {};
3311
+ const skipped = [];
3312
+ const untouched = /* @__PURE__ */ new Set();
3313
+ for (const [tag, version] of Object.entries(old.translations)) {
3314
+ const canonical = canonicalLocale(tag) ?? tag;
3315
+ if (canonical === oldMain) continue;
3316
+ if (listed && !listed.has(canonical)) {
3317
+ skipped.push(canonical);
3318
+ untouched.add(canonical);
3319
+ continue;
3320
+ }
3321
+ patch[canonical] = { ...version, insertions: version.insertions ?? {} };
3322
+ }
3323
+ for (const tag of Object.keys(current.translations)) {
3324
+ const canonical = canonicalLocale(tag) ?? tag;
3325
+ if (!(canonical in patch) && !untouched.has(canonical)) patch[canonical] = null;
3326
+ if (canonical === oldMain) patch[canonical] = null;
3327
+ }
3328
+ const update = {
3329
+ ...oldMain !== (canonicalLocale(current.locale) ?? current.locale) ? { locale: oldMain } : {},
3330
+ subject: old.subject,
3331
+ preheader: old.preheader,
3332
+ document: old.document,
3333
+ insertions: old.insertions ?? {},
3334
+ ...Object.keys(patch).length > 0 ? { translations: patch } : {}
3335
+ };
3336
+ return { update, skipped: skipped.sort() };
3337
+ }
3338
+ function draftsFromTemplate(template) {
3339
+ const insertions = template.insertions ?? {};
3340
+ const translations = {};
3341
+ for (const [tag, version] of Object.entries(template.translations)) {
3342
+ translations[canonicalLocale(tag) ?? tag] = { ...version, insertions: alignTranslationInsertions(insertions, version.insertions) };
3343
+ }
3344
+ return {
3345
+ main: { subject: template.subject ?? "", preheader: template.preheader ?? "", document: template.document },
3346
+ insertions,
3347
+ translations,
3348
+ mainDocumentChanged: false,
3349
+ changed: [],
3350
+ removed: []
3351
+ };
3352
+ }
3353
+ var withChanged = (list2, tag) => list2.includes(tag) ? [...list2] : [...list2, tag];
3354
+ var without = (list2, tag) => list2.filter((t) => t !== tag);
3355
+ function draftDocument(drafts, locale) {
3356
+ if (locale === null) return drafts.main.document;
3357
+ return drafts.translations[locale]?.document;
3358
+ }
3359
+ function addLanguageDraft(drafts, locale) {
3360
+ const tag = canonicalLocale(locale) ?? locale;
3361
+ if (drafts.translations[tag]) return drafts;
3362
+ return {
3363
+ ...drafts,
3364
+ translations: {
3365
+ ...drafts.translations,
3366
+ [tag]: {
3367
+ subject: drafts.main.subject,
3368
+ preheader: drafts.main.preheader,
3369
+ document: structuredClone(drafts.main.document),
3370
+ status: "draft",
3371
+ insertions: copyInsertionTexts(drafts.insertions)
3372
+ }
3373
+ },
3374
+ changed: withChanged(drafts.changed, tag),
3375
+ removed: without(drafts.removed, tag)
3376
+ };
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
+ }
3383
+ function removeLanguageDraft(drafts, locale) {
3384
+ const tag = canonicalLocale(locale) ?? locale;
3385
+ const { [tag]: _gone, ...rest } = drafts.translations;
3386
+ return { ...drafts, translations: rest, changed: without(drafts.changed, tag), removed: withChanged(drafts.removed, tag) };
3387
+ }
3388
+ function editLanguageDraft(drafts, locale, edit) {
3389
+ if (locale === null) {
3390
+ return {
3391
+ ...drafts,
3392
+ main: { ...drafts.main, ...edit },
3393
+ mainDocumentChanged: drafts.mainDocumentChanged || edit.document !== void 0
3394
+ };
3395
+ }
3396
+ const version = drafts.translations[locale];
3397
+ if (!version) return drafts;
3398
+ const next2 = { ...version, ...edit };
3399
+ return { ...drafts, translations: { ...drafts.translations, [locale]: next2 }, changed: withChanged(drafts.changed, locale) };
3400
+ }
3401
+ function keepLanguageDocument(drafts, locale, document) {
3402
+ if (locale === null) return { ...drafts, main: { ...drafts.main, document } };
3403
+ const version = drafts.translations[locale];
3404
+ if (!version) return drafts;
3405
+ return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, document } } };
3406
+ }
3407
+ function setLanguageStatus(drafts, locale, status) {
3408
+ const version = drafts.translations[locale];
3409
+ if (!version || version.status === status) return drafts;
3410
+ return { ...drafts, translations: { ...drafts.translations, [locale]: { ...version, status } }, changed: withChanged(drafts.changed, locale) };
3411
+ }
3412
+ function draftsPatch(drafts, loaded) {
3413
+ return buildTemplatePatch({
3414
+ loaded,
3415
+ insertions: drafts.insertions,
3416
+ main: drafts.main,
3417
+ documentChanged: drafts.mainDocumentChanged,
3418
+ translations: drafts.translations,
3419
+ changed: new Set(drafts.changed),
3420
+ removed: new Set(drafts.removed)
3421
+ });
3422
+ }
3423
+ function draftsDirty(drafts, loaded) {
3424
+ return Object.keys(draftsPatch(drafts, loaded)).length > 0;
3425
+ }
3426
+ function readyWithoutSubject(drafts) {
3427
+ return Object.entries(drafts.translations).filter(([tag, v]) => drafts.changed.includes(tag) && v.status === "ready" && storedTemplateLine(v.subject) === null).map(([tag]) => tag);
3428
+ }
3429
+ function rebaseDrafts(sent, now, saved) {
3430
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
3431
+ let out = draftsFromTemplate(saved);
3432
+ if (!sameJson(now.insertions, sent.insertions)) out = { ...out, insertions: now.insertions };
3433
+ if (now.main.subject !== sent.main.subject) out = editLanguageDraft(out, null, { subject: now.main.subject });
3434
+ if (now.main.preheader !== sent.main.preheader) out = editLanguageDraft(out, null, { preheader: now.main.preheader });
3435
+ out = same(now.main.document, sent.main.document) ? keepLanguageDocument(out, null, now.main.document) : editLanguageDraft(out, null, { document: now.main.document });
3436
+ const tags = /* @__PURE__ */ new Set([...Object.keys(sent.translations), ...Object.keys(now.translations)]);
3437
+ for (const tag of tags) {
3438
+ const before = sent.translations[tag];
3439
+ const after = now.translations[tag];
3440
+ if (same(before, after)) {
3441
+ if (after) out = keepLanguageDocument(out, tag, after.document);
3442
+ continue;
3443
+ }
3444
+ if (!after) {
3445
+ out = removeLanguageDraft(out, tag);
3446
+ continue;
3447
+ }
3448
+ out = {
3449
+ ...out,
3450
+ translations: { ...out.translations, [tag]: after },
3451
+ changed: withChanged(out.changed, tag),
3452
+ removed: without(out.removed, tag)
3453
+ };
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;
3470
+ return out;
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
+ }
3761
+
2687
3762
  // src/providers.ts
2688
- var import_zod10 = require("zod");
3763
+ var import_zod11 = require("zod");
2689
3764
  var PROVIDER_KINDS = ["smtp", "resend"];
2690
- var ProviderKind = import_zod10.z.enum(PROVIDER_KINDS);
2691
- var ProviderPolicy = import_zod10.z.object({
2692
- daily_recipient_budget: import_zod10.z.number().int().min(1).max(1e7),
2693
- min_interval_ms: import_zod10.z.number().int().min(0).max(36e5),
2694
- 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)
2695
3770
  });
2696
3771
  var ICLOUD_SMTP_POLICY = {
2697
3772
  daily_recipient_budget: 800,
@@ -2699,17 +3774,17 @@ var ICLOUD_SMTP_POLICY = {
2699
3774
  max_recipients_per_message: 1
2700
3775
  };
2701
3776
  var SMTP_SECURITY = ["tls", "starttls"];
2702
- var SmtpConfigBase = import_zod10.z.object({
2703
- host: import_zod10.z.string().min(1).max(255),
2704
- 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),
2705
3780
  /** `tls` connects encrypted (465), `starttls` upgrades (587). Never plaintext. */
2706
- security: import_zod10.z.enum(SMTP_SECURITY),
2707
- 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)
2708
3783
  });
2709
- var ResendConfigBase = import_zod10.z.object({});
2710
- var ProviderRejections = import_zod10.z.object({
2711
- count: import_zod10.z.number().int().min(0),
2712
- 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(),
2713
3788
  last_at: Timestamp.nullable(),
2714
3789
  /**
2715
3790
  * The last time the bounce circuit breaker tripped on one of this provider's
@@ -2719,59 +3794,59 @@ var ProviderRejections = import_zod10.z.object({
2719
3794
  * run's bounce blocks were undone and the mailing paused. `sample` is the
2720
3795
  * reply that tripped it.
2721
3796
  */
2722
- anomaly: import_zod10.z.object({
3797
+ anomaly: import_zod11.z.object({
2723
3798
  at: Timestamp,
2724
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. */
2725
- scope: import_zod10.z.enum(["mailing", "provider"]),
3800
+ scope: import_zod11.z.enum(["mailing", "provider"]),
2726
3801
  /**
2727
3802
  * True while the provider-wide breaker is open: no bounce blocks through
2728
3803
  * this provider, test sends refused and mailings not started or resumed
2729
3804
  * (`provider_anomaly`), until an admin clears it (`providers.clearAnomaly`).
2730
3805
  */
2731
- blocking: import_zod10.z.boolean(),
3806
+ blocking: import_zod11.z.boolean(),
2732
3807
  mailing_id: Id.nullable(),
2733
- reason: import_zod10.z.string(),
2734
- sample: import_zod10.z.string()
3808
+ reason: import_zod11.z.string(),
3809
+ sample: import_zod11.z.string()
2735
3810
  }).nullable()
2736
3811
  });
2737
- var ProviderEvents = import_zod10.z.object({
2738
- status: import_zod10.z.enum(["active", "needs_secret"]),
2739
- source: import_zod10.z.enum(["automatic", "manual"]).nullable(),
2740
- url: import_zod10.z.string().url(),
2741
- 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(),
2742
3817
  /**
2743
3818
  * Events that named an email this provider never sent through the service
2744
3819
  * (another workspace or system sharing the Resend account): counted, never
2745
3820
  * acted on.
2746
3821
  */
2747
- unmatched: import_zod10.z.number().int().min(0)
3822
+ unmatched: import_zod11.z.number().int().min(0)
2748
3823
  });
2749
3824
  var RESEND_EVENT_TYPES = ["email.bounced", "email.complained", "email.delivery_delayed"];
2750
- 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_");
2751
- 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 });
2752
3827
  var ProviderFields = {
2753
- name: import_zod10.z.string().min(1).max(120),
2754
- 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),
2755
3830
  from_email: Email,
2756
3831
  reply_to: Email.nullable(),
2757
3832
  policy: ProviderPolicy
2758
3833
  };
2759
- var Provider = import_zod10.z.discriminatedUnion("kind", [
2760
- import_zod10.z.object({
3834
+ var Provider = import_zod11.z.discriminatedUnion("kind", [
3835
+ import_zod11.z.object({
2761
3836
  id: Id,
2762
- kind: import_zod10.z.literal("smtp"),
3837
+ kind: import_zod11.z.literal("smtp"),
2763
3838
  config: SmtpConfigBase,
2764
- has_secret: import_zod10.z.boolean(),
3839
+ has_secret: import_zod11.z.boolean(),
2765
3840
  ...ProviderFields,
2766
3841
  rejections: ProviderRejections,
2767
3842
  created_at: Timestamp,
2768
3843
  updated_at: Timestamp
2769
3844
  }),
2770
- import_zod10.z.object({
3845
+ import_zod11.z.object({
2771
3846
  id: Id,
2772
- kind: import_zod10.z.literal("resend"),
3847
+ kind: import_zod11.z.literal("resend"),
2773
3848
  config: ResendConfigBase,
2774
- has_secret: import_zod10.z.boolean(),
3849
+ has_secret: import_zod11.z.boolean(),
2775
3850
  ...ProviderFields,
2776
3851
  rejections: ProviderRejections,
2777
3852
  events: ProviderEvents,
@@ -2779,31 +3854,31 @@ var Provider = import_zod10.z.discriminatedUnion("kind", [
2779
3854
  updated_at: Timestamp
2780
3855
  })
2781
3856
  ]);
2782
- var ProviderCreate = import_zod10.z.discriminatedUnion("kind", [
2783
- import_zod10.z.object({
2784
- kind: import_zod10.z.literal("smtp"),
2785
- 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) }),
2786
3861
  ...ProviderFields
2787
3862
  }),
2788
- import_zod10.z.object({
2789
- kind: import_zod10.z.literal("resend"),
2790
- 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) }),
2791
3866
  ...ProviderFields
2792
3867
  })
2793
3868
  ]);
2794
- var ProviderUpdate = import_zod10.z.discriminatedUnion("kind", [
2795
- import_zod10.z.object({
2796
- kind: import_zod10.z.literal("smtp"),
2797
- 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(),
2798
3873
  name: ProviderFields.name.optional(),
2799
3874
  from_name: ProviderFields.from_name.optional(),
2800
3875
  from_email: ProviderFields.from_email.optional(),
2801
3876
  reply_to: ProviderFields.reply_to.optional(),
2802
3877
  policy: ProviderPolicy.partial().optional()
2803
3878
  }),
2804
- import_zod10.z.object({
2805
- kind: import_zod10.z.literal("resend"),
2806
- 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(),
2807
3882
  name: ProviderFields.name.optional(),
2808
3883
  from_name: ProviderFields.from_name.optional(),
2809
3884
  from_email: ProviderFields.from_email.optional(),
@@ -2811,48 +3886,48 @@ var ProviderUpdate = import_zod10.z.discriminatedUnion("kind", [
2811
3886
  policy: ProviderPolicy.partial().optional()
2812
3887
  })
2813
3888
  ]);
2814
- var ProviderUsage = import_zod10.z.object({
3889
+ var ProviderUsage = import_zod11.z.object({
2815
3890
  provider_id: Id,
2816
- recipients_last_24h: import_zod10.z.number().int().min(0),
2817
- 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),
2818
3893
  /** When enough of the rolling window frees up to send again, if exhausted. */
2819
3894
  next_capacity_at: Timestamp.nullable()
2820
3895
  });
2821
- var ProviderVerifyResult = import_zod10.z.object({
2822
- ok: import_zod10.z.boolean(),
2823
- 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()
2824
3899
  });
2825
3900
 
2826
3901
  // src/contacts.ts
2827
- var import_zod11 = require("zod");
2828
- var Topic = import_zod11.z.object({
3902
+ var import_zod12 = require("zod");
3903
+ var Topic = import_zod12.z.object({
2829
3904
  id: Id,
2830
3905
  slug: Slug,
2831
- name: import_zod11.z.string().min(1).max(120),
3906
+ name: import_zod12.z.string().min(1).max(120),
2832
3907
  /** Shown on the hosted preference page. */
2833
- description: import_zod11.z.string().max(1e3).nullable(),
3908
+ description: import_zod12.z.string().max(1e3).nullable(),
2834
3909
  /** Translations of `name` and `description` per locale, for the hosted page. */
2835
- translations: import_zod11.z.record(
2836
- 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() })
2837
3912
  ),
2838
3913
  created_at: Timestamp,
2839
3914
  updated_at: Timestamp
2840
3915
  });
2841
- var TopicCreate = import_zod11.z.object({
3916
+ var TopicCreate = import_zod12.z.object({
2842
3917
  slug: Slug,
2843
- name: import_zod11.z.string().min(1).max(120),
2844
- 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(),
2845
3920
  translations: Topic.shape.translations.optional()
2846
3921
  });
2847
3922
  var TopicUpdate = TopicCreate.omit({ slug: true }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
2848
- var Contact = import_zod11.z.object({
3923
+ var Contact = import_zod12.z.object({
2849
3924
  id: Id,
2850
- external_id: import_zod11.z.string().min(1).max(255).nullable(),
3925
+ external_id: import_zod12.z.string().min(1).max(255).nullable(),
2851
3926
  /** Stored lowercased, unique per workspace. */
2852
3927
  email: Email,
2853
- first_name: import_zod11.z.string().max(200).nullable(),
2854
- last_name: import_zod11.z.string().max(200).nullable(),
2855
- 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(),
2856
3931
  /**
2857
3932
  * The person's own time zone, an IANA name such as `Europe/Berlin` (A1).
2858
3933
  * Every wall clock an automation waits for is read in it, falling back to the
@@ -2860,65 +3935,65 @@ var Contact = import_zod11.z.object({
2860
3935
  */
2861
3936
  timezone: TimezoneName.nullable(),
2862
3937
  properties: Properties,
2863
- topics: import_zod11.z.array(Slug),
3938
+ topics: import_zod12.z.array(Slug),
2864
3939
  /** The slugs of the contact's tags (S4). */
2865
- tags: import_zod11.z.array(Slug),
3940
+ tags: import_zod12.z.array(Slug),
2866
3941
  created_at: Timestamp,
2867
3942
  updated_at: Timestamp
2868
3943
  });
2869
- var ContactUpsert = import_zod11.z.object({
2870
- 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(),
2871
3946
  email: Email.optional(),
2872
- first_name: import_zod11.z.string().max(200).nullable().optional(),
2873
- last_name: import_zod11.z.string().max(200).nullable().optional(),
2874
- 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(),
2875
3950
  timezone: TimezoneName.nullable().optional(),
2876
3951
  properties: Properties.optional(),
2877
- topics: import_zod11.z.array(Slug).max(100).optional()
3952
+ topics: import_zod12.z.array(Slug).max(100).optional()
2878
3953
  }).refine((v) => v.external_id !== void 0 || v.email !== void 0, {
2879
3954
  message: "external_id or email is required",
2880
3955
  path: ["email"]
2881
3956
  });
2882
- var ContactUpsertResult = import_zod11.z.object({
3957
+ var ContactUpsertResult = import_zod12.z.object({
2883
3958
  contact: Contact,
2884
- created: import_zod11.z.boolean()
3959
+ created: import_zod12.z.boolean()
2885
3960
  });
2886
3961
  var ContactListQuery = PageQuery.extend({
2887
- email: import_zod11.z.string().min(1).max(254).optional(),
2888
- 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(),
2889
3964
  topic: Slug.optional()
2890
3965
  });
2891
- var ContactErased = import_zod11.z.object({
2892
- ok: import_zod11.z.literal(true),
2893
- erased_messages: import_zod11.z.number().int().min(0),
2894
- erased_recipients: import_zod11.z.number().int().min(0),
2895
- 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)
2896
3971
  });
2897
3972
  var SUPPRESSION_REASONS = ["unsubscribed", "bounced", "complained", "manual"];
2898
- var SuppressionReason = import_zod11.z.enum(SUPPRESSION_REASONS);
2899
- var Suppression = import_zod11.z.object({
3973
+ var SuppressionReason = import_zod12.z.enum(SUPPRESSION_REASONS);
3974
+ var Suppression = import_zod12.z.object({
2900
3975
  id: Id,
2901
3976
  email: Email,
2902
3977
  reason: SuppressionReason,
2903
3978
  topic: Slug.nullable(),
2904
3979
  source_message_id: Id.nullable(),
2905
- note: import_zod11.z.string().max(1e3).nullable(),
3980
+ note: import_zod12.z.string().max(1e3).nullable(),
2906
3981
  created_at: Timestamp
2907
3982
  });
2908
- var SuppressionCreate = import_zod11.z.object({
3983
+ var SuppressionCreate = import_zod12.z.object({
2909
3984
  email: Email,
2910
- reason: import_zod11.z.enum(["manual", "unsubscribed"]),
3985
+ reason: import_zod12.z.enum(["manual", "unsubscribed"]),
2911
3986
  topic: Slug.nullable().optional(),
2912
- note: import_zod11.z.string().max(1e3).optional()
3987
+ note: import_zod12.z.string().max(1e3).optional()
2913
3988
  });
2914
3989
  var SuppressionListQuery = PageQuery.extend({
2915
- email: import_zod11.z.string().min(1).max(254).optional(),
3990
+ email: import_zod12.z.string().min(1).max(254).optional(),
2916
3991
  reason: SuppressionReason.optional(),
2917
3992
  topic: Slug.optional()
2918
3993
  });
2919
3994
 
2920
3995
  // src/webhooks.ts
2921
- var import_zod12 = require("zod");
3996
+ var import_zod13 = require("zod");
2922
3997
  var WEBHOOK_EVENT_TYPES = [
2923
3998
  "message.sent",
2924
3999
  "message.failed",
@@ -2962,40 +4037,47 @@ var WEBHOOK_EVENT_TYPES = [
2962
4037
  "template.updated",
2963
4038
  "template.deleted"
2964
4039
  ];
2965
- var WebhookEventType = import_zod12.z.enum(WEBHOOK_EVENT_TYPES);
2966
- var MessageEventBase = import_zod12.z.object({
4040
+ var WebhookEventType = import_zod13.z.enum(WEBHOOK_EVENT_TYPES);
4041
+ var MessageEventBase = import_zod13.z.object({
2967
4042
  message_id: Id,
2968
4043
  mailing_id: Id.nullable(),
2969
4044
  mailing_metadata: MailingMetadata.nullable(),
2970
4045
  recipient_id: Id.nullable(),
2971
4046
  contact_id: Id.nullable(),
2972
- external_id: import_zod12.z.string().nullable(),
4047
+ external_id: import_zod13.z.string().nullable(),
2973
4048
  email: Email,
2974
- subject: import_zod12.z.string(),
4049
+ subject: import_zod13.z.string(),
2975
4050
  topic: Slug.nullable(),
2976
- provider_message_id: import_zod12.z.string().nullable(),
2977
- is_test: import_zod12.z.boolean()
4051
+ provider_message_id: import_zod13.z.string().nullable(),
4052
+ is_test: import_zod13.z.boolean()
2978
4053
  });
2979
4054
  var MessageSentData = MessageEventBase.extend({
2980
4055
  /** The final HTML as sent, so the client can archive it beside its own records. */
2981
- html: import_zod12.z.string(),
2982
- 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()
2983
4065
  });
2984
4066
  var MessageFailedData = MessageEventBase.extend({
2985
- error: import_zod12.z.string(),
4067
+ error: import_zod13.z.string(),
2986
4068
  /** False when the provider rejected permanently; the recipient will not be retried. */
2987
- retryable: import_zod12.z.boolean(),
4069
+ retryable: import_zod13.z.boolean(),
2988
4070
  failed_at: Timestamp
2989
4071
  });
2990
4072
  var UNSUBSCRIBE_SOURCES = ["hosted_page", "one_click", "api", "dashboard", "signup_form"];
2991
- var ContactUnsubscribedData = import_zod12.z.object({
4073
+ var ContactUnsubscribedData = import_zod13.z.object({
2992
4074
  contact_id: Id.nullable(),
2993
- external_id: import_zod12.z.string().nullable(),
4075
+ external_id: import_zod13.z.string().nullable(),
2994
4076
  email: Email,
2995
4077
  /** The topic unsubscribed from; null means every topic. */
2996
4078
  topic: Slug.nullable(),
2997
4079
  mailing_id: Id.nullable(),
2998
- source: import_zod12.z.enum(UNSUBSCRIBE_SOURCES),
4080
+ source: import_zod13.z.enum(UNSUBSCRIBE_SOURCES),
2999
4081
  unsubscribed_at: Timestamp
3000
4082
  });
3001
4083
  var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: true }).extend({
@@ -3004,142 +4086,142 @@ var ContactResubscribedData = ContactUnsubscribedData.omit({ unsubscribed_at: tr
3004
4086
  * breaker undid a `bounced` block it judged false (a `contact.bounced` for the
3005
4087
  * same address and mailing went out before); `topic` is null.
3006
4088
  */
3007
- source: import_zod12.z.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
4089
+ source: import_zod13.z.enum([...UNSUBSCRIBE_SOURCES, "bounce_reverted"]),
3008
4090
  resubscribed_at: Timestamp
3009
4091
  });
3010
- var ContactBouncedData = import_zod12.z.object({
4092
+ var ContactBouncedData = import_zod13.z.object({
3011
4093
  contact_id: Id.nullable(),
3012
- external_id: import_zod12.z.string().nullable(),
4094
+ external_id: import_zod13.z.string().nullable(),
3013
4095
  email: Email,
3014
4096
  /** `bounced` for a hard bounce, `complained` for a spam complaint. */
3015
- reason: import_zod12.z.enum(["bounced", "complained"]),
4097
+ reason: import_zod13.z.enum(["bounced", "complained"]),
3016
4098
  message_id: Id.nullable(),
3017
- diagnostic: import_zod12.z.string().nullable(),
4099
+ diagnostic: import_zod13.z.string().nullable(),
3018
4100
  bounced_at: Timestamp
3019
4101
  });
3020
- var MailingFinishedData = import_zod12.z.object({
4102
+ var MailingFinishedData = import_zod13.z.object({
3021
4103
  mailing_id: Id,
3022
4104
  mailing_metadata: MailingMetadata,
3023
- status: import_zod12.z.enum(["sent", "partially_failed", "cancelled"]),
4105
+ status: import_zod13.z.enum(["sent", "partially_failed", "cancelled"]),
3024
4106
  counts: MailingCounts,
3025
4107
  finished_at: Timestamp
3026
4108
  });
3027
- var ContactSubscribedData = import_zod12.z.object({
4109
+ var ContactSubscribedData = import_zod13.z.object({
3028
4110
  contact_id: Id,
3029
- external_id: import_zod12.z.string().nullable(),
4111
+ external_id: import_zod13.z.string().nullable(),
3030
4112
  email: Email,
3031
- topics: import_zod12.z.array(Slug),
3032
- 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"]),
3033
4115
  signup_form_id: Id.nullable(),
3034
- signup_form_version: import_zod12.z.number().int().min(1).nullable(),
4116
+ signup_form_version: import_zod13.z.number().int().min(1).nullable(),
3035
4117
  subscribed_at: Timestamp
3036
4118
  });
3037
- var ImportFinishedData = import_zod12.z.object({
4119
+ var ImportFinishedData = import_zod13.z.object({
3038
4120
  import_id: Id,
3039
- status: import_zod12.z.enum(["completed", "cancelled", "failed"]),
4121
+ status: import_zod13.z.enum(["completed", "cancelled", "failed"]),
3040
4122
  result: ImportReport.nullable(),
3041
- error: import_zod12.z.string().nullable(),
4123
+ error: import_zod13.z.string().nullable(),
3042
4124
  finished_at: Timestamp
3043
4125
  });
3044
- var MailingScheduledData = import_zod12.z.object({
4126
+ var MailingScheduledData = import_zod13.z.object({
3045
4127
  mailing_id: Id,
3046
4128
  mailing_metadata: MailingMetadata,
3047
4129
  scheduled_at: Timestamp.nullable()
3048
4130
  });
3049
- var MailingStartedData = import_zod12.z.object({
4131
+ var MailingStartedData = import_zod13.z.object({
3050
4132
  mailing_id: Id,
3051
4133
  mailing_metadata: MailingMetadata,
3052
- trigger: import_zod12.z.enum(["send", "schedule"]),
3053
- 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),
3054
4136
  started_at: Timestamp
3055
4137
  });
3056
- var MailingScheduleFailedData = import_zod12.z.object({
4138
+ var MailingScheduleFailedData = import_zod13.z.object({
3057
4139
  mailing_id: Id,
3058
4140
  mailing_metadata: MailingMetadata,
3059
- code: import_zod12.z.string(),
3060
- message: import_zod12.z.string(),
4141
+ code: import_zod13.z.string(),
4142
+ message: import_zod13.z.string(),
3061
4143
  failed_at: Timestamp
3062
4144
  });
3063
- var MailingAbWinnerSelectedData = import_zod12.z.object({
4145
+ var MailingAbWinnerSelectedData = import_zod13.z.object({
3064
4146
  mailing_id: Id,
3065
4147
  mailing_metadata: MailingMetadata,
3066
- winner: import_zod12.z.string(),
3067
- decided_by: import_zod12.z.enum(["metric", "manual"]),
3068
- 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),
3069
4151
  decided_at: Timestamp
3070
4152
  });
3071
- var ContactTaggedData = import_zod12.z.object({
4153
+ var ContactTaggedData = import_zod13.z.object({
3072
4154
  contact_id: Id,
3073
- external_id: import_zod12.z.string().nullable(),
4155
+ external_id: import_zod13.z.string().nullable(),
3074
4156
  email: Email,
3075
4157
  /** Only the tags the contact did not already have. */
3076
- tags: import_zod12.z.array(Slug).min(1),
3077
- 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"]),
3078
4160
  tagged_at: Timestamp
3079
4161
  });
3080
- var ContactUpdatedData = import_zod12.z.object({
4162
+ var ContactUpdatedData = import_zod13.z.object({
3081
4163
  contact_id: Id,
3082
- external_id: import_zod12.z.string().nullable(),
4164
+ external_id: import_zod13.z.string().nullable(),
3083
4165
  email: Email,
3084
- changed: import_zod12.z.array(import_zod12.z.string()).min(1),
4166
+ changed: import_zod13.z.array(import_zod13.z.string()).min(1),
3085
4167
  properties: Properties,
3086
- source: import_zod12.z.enum(["api", "dashboard", "signup_form", "import", "automation"]),
4168
+ source: import_zod13.z.enum(["api", "dashboard", "signup_form", "import", "automation"]),
3087
4169
  updated_at: Timestamp
3088
4170
  });
3089
- var AutomationRunEventBase = import_zod12.z.object({
4171
+ var AutomationRunEventBase = import_zod13.z.object({
3090
4172
  automation_id: Id,
3091
- automation_name: import_zod12.z.string(),
4173
+ automation_name: import_zod13.z.string(),
3092
4174
  run_id: Id,
3093
- version: import_zod12.z.number().int().min(1),
4175
+ version: import_zod13.z.number().int().min(1),
3094
4176
  contact_id: Id,
3095
- external_id: import_zod12.z.string().nullable(),
4177
+ external_id: import_zod13.z.string().nullable(),
3096
4178
  email: Email
3097
4179
  });
3098
4180
  var AutomationRunStartedData = AutomationRunEventBase.extend({
3099
4181
  /** What let them in: a trigger kind, or `manual` for an explicit enrolment. */
3100
- trigger_kind: import_zod12.z.string(),
4182
+ trigger_kind: import_zod13.z.string(),
3101
4183
  entered_at: Timestamp
3102
4184
  });
3103
4185
  var AutomationRunCompletedData = AutomationRunEventBase.extend({
3104
- last_step_id: import_zod12.z.string().nullable(),
4186
+ last_step_id: import_zod13.z.string().nullable(),
3105
4187
  completed_at: Timestamp
3106
4188
  });
3107
4189
  var AutomationRunExitedData = AutomationRunEventBase.extend({
3108
4190
  reason: AutomationEndReason,
3109
- step_id: import_zod12.z.string().nullable(),
4191
+ step_id: import_zod13.z.string().nullable(),
3110
4192
  exited_at: Timestamp
3111
4193
  });
3112
4194
  var AutomationRunFailedData = AutomationRunEventBase.extend({
3113
- reason: import_zod12.z.string(),
3114
- step_id: import_zod12.z.string().nullable(),
4195
+ reason: import_zod13.z.string(),
4196
+ step_id: import_zod13.z.string().nullable(),
3115
4197
  failed_at: Timestamp
3116
4198
  });
3117
4199
  var AutomationWebhookData = AutomationRunEventBase.extend({
3118
- step_id: import_zod12.z.string(),
4200
+ step_id: import_zod13.z.string(),
3119
4201
  /** The step's own label, when it was given one, so a receiver can branch on a name. */
3120
- step_label: import_zod12.z.string().nullable(),
4202
+ step_label: import_zod13.z.string().nullable(),
3121
4203
  /** Whatever the step carries, verbatim. Null when it carries nothing. */
3122
4204
  payload: JsonValue.nullable(),
3123
4205
  fired_at: Timestamp
3124
4206
  });
3125
- var TemplateEventBase = import_zod12.z.object({
4207
+ var TemplateEventBase = import_zod13.z.object({
3126
4208
  template_id: Id,
3127
- name: import_zod12.z.string().min(1).max(200),
3128
- version: import_zod12.z.number().int().min(1),
3129
- 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"])
3130
4212
  });
3131
4213
  var TemplateCreatedData = TemplateEventBase.extend({ created_at: Timestamp });
3132
- 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 });
3133
4215
  var TemplateDeletedData = TemplateEventBase.extend({ deleted_at: Timestamp });
3134
- var envelope = (type, data) => import_zod12.z.object({
4216
+ var envelope = (type, data) => import_zod13.z.object({
3135
4217
  /** Unique per event: a receiver deduplicates on it (deliveries may repeat). */
3136
4218
  id: Id,
3137
- type: import_zod12.z.literal(type),
4219
+ type: import_zod13.z.literal(type),
3138
4220
  created_at: Timestamp,
3139
4221
  workspace_id: Id,
3140
4222
  data
3141
4223
  });
3142
- var WebhookEvent = import_zod12.z.discriminatedUnion("type", [
4224
+ var WebhookEvent = import_zod13.z.discriminatedUnion("type", [
3143
4225
  envelope("message.sent", MessageSentData),
3144
4226
  envelope("message.failed", MessageFailedData),
3145
4227
  envelope("contact.unsubscribed", ContactUnsubscribedData),
@@ -3163,16 +4245,16 @@ var WebhookEvent = import_zod12.z.discriminatedUnion("type", [
3163
4245
  envelope("template.updated", TemplateUpdatedData),
3164
4246
  envelope("template.deleted", TemplateDeletedData)
3165
4247
  ]);
3166
- var WebhookEndpoint = import_zod12.z.object({
4248
+ var WebhookEndpoint = import_zod13.z.object({
3167
4249
  id: Id,
3168
- url: import_zod12.z.string().url(),
3169
- description: import_zod12.z.string().max(500).nullable(),
3170
- events: import_zod12.z.array(WebhookEventType).min(1),
3171
- 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(),
3172
4254
  created_at: Timestamp,
3173
4255
  updated_at: Timestamp
3174
4256
  });
3175
- var WebhookUrl = import_zod12.z.string().url().refine((u) => {
4257
+ var WebhookUrl = import_zod13.z.string().url().refine((u) => {
3176
4258
  try {
3177
4259
  const url = new URL(u);
3178
4260
  if (url.protocol === "https:") return true;
@@ -3181,35 +4263,35 @@ var WebhookUrl = import_zod12.z.string().url().refine((u) => {
3181
4263
  return false;
3182
4264
  }
3183
4265
  }, "must be https (http only for localhost)");
3184
- var WebhookEndpointCreate = import_zod12.z.object({
4266
+ var WebhookEndpointCreate = import_zod13.z.object({
3185
4267
  url: WebhookUrl,
3186
- description: import_zod12.z.string().max(500).optional(),
3187
- events: import_zod12.z.array(WebhookEventType).min(1),
3188
- 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()
3189
4271
  });
3190
- var WebhookEndpointUpdate = import_zod12.z.object({
4272
+ var WebhookEndpointUpdate = import_zod13.z.object({
3191
4273
  url: WebhookUrl,
3192
- description: import_zod12.z.string().max(500).nullable(),
3193
- events: import_zod12.z.array(WebhookEventType).min(1),
3194
- 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()
3195
4277
  }).partial().refine((v) => Object.keys(v).length > 0, "at least one field");
3196
- var WebhookEndpointWithSecret = import_zod12.z.object({
4278
+ var WebhookEndpointWithSecret = import_zod13.z.object({
3197
4279
  endpoint: WebhookEndpoint,
3198
- secret: import_zod12.z.string().min(32)
4280
+ secret: import_zod13.z.string().min(32)
3199
4281
  });
3200
4282
  var WEBHOOK_DELIVERY_STATUSES = ["pending", "succeeded", "failed"];
3201
- var WebhookDeliveryStatus = import_zod12.z.enum(WEBHOOK_DELIVERY_STATUSES);
4283
+ var WebhookDeliveryStatus = import_zod13.z.enum(WEBHOOK_DELIVERY_STATUSES);
3202
4284
  var WEBHOOK_MAX_ATTEMPTS = 8;
3203
4285
  var WEBHOOK_RETRY_DELAYS_SECONDS = [30, 120, 600, 1800, 3600, 7200, 21600];
3204
- var WebhookDelivery = import_zod12.z.object({
4286
+ var WebhookDelivery = import_zod13.z.object({
3205
4287
  id: Id,
3206
4288
  endpoint_id: Id,
3207
4289
  event_id: Id,
3208
4290
  event_type: WebhookEventType,
3209
4291
  status: WebhookDeliveryStatus,
3210
- attempts: import_zod12.z.number().int().min(0),
3211
- last_status_code: import_zod12.z.number().int().min(100).max(599).nullable(),
3212
- 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(),
3213
4295
  next_attempt_at: Timestamp.nullable(),
3214
4296
  delivered_at: Timestamp.nullable(),
3215
4297
  created_at: Timestamp
@@ -3218,7 +4300,7 @@ var WebhookDeliveryListQuery = PageQuery.extend({
3218
4300
  status: WebhookDeliveryStatus.optional(),
3219
4301
  event_type: WebhookEventType.optional()
3220
4302
  });
3221
- var WebhookDeliveryParams = import_zod12.z.object({ id: Id, delivery_id: Id });
4303
+ var WebhookDeliveryParams = import_zod13.z.object({ id: Id, delivery_id: Id });
3222
4304
 
3223
4305
  // src/webhook-signing.ts
3224
4306
  var WEBHOOK_SIGNATURE_HEADER = "x-mail-signature";
@@ -3285,79 +4367,79 @@ async function verifyWebhook(input) {
3285
4367
  }
3286
4368
 
3287
4369
  // src/billing.ts
3288
- var import_zod13 = require("zod");
4370
+ var import_zod14 = require("zod");
3289
4371
  var PLAN_IDS = ["free", "starter", "growth", "design_partner"];
3290
- var PlanId = import_zod13.z.enum(PLAN_IDS);
3291
- var PlanLimits = import_zod13.z.object({
3292
- monthly_messages: import_zod13.z.number().int().min(0).nullable(),
3293
- contacts: import_zod13.z.number().int().min(0).nullable(),
3294
- members: import_zod13.z.number().int().min(1).nullable(),
3295
- providers: import_zod13.z.number().int().min(1).nullable(),
3296
- 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(),
3297
4379
  /** Automations that may be live (active or paused) at once (A1). */
3298
- active_automations: import_zod13.z.number().int().min(0).nullable(),
4380
+ active_automations: import_zod14.z.number().int().min(0).nullable(),
3299
4381
  /** Steps one automation may hold. Null is the flow's own cap, not unlimited. */
3300
- automation_steps: import_zod13.z.number().int().min(1).nullable()
4382
+ automation_steps: import_zod14.z.number().int().min(1).nullable()
3301
4383
  });
3302
- var PlanFeatures = import_zod13.z.object({
4384
+ var PlanFeatures = import_zod14.z.object({
3303
4385
  /** A/B tests of subject and content on a mailing (S4). */
3304
- ab_testing: import_zod13.z.boolean(),
4386
+ ab_testing: import_zod14.z.boolean(),
3305
4387
  /** Open and click tracking, still off until a workspace turns it on (`settings.tracking_enabled`). */
3306
- tracking: import_zod13.z.boolean(),
4388
+ tracking: import_zod14.z.boolean(),
3307
4389
  /** Sending and hosting pages on the workspace's own domain (a later phase). */
3308
- custom_domains: import_zod13.z.boolean(),
4390
+ custom_domains: import_zod14.z.boolean(),
3309
4391
  /** Automations: flows a person walks through step by step (A1). */
3310
- automations: import_zod13.z.boolean()
4392
+ automations: import_zod14.z.boolean()
3311
4393
  });
3312
4394
  var PLAN_FEATURES = ["ab_testing", "tracking", "custom_domains", "automations"];
3313
- var Plan = import_zod13.z.object({
4395
+ var Plan = import_zod14.z.object({
3314
4396
  id: PlanId,
3315
- name: import_zod13.z.string().min(1),
4397
+ name: import_zod14.z.string().min(1),
3316
4398
  /** Price in the smallest currency unit per month; null for plans not sold. */
3317
- monthly_price_cents: import_zod13.z.number().int().min(0).nullable(),
3318
- 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),
3319
4401
  limits: PlanLimits,
3320
4402
  features: PlanFeatures
3321
4403
  });
3322
- var CatalogPlan = Plan.extend({ sellable: import_zod13.z.boolean() });
3323
- var Subscription = import_zod13.z.object({
4404
+ var CatalogPlan = Plan.extend({ sellable: import_zod14.z.boolean() });
4405
+ var Subscription = import_zod14.z.object({
3324
4406
  workspace_id: Id,
3325
4407
  plan: PlanId,
3326
- status: import_zod13.z.enum(["active", "trialing", "past_due", "cancelled"]),
4408
+ status: import_zod14.z.enum(["active", "trialing", "past_due", "cancelled"]),
3327
4409
  current_period_start: Timestamp,
3328
4410
  current_period_end: Timestamp,
3329
- cancel_at_period_end: import_zod13.z.boolean(),
4411
+ cancel_at_period_end: import_zod14.z.boolean(),
3330
4412
  /**
3331
4413
  * A design partner outside billing (plan `design_partner`, no limits, no
3332
4414
  * checkout). Set only by an operator, never through the API.
3333
4415
  */
3334
- billing_exempt: import_zod13.z.boolean()
4416
+ billing_exempt: import_zod14.z.boolean()
3335
4417
  });
3336
4418
  var USAGE_METRICS = ["messages", "contacts", "members", "providers", "webhook_endpoints", "active_automations"];
3337
- var UsageMetric = import_zod13.z.enum(USAGE_METRICS);
4419
+ var UsageMetric = import_zod14.z.enum(USAGE_METRICS);
3338
4420
  var USAGE_WARNING_RATIO = 0.8;
3339
- var UsageWarning = import_zod13.z.object({
4421
+ var UsageWarning = import_zod14.z.object({
3340
4422
  metric: UsageMetric,
3341
- used: import_zod13.z.number().int().min(0),
3342
- 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),
3343
4425
  /** `approaching`: at or above USAGE_WARNING_RATIO of the limit; `reached`: at or above the limit. */
3344
- level: import_zod13.z.enum(["approaching", "reached"])
4426
+ level: import_zod14.z.enum(["approaching", "reached"])
3345
4427
  });
3346
- var Usage = import_zod13.z.object({
4428
+ var Usage = import_zod14.z.object({
3347
4429
  plan: PlanId,
3348
4430
  period_start: Timestamp,
3349
4431
  period_end: Timestamp,
3350
- 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() })),
3351
- 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)
3352
4434
  });
3353
- var CheckoutRequest = import_zod13.z.object({
3354
- plan: import_zod13.z.enum(["starter", "growth"]),
3355
- success_url: import_zod13.z.string().url(),
3356
- 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()
3357
4439
  });
3358
- var CheckoutSession = import_zod13.z.object({ url: import_zod13.z.string().url() });
3359
- var PortalRequest = import_zod13.z.object({ return_url: import_zod13.z.string().url() });
3360
- 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() });
3361
4443
  var USAGE_WARNING_HEADER = "x-mail-usage-warning";
3362
4444
  function formatUsageWarningHeader(warnings) {
3363
4445
  if (warnings.length === 0) return null;
@@ -3377,7 +4459,7 @@ function parseUsageWarningHeader(value) {
3377
4459
  }
3378
4460
 
3379
4461
  // src/routes.ts
3380
- var import_zod14 = require("zod");
4462
+ var import_zod15 = require("zod");
3381
4463
  var list = (item) => page(item);
3382
4464
  var foundationRoutes = {
3383
4465
  "workspaces.create": {
@@ -3585,7 +4667,7 @@ var templateRoutes = {
3585
4667
  path: "/v1/templates/:id/export",
3586
4668
  params: IdParams,
3587
4669
  query: TemplateExportQuery,
3588
- response: import_zod14.z.string(),
4670
+ response: import_zod15.z.string(),
3589
4671
  responseType: "text",
3590
4672
  status: 200,
3591
4673
  access: "read",
@@ -3909,7 +4991,7 @@ var sendingRoutes = {
3909
4991
  path: "/v1/mailings/:id/export",
3910
4992
  params: IdParams,
3911
4993
  query: MailingExportQuery,
3912
- response: import_zod14.z.string(),
4994
+ response: import_zod15.z.string(),
3913
4995
  responseType: "text",
3914
4996
  status: 200,
3915
4997
  access: "read",
@@ -4142,7 +5224,7 @@ var platformRoutes = {
4142
5224
  "contactProperties.list": {
4143
5225
  method: "GET",
4144
5226
  path: "/v1/contact-properties",
4145
- response: import_zod14.z.object({ data: import_zod14.z.array(ContactPropertyDefinition) }),
5227
+ response: import_zod15.z.object({ data: import_zod15.z.array(ContactPropertyDefinition) }),
4146
5228
  status: 200,
4147
5229
  access: "read",
4148
5230
  phase: "S4"
@@ -4278,6 +5360,15 @@ var platformRoutes = {
4278
5360
  access: "read",
4279
5361
  phase: "S4"
4280
5362
  },
5363
+ "mailings.languages": {
5364
+ method: "GET",
5365
+ path: "/v1/mailings/:id/languages",
5366
+ params: IdParams,
5367
+ response: MailingLanguageReport,
5368
+ status: 200,
5369
+ access: "read",
5370
+ phase: "S4"
5371
+ },
4281
5372
  "signupForms.list": {
4282
5373
  method: "GET",
4283
5374
  path: "/v1/signup-forms",
@@ -4432,7 +5523,7 @@ var billingRoutes = {
4432
5523
  "billing.plans": {
4433
5524
  method: "GET",
4434
5525
  path: "/v1/billing/plans",
4435
- response: import_zod14.z.object({ data: import_zod14.z.array(CatalogPlan) }),
5526
+ response: import_zod15.z.object({ data: import_zod15.z.array(CatalogPlan) }),
4436
5527
  status: 200,
4437
5528
  access: "read",
4438
5529
  phase: "S5"
@@ -4574,7 +5665,7 @@ var automationRoutes = {
4574
5665
  method: "POST",
4575
5666
  path: "/v1/automations/:id/pause",
4576
5667
  params: IdParams,
4577
- body: import_zod14.z.object({}).strict(),
5668
+ body: import_zod15.z.object({}).strict(),
4578
5669
  response: Automation,
4579
5670
  status: 200,
4580
5671
  access: "write",
@@ -4584,7 +5675,7 @@ var automationRoutes = {
4584
5675
  method: "POST",
4585
5676
  path: "/v1/automations/:id/resume",
4586
5677
  params: IdParams,
4587
- body: import_zod14.z.object({}).strict(),
5678
+ body: import_zod15.z.object({}).strict(),
4588
5679
  response: Automation,
4589
5680
  status: 200,
4590
5681
  access: "write",
@@ -4594,7 +5685,7 @@ var automationRoutes = {
4594
5685
  method: "POST",
4595
5686
  path: "/v1/automations/:id/archive",
4596
5687
  params: IdParams,
4597
- body: import_zod14.z.object({}).strict(),
5688
+ body: import_zod15.z.object({}).strict(),
4598
5689
  response: Automation,
4599
5690
  status: 200,
4600
5691
  access: "write",
@@ -4728,13 +5819,13 @@ function matchRoute(method, pathname) {
4728
5819
  for (const [id, route] of Object.entries(routes)) {
4729
5820
  if (route.method !== method) continue;
4730
5821
  const names = [];
4731
- const pattern = new RegExp(
5822
+ const pattern2 = new RegExp(
4732
5823
  "^" + route.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:([a-z_]+)/g, (_, name) => {
4733
5824
  names.push(name);
4734
5825
  return "([^/]+)";
4735
5826
  }) + "$"
4736
5827
  );
4737
- const match = pattern.exec(pathname);
5828
+ const match = pattern2.exec(pathname);
4738
5829
  if (!match) continue;
4739
5830
  const params = {};
4740
5831
  names.forEach((name, i) => {
@@ -4876,6 +5967,7 @@ function matchRoute(method, pathname) {
4876
5967
  EXPORT_WARNING_COUNT_HEADER,
4877
5968
  Email,
4878
5969
  EmailStep,
5970
+ EmailStepLanguage,
4879
5971
  ErrorBody,
4880
5972
  ErrorCodeSchema,
4881
5973
  Event,
@@ -4900,6 +5992,8 @@ function matchRoute(method, pathname) {
4900
5992
  IMPORT_SKIP_REASONS,
4901
5993
  IMPORT_STATUSES,
4902
5994
  IMPORT_TERMINAL_STATUSES,
5995
+ INSERTION_PROBLEM_REASONS,
5996
+ INSERTION_TEXT_SLOTS,
4903
5997
  INVITE_REFUSAL_REASONS,
4904
5998
  INVITE_STATUSES,
4905
5999
  Id,
@@ -4917,6 +6011,13 @@ function matchRoute(method, pathname) {
4917
6011
  ImportStatus,
4918
6012
  ImportWarning,
4919
6013
  ImportedAsset,
6014
+ Insertion,
6015
+ InsertionChoice,
6016
+ InsertionChoiceKey,
6017
+ InsertionChoices,
6018
+ InsertionName,
6019
+ InsertionText,
6020
+ Insertions,
4920
6021
  Invite,
4921
6022
  InviteAccept,
4922
6023
  InviteAccepted,
@@ -4927,6 +6028,8 @@ function matchRoute(method, pathname) {
4927
6028
  JsonValue,
4928
6029
  LETTER_RECIPIENT_REFUSAL,
4929
6030
  LIST_UNSUBSCRIBE_POST_VALUE,
6031
+ LOCALE_TAG_PATTERN,
6032
+ LocaleTag,
4930
6033
  MAILING_ACTIONS,
4931
6034
  MAILING_KINDS,
4932
6035
  MAILING_STATUSES,
@@ -4942,6 +6045,9 @@ function matchRoute(method, pathname) {
4942
6045
  MAX_IMPORT_BYTES,
4943
6046
  MAX_IMPORT_ROWS,
4944
6047
  MAX_IMPORT_URL_LENGTH,
6048
+ MAX_INSERTIONS,
6049
+ MAX_INSERTION_CHOICES,
6050
+ MAX_INSERTION_TEXT_LENGTH,
4945
6051
  MAX_INVITE_TTL_DAYS,
4946
6052
  MAX_IN_LIST,
4947
6053
  MAX_LETTER_RECIPIENTS,
@@ -4951,12 +6057,15 @@ function matchRoute(method, pathname) {
4951
6057
  MAX_MJML_ELEMENTS,
4952
6058
  MAX_MJML_IMPORT_BYTES,
4953
6059
  MAX_PAGE_LIMIT,
6060
+ MAX_PREHEADER_LENGTH,
4954
6061
  MAX_RECIPIENTS_PER_BATCH,
4955
6062
  MAX_RE_ENTRY_COOLDOWN_SECONDS,
4956
6063
  MAX_SAVED_SECTIONS,
4957
6064
  MAX_SPLIT_BRANCHES,
4958
6065
  MAX_STEPS,
4959
6066
  MAX_STEP_VISITS,
6067
+ MAX_SUBJECT_LENGTH,
6068
+ MAX_TEMPLATE_TRANSLATIONS,
4960
6069
  MAX_TRIGGERS,
4961
6070
  MEMBER_ROLES,
4962
6071
  MERGE_FIELD_PATTERN,
@@ -4974,6 +6083,9 @@ function matchRoute(method, pathname) {
4974
6083
  MailingExportQuery,
4975
6084
  MailingFinishedData,
4976
6085
  MailingKind,
6086
+ MailingLanguage,
6087
+ MailingLanguageReport,
6088
+ MailingLanguageStatus,
4977
6089
  MailingListQuery,
4978
6090
  MailingMetadata,
4979
6091
  MailingRetryFailedRequest,
@@ -5008,6 +6120,8 @@ function matchRoute(method, pathname) {
5008
6120
  Ok,
5009
6121
  PLAN_FEATURES,
5010
6122
  PLAN_IDS,
6123
+ PREVIEW_EMPTY,
6124
+ PREVIEW_FILLED,
5011
6125
  PROVIDER_KINDS,
5012
6126
  PageQuery,
5013
6127
  Plan,
@@ -5074,6 +6188,7 @@ function matchRoute(method, pathname) {
5074
6188
  SuppressionCreate,
5075
6189
  SuppressionListQuery,
5076
6190
  SuppressionReason,
6191
+ TEMPLATE_LANGUAGE_STATUSES,
5077
6192
  TERMINAL_MAILING_STATUSES,
5078
6193
  Tag,
5079
6194
  TagAssignment,
@@ -5089,8 +6204,13 @@ function matchRoute(method, pathname) {
5089
6204
  TemplateImportPreview,
5090
6205
  TemplateImportPreviewRequest,
5091
6206
  TemplateImportResult,
6207
+ TemplateLanguageStatus,
5092
6208
  TemplateListQuery,
6209
+ TemplatePreheader,
6210
+ TemplateSubject,
5093
6211
  TemplateSummary,
6212
+ TemplateTranslation,
6213
+ TemplateTranslations,
5094
6214
  TemplateUpdate,
5095
6215
  TemplateUpdatedData,
5096
6216
  TemplateVersion,
@@ -5102,6 +6222,8 @@ function matchRoute(method, pathname) {
5102
6222
  TopicCreate,
5103
6223
  TopicUpdate,
5104
6224
  TrackingSettings,
6225
+ TranslationInsertion,
6226
+ TranslationInsertions,
5105
6227
  UNSUBSCRIBE_PATH_PREFIX,
5106
6228
  UNSUBSCRIBE_SOURCES,
5107
6229
  USAGE_METRICS,
@@ -5143,16 +6265,39 @@ function matchRoute(method, pathname) {
5143
6265
  WorkspaceSettings,
5144
6266
  WorkspaceUpdate,
5145
6267
  acceptsIdempotencyKey,
6268
+ addInsertion,
6269
+ addInsertionChoice,
6270
+ addLanguageDraft,
6271
+ alignTranslationInsertions,
5146
6272
  alwaysWaits,
5147
6273
  automationFilterFamily,
5148
6274
  automationRoutes,
5149
6275
  billingRoutes,
5150
6276
  buildPath,
6277
+ buildTemplatePatch,
5151
6278
  canTransition,
6279
+ canonicalLocale,
6280
+ choiceKeyProblem,
6281
+ chooseInsertion,
5152
6282
  cycles,
6283
+ defaultPreviewPick,
5153
6284
  defaultSettings,
6285
+ demoteForSave,
6286
+ demotedBySave,
6287
+ describeMissingTexts,
6288
+ draftDocument,
6289
+ draftInsertionProblems,
6290
+ draftsDirty,
6291
+ draftsFromTemplate,
6292
+ draftsPatch,
6293
+ editInsertionText,
6294
+ editLanguageDraft,
6295
+ editMainInsertionText,
6296
+ editMainInsertions,
6297
+ effectivePreviewPicks,
5154
6298
  emptyFlow,
5155
6299
  errorBody,
6300
+ escapeMergeHtml,
5156
6301
  exportFilename,
5157
6302
  filterConditions,
5158
6303
  filterDepth,
@@ -5162,30 +6307,78 @@ function matchRoute(method, pathname) {
5162
6307
  formatExportWarningsHeader,
5163
6308
  formatUsageWarningHeader,
5164
6309
  foundationRoutes,
6310
+ insertionByProblem,
6311
+ insertionChoices,
6312
+ insertionCoverage,
6313
+ insertionInputs,
6314
+ insertionNameProblem,
6315
+ insertionProblems,
6316
+ insertionRefusalHint,
6317
+ insertionSlotLabel,
6318
+ insertionTextSlots,
6319
+ insertionTokens,
6320
+ insertionValue,
5165
6321
  inviteRoutes,
5166
6322
  isDayEntirelyPast,
5167
6323
  isPublishable,
5168
6324
  isRetryableError,
6325
+ keepLanguageDocument,
6326
+ languageInsertions,
6327
+ languageName,
6328
+ languageTabs,
6329
+ mainInsertionText,
6330
+ matchLocale,
5169
6331
  matchRoute,
6332
+ missingInsertionTexts,
5170
6333
  missingRequiredMergeFields,
6334
+ normalizeChoiceKey,
5171
6335
  nowSeconds,
5172
6336
  operationsWithAccess,
6337
+ ownEntry,
5173
6338
  page,
5174
6339
  parseExportWarningsHeader,
5175
6340
  parseUsageWarningHeader,
6341
+ personalizeHtml,
6342
+ personalizeText,
6343
+ placementProblems,
5176
6344
  platformRoutes,
6345
+ previewControls,
6346
+ previewInsertions,
6347
+ previewPersonalization,
6348
+ primaryLanguage,
5177
6349
  reachable,
6350
+ readyWithoutInsertions,
6351
+ readyWithoutSubject,
6352
+ rebaseDrafts,
6353
+ removeInsertion,
6354
+ removeInsertionChoice,
6355
+ removeLanguageDraft,
6356
+ renameInsertion,
6357
+ renameInsertionChecked,
6358
+ renameInsertionChoice,
6359
+ renameInsertionChoiceChecked,
6360
+ resolveInsertions,
6361
+ restorePatch,
5178
6362
  routes,
6363
+ sampleMergeValue,
6364
+ saveDemotions,
5179
6365
  sendingRoutes,
6366
+ setInsertionBy,
6367
+ setInsertionFilled,
6368
+ setLanguageStatus,
5180
6369
  signWebhook,
5181
6370
  statusForError,
5182
6371
  stepTargets,
6372
+ storedTemplateLine,
5183
6373
  stronglyConnected,
5184
6374
  templateRoutes,
5185
6375
  testGroupSize,
5186
6376
  tightLoops,
5187
6377
  timingSafeEqualString,
6378
+ translationInsertionProblems,
6379
+ translationTextFromInput,
5188
6380
  triggerKey,
6381
+ unplacedInsertions,
5189
6382
  validateAutomation,
5190
6383
  verifyWebhook
5191
6384
  });